From d01d4ac48b0583a5655c0d76a1148e4c6bd20a66 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 18 Jun 2020 18:49:57 +0300 Subject: [PATCH 001/463] Created oauth2 client_registration and mapper_config (as separate entities) --- .../main/data/upgrade/3.1.0/schema_update.sql | 56 ++++++++++ .../server/common/data/oauth2/MapperType.java | 5 + .../data/oauth2/OAuth2BasicMapperConfig.java | 21 ++++ .../data/oauth2/OAuth2ClientRegistration.java | 57 ++++++++++ .../data/oauth2/OAuth2CustomMapperConfig.java | 16 +++ .../data/oauth2/OAuth2MapperConfig.java | 34 ++++++ .../server/dao/model/ModelConstants.java | 40 +++++++ .../sql/OAuth2ClientRegistrationEntity.java | 99 +++++++++++++++++ .../model/sql/OAuth2MapperConfigEntity.java | 101 ++++++++++++++++++ .../resources/sql/schema-entities-hsql.sql | 36 +++++++ .../main/resources/sql/schema-entities.sql | 38 +++++++ 11 files changed, 503 insertions(+) create mode 100644 application/src/main/data/upgrade/3.1.0/schema_update.sql create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2MapperConfigEntity.java diff --git a/application/src/main/data/upgrade/3.1.0/schema_update.sql b/application/src/main/data/upgrade/3.1.0/schema_update.sql new file mode 100644 index 0000000000..3ee67b03a4 --- /dev/null +++ b/application/src/main/data/upgrade/3.1.0/schema_update.sql @@ -0,0 +1,56 @@ +-- +-- Copyright © 2016-2020 The Thingsboard Authors +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +DROP TABLE IF EXISTS oauth2_client_registration; + +CREATE TABLE IF NOT EXISTS oauth2_client_registration ( + registration_id varchar(255) NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + mapper_config_id varchar(31), + client_id varchar(255), + client_secret varchar(255), + authorization_uri varchar(255), + token_uri varchar(255), + redirect_uri_template varchar(255), + scope varchar(255), + authorization_grant_type varchar(255), + user_info_uri varchar(255), + user_name_attribute varchar(255), + jwk_set_uri varchar(255), + client_authentication_method varchar(255), + client_name varchar(255), + login_button_label varchar(255), + login_button_icon varchar(255) +); + +DROP TABLE IF EXISTS oauth2_mapper_config; + +CREATE TABLE IF NOT EXISTS oauth2_mapper_config ( + id varchar(31) NOT NULL CONSTRAINT oauth2_mapper_config_pkey PRIMARY KEY, + allow_user_creation boolean, + activate_user boolean, + type varchar(31), + basic_email_attribute_key varchar(31), + basic_first_name_attribute_key varchar(31), + basic_last_name_attribute_key varchar(31), + basic_tenant_name_strategy varchar(31), + basic_tenant_name_pattern varchar(255), + basic_customer_name_pattern varchar(255), + basic_default_dashboard_name varchar(255), + basic_always_full_screen boolean, + custom_url varchar(255), + custom_username varchar(255), + custom_password varchar(255) +); \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java new file mode 100644 index 0000000000..d33563b668 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java @@ -0,0 +1,5 @@ +package org.thingsboard.server.common.data.oauth2; + +public enum MapperType { + BASIC, CUSTOM; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java new file mode 100644 index 0000000000..e91daf75e7 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java @@ -0,0 +1,21 @@ +package org.thingsboard.server.common.data.oauth2; + +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@Builder(toBuilder = true) +@EqualsAndHashCode +@Getter +@ToString +public class OAuth2BasicMapperConfig { + private final String emailAttributeKey; + private final String firstNameAttributeKey; + private final String lastNameAttributeKey; + private final String tenantNameStrategy; + private final String tenantNamePattern; + private final String customerNamePattern; + private final String defaultDashboardName; + private final boolean alwaysFullScreen; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java new file mode 100644 index 0000000000..d5bd3b076e --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java @@ -0,0 +1,57 @@ +package org.thingsboard.server.common.data.oauth2; + +import lombok.*; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.id.OAuth2IntegrationId; + +@EqualsAndHashCode(callSuper = true) +@Data +@ToString(exclude = {"clientSecret"}) +public class OAuth2ClientRegistration extends BaseData { + + private String registrationId; + private OAuth2IntegrationId mapperConfigId; + private String clientId; + private String clientSecret; + private String authorizationUri; + private String tokenUri; + private String redirectUriTemplate; + private String scope; + private String authorizationGrantType; + private String userInfoUri; + private String userNameAttribute; + private String jwkSetUri; + private String clientAuthenticationMethod; + private String clientName; + private String loginButtonLabel; + private String loginButtonIcon; + + public OAuth2ClientRegistration() { + super(); + } + + public OAuth2ClientRegistration(OAuth2IntegrationId id) { + super(id); + } + + @Builder(toBuilder = true) + public OAuth2ClientRegistration(OAuth2IntegrationId id, String registrationId, String clientId, String clientSecret, String authorizationUri, String tokenUri, String redirectUriTemplate, String scope, String authorizationGrantType, String userInfoUri, String userNameAttribute, String jwkSetUri, String clientAuthenticationMethod, String clientName, String loginButtonLabel, String loginButtonIcon, OAuth2IntegrationId mapperConfigId) { + super(id); + this.registrationId = registrationId; + this.clientId = clientId; + this.clientSecret = clientSecret; + this.authorizationUri = authorizationUri; + this.tokenUri = tokenUri; + this.redirectUriTemplate = redirectUriTemplate; + this.scope = scope; + this.authorizationGrantType = authorizationGrantType; + this.userInfoUri = userInfoUri; + this.userNameAttribute = userNameAttribute; + this.jwkSetUri = jwkSetUri; + this.clientAuthenticationMethod = clientAuthenticationMethod; + this.clientName = clientName; + this.loginButtonLabel = loginButtonLabel; + this.loginButtonIcon = loginButtonIcon; + this.mapperConfigId = mapperConfigId; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java new file mode 100644 index 0000000000..ea9c963721 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java @@ -0,0 +1,16 @@ +package org.thingsboard.server.common.data.oauth2; + +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@Builder(toBuilder = true) +@EqualsAndHashCode +@Getter +@ToString(exclude = {"password"}) +public class OAuth2CustomMapperConfig { + private final String url; + private final String username; + private final String password; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java new file mode 100644 index 0000000000..95c1fdf4a8 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java @@ -0,0 +1,34 @@ +package org.thingsboard.server.common.data.oauth2; + +import lombok.*; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.id.OAuth2IntegrationId; + +@EqualsAndHashCode(callSuper = true) +@Data +@ToString +public class OAuth2MapperConfig extends BaseData { + private boolean allowUserCreation; + private boolean activateUser; + private MapperType type; + private OAuth2BasicMapperConfig basicConfig; + private OAuth2CustomMapperConfig customConfig; + + public OAuth2MapperConfig() { + super(); + } + + public OAuth2MapperConfig(OAuth2IntegrationId id) { + super(id); + } + + @Builder(toBuilder = true) + public OAuth2MapperConfig(OAuth2IntegrationId id, boolean allowUserCreation, boolean activateUser, MapperType type, OAuth2BasicMapperConfig basicConfig, OAuth2CustomMapperConfig customConfig) { + super(id); + this.allowUserCreation = allowUserCreation; + this.activateUser = activateUser; + this.type = type; + this.basicConfig = basicConfig; + this.customConfig = customConfig; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index f52e450d8b..c551114aa5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -354,6 +354,46 @@ public class ModelConstants { public static final String RULE_NODE_NAME_PROPERTY = "name"; public static final String RULE_NODE_CONFIGURATION_PROPERTY = "configuration"; + /** + * Cassandra OAuth2 client registration constants. + */ + public static final String OAUTH2_CLIENT_REGISTRATION_COLUMN_FAMILY_NAME = "oauth2_client_registration"; + public static final String OAUTH2_CLIENT_REGISTRATION_ID_PROPERTY = "registration_id"; + public static final String OAUTH2_CLIENT_REGISTRATION_MAPPER_CONFIG_ID_PROPERTY = "mapper_config_id"; + public static final String OAUTH2_CLIENT_ID_PROPERTY = "client_id"; + public static final String OAUTH2_CLIENT_SECRET_PROPERTY = "client_secret"; + public static final String OAUTH2_AUTHORIZATION_URI_PROPERTY = "authorization_uri"; + public static final String OAUTH2_TOKEN_URI_PROPERTY = "token_uri"; + public static final String OAUTH2_REDIRECT_URI_TEMPLATE_PROPERTY = "redirect_uri_template"; + public static final String OAUTH2_SCOPE_PROPERTY = "scope"; + public static final String OAUTH2_AUTHORIZATION_GRANT_TYPE_PROPERTY = "authorization_grant_type"; + public static final String OAUTH2_USER_INFO_URI_PROPERTY = "user_info_uri"; + public static final String OAUTH2_USER_NAME_ATTRIBUTE_PROPERTY = "user_name_attribute"; + public static final String OAUTH2_JWK_SET_URI_PROPERTY = "jwk_set_uri"; + public static final String OAUTH2_CLIENT_AUTHENTICATION_METHOD_PROPERTY = "client_authentication_method"; + public static final String OAUTH2_CLIENT_NAME_PROPERTY = "client_name"; + public static final String OAUTH2_LOGIN_BUTTON_LABEL_PROPERTY = "login_button_label"; + public static final String OAUTH2_LOGIN_BUTTON_ICON_PROPERTY = "login_button_icon"; + + /** + * Cassandra OAuth2 mapper config constants. + */ + public static final String OAUTH2_MAPPER_CONFIG_COLUMN_FAMILY_NAME = "oauth2_mapper_config"; + public static final String OAUTH2_ALLOW_USER_CREATION_PROPERTY = "allow_user_creation"; + public static final String OAUTH2_ACTIVATE_USER_PROPERTY = "activate_user"; + public static final String OAUTH2_MAPPER_TYPE_PROPERTY = "type"; + public static final String OAUTH2_EMAIL_ATTRIBUTE_KEY_PROPERTY = "basic_email_attribute_key"; + public static final String OAUTH2_FIRST_NAME_ATTRIBUTE_KEY_PROPERTY = "basic_first_name_attribute_key"; + public static final String OAUTH2_LAST_NAME_ATTRIBUTE_KEY_PROPERTY = "basic_last_name_attribute_key"; + public static final String OAUTH2_TENANT_NAME_STRATEGY_PROPERTY = "basic_tenant_name_strategy"; + public static final String OAUTH2_TENANT_NAME_PATTERN_PROPERTY = "basic_tenant_name_pattern"; + public static final String OAUTH2_CUSTOMER_NAME_PATTERN_PROPERTY = "basic_customer_name_pattern"; + public static final String OAUTH2_DEFAULT_DASHBOARD_NAME_PROPERTY = "basic_default_dashboard_name"; + public static final String OAUTH2_ALWAYS_FULL_SCREEN_PROPERTY = "basic_always_full_screen"; + public static final String OAUTH2_MAPPER_URL_PROPERTY = "custom_url"; + public static final String OAUTH2_MAPPER_USERNAME_PROPERTY = "custom_username"; + public static final String OAUTH2_MAPPER_PASSWORD_PROPERTY = "custom_password"; + /** * Cassandra attributes and timeseries constants. */ diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java new file mode 100644 index 0000000000..bc402a7d7d --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java @@ -0,0 +1,99 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.id.OAuth2IntegrationId; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.util.mapping.JsonStringType; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@TypeDef(name = "json", typeClass = JsonStringType.class) +@Table(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_COLUMN_FAMILY_NAME) +public class OAuth2ClientRegistrationEntity extends BaseSqlEntity { + + @Column(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_ID_PROPERTY) + private String registrationId; + @Column(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_MAPPER_CONFIG_ID_PROPERTY) + private String mapperConfigId; + @Column(name = ModelConstants.OAUTH2_CLIENT_ID_PROPERTY) + private String clientId; + @Column(name = ModelConstants.OAUTH2_CLIENT_SECRET_PROPERTY) + private String clientSecret; + @Column(name = ModelConstants.OAUTH2_AUTHORIZATION_URI_PROPERTY) + private String authorizationUri; + @Column(name = ModelConstants.OAUTH2_TOKEN_URI_PROPERTY) + private String tokenUri; + @Column(name = ModelConstants.OAUTH2_REDIRECT_URI_TEMPLATE_PROPERTY) + private String redirectUriTemplate; + @Column(name = ModelConstants.OAUTH2_SCOPE_PROPERTY) + private String scope; + @Column(name = ModelConstants.OAUTH2_AUTHORIZATION_GRANT_TYPE_PROPERTY) + private String authorizationGrantType; + @Column(name = ModelConstants.OAUTH2_USER_INFO_URI_PROPERTY) + private String userInfoUri; + @Column(name = ModelConstants.OAUTH2_USER_NAME_ATTRIBUTE_PROPERTY) + private String userNameAttribute; + @Column(name = ModelConstants.OAUTH2_JWK_SET_URI_PROPERTY) + private String jwkSetUri; + @Column(name = ModelConstants.OAUTH2_CLIENT_AUTHENTICATION_METHOD_PROPERTY) + private String clientAuthenticationMethod; + @Column(name = ModelConstants.OAUTH2_CLIENT_NAME_PROPERTY) + private String clientName; + @Column(name = ModelConstants.OAUTH2_LOGIN_BUTTON_LABEL_PROPERTY) + private String loginButtonLabel; + @Column(name = ModelConstants.OAUTH2_LOGIN_BUTTON_ICON_PROPERTY) + private String loginButtonIcon; + + public OAuth2ClientRegistrationEntity() { + super(); + } + + @Override + public OAuth2ClientRegistration toData() { + return OAuth2ClientRegistration.builder() + .id(new OAuth2IntegrationId(toUUID(id))) + .registrationId(registrationId) + .mapperConfigId(new OAuth2IntegrationId(toUUID(mapperConfigId))) + .clientId(clientId) + .clientSecret(clientSecret) + .authorizationUri(authorizationUri) + .tokenUri(tokenUri) + .redirectUriTemplate(redirectUriTemplate) + .scope(scope) + .authorizationGrantType(authorizationGrantType) + .userInfoUri(userInfoUri) + .userNameAttribute(userNameAttribute) + .jwkSetUri(jwkSetUri) + .clientAuthenticationMethod(clientAuthenticationMethod) + .clientName(clientName) + .loginButtonLabel(loginButtonLabel) + .loginButtonIcon(loginButtonIcon) + .build(); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2MapperConfigEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2MapperConfigEntity.java new file mode 100644 index 0000000000..de75c052e4 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2MapperConfigEntity.java @@ -0,0 +1,101 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.id.OAuth2IntegrationId; +import org.thingsboard.server.common.data.oauth2.MapperType; +import org.thingsboard.server.common.data.oauth2.OAuth2BasicMapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.util.mapping.JsonStringType; + +import javax.persistence.*; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@TypeDef(name = "json", typeClass = JsonStringType.class) +@Table(name = ModelConstants.OAUTH2_MAPPER_CONFIG_COLUMN_FAMILY_NAME) +public class OAuth2MapperConfigEntity extends BaseSqlEntity { + + @Column(name = ModelConstants.OAUTH2_ALLOW_USER_CREATION_PROPERTY) + private Boolean allowUserCreation; + @Column(name = ModelConstants.OAUTH2_ACTIVATE_USER_PROPERTY) + private Boolean activateUser; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.OAUTH2_MAPPER_TYPE_PROPERTY) + private MapperType type; + @Column(name = ModelConstants.OAUTH2_EMAIL_ATTRIBUTE_KEY_PROPERTY) + private String emailAttributeKey; + @Column(name = ModelConstants.OAUTH2_FIRST_NAME_ATTRIBUTE_KEY_PROPERTY) + private String firstNameAttributeKey; + @Column(name = ModelConstants.OAUTH2_LAST_NAME_ATTRIBUTE_KEY_PROPERTY) + private String lastNameAttributeKey; + @Column(name = ModelConstants.OAUTH2_TENANT_NAME_STRATEGY_PROPERTY) + private String tenantNameStrategy; + @Column(name = ModelConstants.OAUTH2_TENANT_NAME_PATTERN_PROPERTY) + private String tenantNamePattern; + @Column(name = ModelConstants.OAUTH2_CUSTOMER_NAME_PATTERN_PROPERTY) + private String customerNamePattern; + @Column(name = ModelConstants.OAUTH2_DEFAULT_DASHBOARD_NAME_PROPERTY) + private String defaultDashboardName; + @Column(name = ModelConstants.OAUTH2_ALWAYS_FULL_SCREEN_PROPERTY) + private Boolean alwaysFullScreen; + @Column(name = ModelConstants.OAUTH2_MAPPER_URL_PROPERTY) + private String url; + @Column(name = ModelConstants.OAUTH2_MAPPER_USERNAME_PROPERTY) + private String username; + @Column(name = ModelConstants.OAUTH2_MAPPER_PASSWORD_PROPERTY) + private String password; + + public OAuth2MapperConfigEntity() { + super(); + } + + @Override + public OAuth2MapperConfig toData() { + return OAuth2MapperConfig.builder() + .id(new OAuth2IntegrationId(toUUID(id))) + .allowUserCreation(allowUserCreation) + .activateUser(activateUser) + .type(type) + .basicConfig( + OAuth2BasicMapperConfig.builder() + .emailAttributeKey(emailAttributeKey) + .firstNameAttributeKey(firstNameAttributeKey) + .lastNameAttributeKey(lastNameAttributeKey) + .tenantNameStrategy(tenantNameStrategy) + .tenantNamePattern(tenantNamePattern) + .customerNamePattern(customerNamePattern) + .defaultDashboardName(defaultDashboardName) + .alwaysFullScreen(alwaysFullScreen) + .build() + ) + .customConfig( + OAuth2CustomMapperConfig.builder() + .url(url) + .username(username) + .password(password) + .build() + ) + .build(); + } +} diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index 697e34930a..d9dba652b1 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -253,3 +253,39 @@ CREATE TABLE IF NOT EXISTS entity_view ( additional_info varchar ); +CREATE TABLE IF NOT EXISTS oauth2_client_registration ( + registration_id varchar(255) NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + mapper_config_id varchar(31), + client_id varchar(255), + client_secret varchar(255), + authorization_uri varchar(255), + token_uri varchar(255), + redirect_uri_template varchar(255), + scope varchar(255), + authorization_grant_type varchar(255), + user_info_uri varchar(255), + user_name_attribute varchar(255), + jwk_set_uri varchar(255), + client_authentication_method varchar(255), + client_name varchar(255), + login_button_label varchar(255), + login_button_icon varchar(255) +); + +CREATE TABLE IF NOT EXISTS oauth2_mapper_config ( + id varchar(31) NOT NULL CONSTRAINT oauth2_mapper_config_pkey PRIMARY KEY, + allow_user_creation boolean, + activate_user boolean, + type varchar(31), + basic_email_attribute_key varchar(31), + basic_first_name_attribute_key varchar(31), + basic_last_name_attribute_key varchar(31), + basic_tenant_name_strategy varchar(31), + basic_tenant_name_pattern varchar(255), + basic_customer_name_pattern varchar(255), + basic_default_dashboard_name varchar(255), + basic_always_full_screen boolean, + custom_url varchar(255), + custom_username varchar(255), + custom_password varchar(255) +); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 931856e1df..820a7775c5 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -253,6 +253,44 @@ CREATE TABLE IF NOT EXISTS entity_view ( additional_info varchar ); +CREATE TABLE IF NOT EXISTS oauth2_client_registration ( + id varchar(31) NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + registration_id varchar(255) UNIQUE, + mapper_config_id varchar(31), + client_id varchar(255), + client_secret varchar(255), + authorization_uri varchar(255), + token_uri varchar(255), + redirect_uri_template varchar(255), + scope varchar(255), + authorization_grant_type varchar(255), + user_info_uri varchar(255), + user_name_attribute varchar(255), + jwk_set_uri varchar(255), + client_authentication_method varchar(255), + client_name varchar(255), + login_button_label varchar(255), + login_button_icon varchar(255) +); + +CREATE TABLE IF NOT EXISTS oauth2_mapper_config ( + id varchar(31) NOT NULL CONSTRAINT oauth2_mapper_config_pkey PRIMARY KEY, + allow_user_creation boolean, + activate_user boolean, + type varchar(31), + basic_email_attribute_key varchar(31), + basic_first_name_attribute_key varchar(31), + basic_last_name_attribute_key varchar(31), + basic_tenant_name_strategy varchar(31), + basic_tenant_name_pattern varchar(255), + basic_customer_name_pattern varchar(255), + basic_default_dashboard_name varchar(255), + basic_always_full_screen boolean, + custom_url varchar(255), + custom_username varchar(255), + custom_password varchar(255) +); + CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint) LANGUAGE plpgsql AS $$ From da8ad473892f3618ca51b3ffaaf8384dc554d4ab Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 18 Jun 2020 18:54:44 +0300 Subject: [PATCH 002/463] Fixed schema-entities-hsql.sql and update script --- application/src/main/data/upgrade/3.1.0/schema_update.sql | 3 ++- dao/src/main/resources/sql/schema-entities-hsql.sql | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/application/src/main/data/upgrade/3.1.0/schema_update.sql b/application/src/main/data/upgrade/3.1.0/schema_update.sql index 3ee67b03a4..7ef91b2bec 100644 --- a/application/src/main/data/upgrade/3.1.0/schema_update.sql +++ b/application/src/main/data/upgrade/3.1.0/schema_update.sql @@ -17,7 +17,8 @@ DROP TABLE IF EXISTS oauth2_client_registration; CREATE TABLE IF NOT EXISTS oauth2_client_registration ( - registration_id varchar(255) NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + id varchar(31) NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + registration_id varchar(255) UNIQUE, mapper_config_id varchar(31), client_id varchar(255), client_secret varchar(255), diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index d9dba652b1..ca14f0b0e8 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -254,7 +254,8 @@ CREATE TABLE IF NOT EXISTS entity_view ( ); CREATE TABLE IF NOT EXISTS oauth2_client_registration ( - registration_id varchar(255) NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + id varchar(31) NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + registration_id varchar(255) UNIQUE, mapper_config_id varchar(31), client_id varchar(255), client_secret varchar(255), From b63f34316d5a92312fe85292115621c556d1e6cb Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 18 Jun 2020 18:58:30 +0300 Subject: [PATCH 003/463] Changed @Getter to @Data for Custom and Basic mappers --- .../server/common/data/oauth2/OAuth2BasicMapperConfig.java | 7 ++----- .../common/data/oauth2/OAuth2CustomMapperConfig.java | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java index e91daf75e7..a38390ef81 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java @@ -1,13 +1,10 @@ package org.thingsboard.server.common.data.oauth2; -import lombok.Builder; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.ToString; +import lombok.*; @Builder(toBuilder = true) @EqualsAndHashCode -@Getter +@Data @ToString public class OAuth2BasicMapperConfig { private final String emailAttributeKey; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java index ea9c963721..ad401e0aff 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java @@ -1,13 +1,10 @@ package org.thingsboard.server.common.data.oauth2; -import lombok.Builder; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.ToString; +import lombok.*; @Builder(toBuilder = true) @EqualsAndHashCode -@Getter +@Data @ToString(exclude = {"password"}) public class OAuth2CustomMapperConfig { private final String url; From d7ff230bbda700db3055e8ba92092d2bbeb6d6db Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Fri, 19 Jun 2020 10:41:34 +0300 Subject: [PATCH 004/463] Merged OAuth2 mapper_config with client_registration --- .../main/data/upgrade/3.1.0/schema_update.sql | 39 +++---- .../data/oauth2/OAuth2ClientRegistration.java | 6 +- .../data/oauth2/OAuth2MapperConfig.java | 30 ++---- .../server/dao/model/ModelConstants.java | 6 -- .../sql/OAuth2ClientRegistrationEntity.java | 62 +++++++++-- .../model/sql/OAuth2MapperConfigEntity.java | 101 ------------------ .../resources/sql/schema-entities-hsql.sql | 37 +++---- .../main/resources/sql/schema-entities.sql | 37 +++---- 8 files changed, 113 insertions(+), 205 deletions(-) delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2MapperConfigEntity.java diff --git a/application/src/main/data/upgrade/3.1.0/schema_update.sql b/application/src/main/data/upgrade/3.1.0/schema_update.sql index 7ef91b2bec..61ba70894c 100644 --- a/application/src/main/data/upgrade/3.1.0/schema_update.sql +++ b/application/src/main/data/upgrade/3.1.0/schema_update.sql @@ -17,29 +17,22 @@ DROP TABLE IF EXISTS oauth2_client_registration; CREATE TABLE IF NOT EXISTS oauth2_client_registration ( - id varchar(31) NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, - registration_id varchar(255) UNIQUE, - mapper_config_id varchar(31), - client_id varchar(255), - client_secret varchar(255), - authorization_uri varchar(255), - token_uri varchar(255), - redirect_uri_template varchar(255), - scope varchar(255), - authorization_grant_type varchar(255), - user_info_uri varchar(255), - user_name_attribute varchar(255), - jwk_set_uri varchar(255), - client_authentication_method varchar(255), - client_name varchar(255), - login_button_label varchar(255), - login_button_icon varchar(255) -); - -DROP TABLE IF EXISTS oauth2_mapper_config; - -CREATE TABLE IF NOT EXISTS oauth2_mapper_config ( - id varchar(31) NOT NULL CONSTRAINT oauth2_mapper_config_pkey PRIMARY KEY, + id varchar(31) NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + registration_id varchar(255) UNIQUE, + client_id varchar(255), + client_secret varchar(255), + authorization_uri varchar(255), + token_uri varchar(255), + redirect_uri_template varchar(255), + scope varchar(255), + authorization_grant_type varchar(255), + user_info_uri varchar(255), + user_name_attribute varchar(255), + jwk_set_uri varchar(255), + client_authentication_method varchar(255), + client_name varchar(255), + login_button_label varchar(255), + login_button_icon varchar(255), allow_user_creation boolean, activate_user boolean, type varchar(31), diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java index d5bd3b076e..fc855e24f1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java @@ -10,7 +10,7 @@ import org.thingsboard.server.common.data.id.OAuth2IntegrationId; public class OAuth2ClientRegistration extends BaseData { private String registrationId; - private OAuth2IntegrationId mapperConfigId; + private OAuth2MapperConfig mapperConfig; private String clientId; private String clientSecret; private String authorizationUri; @@ -35,7 +35,7 @@ public class OAuth2ClientRegistration extends BaseData { } @Builder(toBuilder = true) - public OAuth2ClientRegistration(OAuth2IntegrationId id, String registrationId, String clientId, String clientSecret, String authorizationUri, String tokenUri, String redirectUriTemplate, String scope, String authorizationGrantType, String userInfoUri, String userNameAttribute, String jwkSetUri, String clientAuthenticationMethod, String clientName, String loginButtonLabel, String loginButtonIcon, OAuth2IntegrationId mapperConfigId) { + public OAuth2ClientRegistration(OAuth2IntegrationId id, String registrationId, String clientId, String clientSecret, String authorizationUri, String tokenUri, String redirectUriTemplate, String scope, String authorizationGrantType, String userInfoUri, String userNameAttribute, String jwkSetUri, String clientAuthenticationMethod, String clientName, String loginButtonLabel, String loginButtonIcon, OAuth2MapperConfig mapperConfig) { super(id); this.registrationId = registrationId; this.clientId = clientId; @@ -52,6 +52,6 @@ public class OAuth2ClientRegistration extends BaseData { this.clientName = clientName; this.loginButtonLabel = loginButtonLabel; this.loginButtonIcon = loginButtonIcon; - this.mapperConfigId = mapperConfigId; + this.mapperConfig = mapperConfig; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java index 95c1fdf4a8..1e45edc4de 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java @@ -1,34 +1,18 @@ package org.thingsboard.server.common.data.oauth2; -import lombok.*; -import org.thingsboard.server.common.data.BaseData; -import org.thingsboard.server.common.data.id.OAuth2IntegrationId; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; -@EqualsAndHashCode(callSuper = true) +@Builder(toBuilder = true) +@EqualsAndHashCode @Data @ToString -public class OAuth2MapperConfig extends BaseData { +public class OAuth2MapperConfig { private boolean allowUserCreation; private boolean activateUser; private MapperType type; private OAuth2BasicMapperConfig basicConfig; private OAuth2CustomMapperConfig customConfig; - - public OAuth2MapperConfig() { - super(); - } - - public OAuth2MapperConfig(OAuth2IntegrationId id) { - super(id); - } - - @Builder(toBuilder = true) - public OAuth2MapperConfig(OAuth2IntegrationId id, boolean allowUserCreation, boolean activateUser, MapperType type, OAuth2BasicMapperConfig basicConfig, OAuth2CustomMapperConfig customConfig) { - super(id); - this.allowUserCreation = allowUserCreation; - this.activateUser = activateUser; - this.type = type; - this.basicConfig = basicConfig; - this.customConfig = customConfig; - } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index c551114aa5..2f967b648b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -359,7 +359,6 @@ public class ModelConstants { */ public static final String OAUTH2_CLIENT_REGISTRATION_COLUMN_FAMILY_NAME = "oauth2_client_registration"; public static final String OAUTH2_CLIENT_REGISTRATION_ID_PROPERTY = "registration_id"; - public static final String OAUTH2_CLIENT_REGISTRATION_MAPPER_CONFIG_ID_PROPERTY = "mapper_config_id"; public static final String OAUTH2_CLIENT_ID_PROPERTY = "client_id"; public static final String OAUTH2_CLIENT_SECRET_PROPERTY = "client_secret"; public static final String OAUTH2_AUTHORIZATION_URI_PROPERTY = "authorization_uri"; @@ -374,11 +373,6 @@ public class ModelConstants { public static final String OAUTH2_CLIENT_NAME_PROPERTY = "client_name"; public static final String OAUTH2_LOGIN_BUTTON_LABEL_PROPERTY = "login_button_label"; public static final String OAUTH2_LOGIN_BUTTON_ICON_PROPERTY = "login_button_icon"; - - /** - * Cassandra OAuth2 mapper config constants. - */ - public static final String OAUTH2_MAPPER_CONFIG_COLUMN_FAMILY_NAME = "oauth2_mapper_config"; public static final String OAUTH2_ALLOW_USER_CREATION_PROPERTY = "allow_user_creation"; public static final String OAUTH2_ACTIVATE_USER_PROPERTY = "activate_user"; public static final String OAUTH2_MAPPER_TYPE_PROPERTY = "type"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java index bc402a7d7d..7189b315c2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java @@ -21,14 +21,12 @@ import lombok.NoArgsConstructor; import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.id.OAuth2IntegrationId; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; +import org.thingsboard.server.common.data.oauth2.*; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonStringType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; +import javax.persistence.*; @Data @EqualsAndHashCode(callSuper = true) @@ -39,8 +37,6 @@ public class OAuth2ClientRegistrationEntity extends BaseSqlEntity - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.model.sql; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.hibernate.annotations.TypeDef; -import org.thingsboard.server.common.data.id.OAuth2IntegrationId; -import org.thingsboard.server.common.data.oauth2.MapperType; -import org.thingsboard.server.common.data.oauth2.OAuth2BasicMapperConfig; -import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; -import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; -import org.thingsboard.server.dao.model.BaseSqlEntity; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.util.mapping.JsonStringType; - -import javax.persistence.*; - -@Data -@EqualsAndHashCode(callSuper = true) -@Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) -@Table(name = ModelConstants.OAUTH2_MAPPER_CONFIG_COLUMN_FAMILY_NAME) -public class OAuth2MapperConfigEntity extends BaseSqlEntity { - - @Column(name = ModelConstants.OAUTH2_ALLOW_USER_CREATION_PROPERTY) - private Boolean allowUserCreation; - @Column(name = ModelConstants.OAUTH2_ACTIVATE_USER_PROPERTY) - private Boolean activateUser; - @Enumerated(EnumType.STRING) - @Column(name = ModelConstants.OAUTH2_MAPPER_TYPE_PROPERTY) - private MapperType type; - @Column(name = ModelConstants.OAUTH2_EMAIL_ATTRIBUTE_KEY_PROPERTY) - private String emailAttributeKey; - @Column(name = ModelConstants.OAUTH2_FIRST_NAME_ATTRIBUTE_KEY_PROPERTY) - private String firstNameAttributeKey; - @Column(name = ModelConstants.OAUTH2_LAST_NAME_ATTRIBUTE_KEY_PROPERTY) - private String lastNameAttributeKey; - @Column(name = ModelConstants.OAUTH2_TENANT_NAME_STRATEGY_PROPERTY) - private String tenantNameStrategy; - @Column(name = ModelConstants.OAUTH2_TENANT_NAME_PATTERN_PROPERTY) - private String tenantNamePattern; - @Column(name = ModelConstants.OAUTH2_CUSTOMER_NAME_PATTERN_PROPERTY) - private String customerNamePattern; - @Column(name = ModelConstants.OAUTH2_DEFAULT_DASHBOARD_NAME_PROPERTY) - private String defaultDashboardName; - @Column(name = ModelConstants.OAUTH2_ALWAYS_FULL_SCREEN_PROPERTY) - private Boolean alwaysFullScreen; - @Column(name = ModelConstants.OAUTH2_MAPPER_URL_PROPERTY) - private String url; - @Column(name = ModelConstants.OAUTH2_MAPPER_USERNAME_PROPERTY) - private String username; - @Column(name = ModelConstants.OAUTH2_MAPPER_PASSWORD_PROPERTY) - private String password; - - public OAuth2MapperConfigEntity() { - super(); - } - - @Override - public OAuth2MapperConfig toData() { - return OAuth2MapperConfig.builder() - .id(new OAuth2IntegrationId(toUUID(id))) - .allowUserCreation(allowUserCreation) - .activateUser(activateUser) - .type(type) - .basicConfig( - OAuth2BasicMapperConfig.builder() - .emailAttributeKey(emailAttributeKey) - .firstNameAttributeKey(firstNameAttributeKey) - .lastNameAttributeKey(lastNameAttributeKey) - .tenantNameStrategy(tenantNameStrategy) - .tenantNamePattern(tenantNamePattern) - .customerNamePattern(customerNamePattern) - .defaultDashboardName(defaultDashboardName) - .alwaysFullScreen(alwaysFullScreen) - .build() - ) - .customConfig( - OAuth2CustomMapperConfig.builder() - .url(url) - .username(username) - .password(password) - .build() - ) - .build(); - } -} diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index ca14f0b0e8..b76665df3f 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -254,27 +254,22 @@ CREATE TABLE IF NOT EXISTS entity_view ( ); CREATE TABLE IF NOT EXISTS oauth2_client_registration ( - id varchar(31) NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, - registration_id varchar(255) UNIQUE, - mapper_config_id varchar(31), - client_id varchar(255), - client_secret varchar(255), - authorization_uri varchar(255), - token_uri varchar(255), - redirect_uri_template varchar(255), - scope varchar(255), - authorization_grant_type varchar(255), - user_info_uri varchar(255), - user_name_attribute varchar(255), - jwk_set_uri varchar(255), - client_authentication_method varchar(255), - client_name varchar(255), - login_button_label varchar(255), - login_button_icon varchar(255) -); - -CREATE TABLE IF NOT EXISTS oauth2_mapper_config ( - id varchar(31) NOT NULL CONSTRAINT oauth2_mapper_config_pkey PRIMARY KEY, + id varchar(31) NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + registration_id varchar(255) UNIQUE, + client_id varchar(255), + client_secret varchar(255), + authorization_uri varchar(255), + token_uri varchar(255), + redirect_uri_template varchar(255), + scope varchar(255), + authorization_grant_type varchar(255), + user_info_uri varchar(255), + user_name_attribute varchar(255), + jwk_set_uri varchar(255), + client_authentication_method varchar(255), + client_name varchar(255), + login_button_label varchar(255), + login_button_icon varchar(255), allow_user_creation boolean, activate_user boolean, type varchar(31), diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 820a7775c5..a27907fc45 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -254,27 +254,22 @@ CREATE TABLE IF NOT EXISTS entity_view ( ); CREATE TABLE IF NOT EXISTS oauth2_client_registration ( - id varchar(31) NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, - registration_id varchar(255) UNIQUE, - mapper_config_id varchar(31), - client_id varchar(255), - client_secret varchar(255), - authorization_uri varchar(255), - token_uri varchar(255), - redirect_uri_template varchar(255), - scope varchar(255), - authorization_grant_type varchar(255), - user_info_uri varchar(255), - user_name_attribute varchar(255), - jwk_set_uri varchar(255), - client_authentication_method varchar(255), - client_name varchar(255), - login_button_label varchar(255), - login_button_icon varchar(255) -); - -CREATE TABLE IF NOT EXISTS oauth2_mapper_config ( - id varchar(31) NOT NULL CONSTRAINT oauth2_mapper_config_pkey PRIMARY KEY, + id varchar(31) NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + registration_id varchar(255) UNIQUE, + client_id varchar(255), + client_secret varchar(255), + authorization_uri varchar(255), + token_uri varchar(255), + redirect_uri_template varchar(255), + scope varchar(255), + authorization_grant_type varchar(255), + user_info_uri varchar(255), + user_name_attribute varchar(255), + jwk_set_uri varchar(255), + client_authentication_method varchar(255), + client_name varchar(255), + login_button_label varchar(255), + login_button_icon varchar(255), allow_user_creation boolean, activate_user boolean, type varchar(31), From e8bf9b45efb8aa08831f35467c3dc70125d457fa Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Fri, 19 Jun 2020 11:42:45 +0300 Subject: [PATCH 005/463] Added repository and dao for OAuth2ClientRegistration --- .../sql/OAuth2ClientRegistrationEntity.java | 44 ++++++++++ .../oauth2/OAuth2ClientRegistrationDao.java | 15 ++++ .../JpaOAuth2ClientRegistrationDao.java | 83 +++++++++++++++++++ .../OAuth2ClientRegistrationRepository.java | 30 +++++++ 4 files changed, 172 insertions(+) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java index 7189b315c2..66e57faaf4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java @@ -99,6 +99,50 @@ public class OAuth2ClientRegistrationEntity extends BaseSqlEntity find(); + + OAuth2ClientRegistration findById(String registrationId); + + OAuth2ClientRegistration save(OAuth2ClientRegistration clientRegistration); + + boolean removeById(String registrationId); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java new file mode 100644 index 0000000000..d2b951a072 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java @@ -0,0 +1,83 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.oauth2; + +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationEntity; +import org.thingsboard.server.dao.oauth2.OAuth2ClientRegistrationDao; +import org.thingsboard.server.dao.sql.JpaAbstractDao; +import org.thingsboard.server.dao.util.SqlDao; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; + +@Slf4j +@Component +@SqlDao +@RequiredArgsConstructor +public class JpaOAuth2ClientRegistrationDao implements OAuth2ClientRegistrationDao { + private final OAuth2ClientRegistrationRepository repository; + + @Override + @Transactional + public OAuth2ClientRegistration save(OAuth2ClientRegistration clientRegistration) { + OAuth2ClientRegistrationEntity entity; + try { + entity = new OAuth2ClientRegistrationEntity(clientRegistration); + } catch (Exception e) { + log.error("Can't create entity for domain object {}", clientRegistration, e); + throw new IllegalArgumentException("Can't create entity for domain object {" + clientRegistration + "}", e); + } + log.debug("Saving entity {}", entity); + entity = repository.save(entity); + return DaoUtil.getData(entity); + } + + @Override + public List find() { + List entities = Lists.newArrayList(repository.findAll()); + return DaoUtil.convertDataList(entities); + } + + @Override + public OAuth2ClientRegistration findById(String registrationId) { + log.debug("Get entity by registration id {}", registrationId); + Optional entity = repository.findByRegistrationId(registrationId); + return DaoUtil.getData(entity); + } + + @Override + public boolean removeById(String registrationId) { + repository.deleteByRegistrationId(registrationId); + log.debug("Remove request: {}", registrationId); + return !repository.existsByRegistrationId(registrationId); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java new file mode 100644 index 0000000000..2e6eb57209 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.oauth2; + +import org.springframework.data.repository.CrudRepository; +import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationEntity; +import org.thingsboard.server.dao.util.SqlDao; + +import java.util.Optional; + +@SqlDao +public interface OAuth2ClientRegistrationRepository extends CrudRepository { + Optional findByRegistrationId(String registrationId); + int deleteByRegistrationId(String registrationId); + boolean existsByRegistrationId(String registrationId); + +} From cdf0e78176e4a50d17108a7a7694821cdd593db1 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Fri, 19 Jun 2020 14:05:58 +0300 Subject: [PATCH 006/463] Added named UNIQUE constraint --- application/src/main/data/upgrade/3.1.0/schema_update.sql | 5 +++-- dao/src/main/resources/sql/schema-entities-hsql.sql | 5 +++-- dao/src/main/resources/sql/schema-entities.sql | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/application/src/main/data/upgrade/3.1.0/schema_update.sql b/application/src/main/data/upgrade/3.1.0/schema_update.sql index 61ba70894c..fc334929b4 100644 --- a/application/src/main/data/upgrade/3.1.0/schema_update.sql +++ b/application/src/main/data/upgrade/3.1.0/schema_update.sql @@ -18,7 +18,7 @@ DROP TABLE IF EXISTS oauth2_client_registration; CREATE TABLE IF NOT EXISTS oauth2_client_registration ( id varchar(31) NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, - registration_id varchar(255) UNIQUE, + registration_id varchar(255), client_id varchar(255), client_secret varchar(255), authorization_uri varchar(255), @@ -46,5 +46,6 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( basic_always_full_screen boolean, custom_url varchar(255), custom_username varchar(255), - custom_password varchar(255) + custom_password varchar(255), + CONSTRAINT oauth2_registration_id_unq_key UNIQUE (registration_id) ); \ No newline at end of file diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index b76665df3f..425d303f2d 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -255,7 +255,7 @@ CREATE TABLE IF NOT EXISTS entity_view ( CREATE TABLE IF NOT EXISTS oauth2_client_registration ( id varchar(31) NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, - registration_id varchar(255) UNIQUE, + registration_id varchar(255), client_id varchar(255), client_secret varchar(255), authorization_uri varchar(255), @@ -283,5 +283,6 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( basic_always_full_screen boolean, custom_url varchar(255), custom_username varchar(255), - custom_password varchar(255) + custom_password varchar(255), + CONSTRAINT oauth2_registration_id_unq_key UNIQUE (registration_id) ); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index a27907fc45..42aabf72db 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -255,7 +255,7 @@ CREATE TABLE IF NOT EXISTS entity_view ( CREATE TABLE IF NOT EXISTS oauth2_client_registration ( id varchar(31) NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, - registration_id varchar(255) UNIQUE, + registration_id varchar(255), client_id varchar(255), client_secret varchar(255), authorization_uri varchar(255), @@ -283,7 +283,8 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( basic_always_full_screen boolean, custom_url varchar(255), custom_username varchar(255), - custom_password varchar(255) + custom_password varchar(255), + CONSTRAINT oauth2_registration_id_unq_key UNIQUE (registration_id) ); CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint) From b08d9cc935023d977ed69a8a763c843ec188bb7d Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Fri, 19 Jun 2020 15:56:35 +0300 Subject: [PATCH 007/463] Made TenantNameStrategyType as Enum --- .../server/common/data/oauth2/OAuth2BasicMapperConfig.java | 2 +- .../server/common/data/oauth2/TenantNameStrategyType.java | 5 +++++ .../server/dao/model/sql/OAuth2ClientRegistrationEntity.java | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/TenantNameStrategyType.java diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java index a38390ef81..18bab333fb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java @@ -10,7 +10,7 @@ public class OAuth2BasicMapperConfig { private final String emailAttributeKey; private final String firstNameAttributeKey; private final String lastNameAttributeKey; - private final String tenantNameStrategy; + private final TenantNameStrategyType tenantNameStrategy; private final String tenantNamePattern; private final String customerNamePattern; private final String defaultDashboardName; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/TenantNameStrategyType.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/TenantNameStrategyType.java new file mode 100644 index 0000000000..e38c7e37dc --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/TenantNameStrategyType.java @@ -0,0 +1,5 @@ +package org.thingsboard.server.common.data.oauth2; + +public enum TenantNameStrategyType { + DOMAIN, EMAIL, CUSTOM; +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java index 66e57faaf4..70e9c034dc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java @@ -78,8 +78,9 @@ public class OAuth2ClientRegistrationEntity extends BaseSqlEntity Date: Fri, 19 Jun 2020 16:03:36 +0300 Subject: [PATCH 008/463] Refactored OAuth2Dao --- .../dao/oauth2/OAuth2ClientRegistrationDao.java | 4 ++-- .../sql/oauth2/JpaOAuth2ClientRegistrationDao.java | 14 ++------------ 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java index 25c68d58e4..dbdcd8f68b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java @@ -7,9 +7,9 @@ import java.util.List; public interface OAuth2ClientRegistrationDao { List find(); - OAuth2ClientRegistration findById(String registrationId); + OAuth2ClientRegistration findByRegistrationId(String registrationId); OAuth2ClientRegistration save(OAuth2ClientRegistration clientRegistration); - boolean removeById(String registrationId); + boolean removeByRegistrationId(String registrationId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java index d2b951a072..98e1b8431a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java @@ -16,29 +16,19 @@ package org.thingsboard.server.dao.sql.oauth2; import com.google.common.collect.Lists; -import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationEntity; import org.thingsboard.server.dao.oauth2.OAuth2ClientRegistrationDao; -import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.util.SqlDao; import java.util.List; -import java.util.Objects; import java.util.Optional; -import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; - @Slf4j @Component @SqlDao @@ -68,14 +58,14 @@ public class JpaOAuth2ClientRegistrationDao implements OAuth2ClientRegistrationD } @Override - public OAuth2ClientRegistration findById(String registrationId) { + public OAuth2ClientRegistration findByRegistrationId(String registrationId) { log.debug("Get entity by registration id {}", registrationId); Optional entity = repository.findByRegistrationId(registrationId); return DaoUtil.getData(entity); } @Override - public boolean removeById(String registrationId) { + public boolean removeByRegistrationId(String registrationId) { repository.deleteByRegistrationId(registrationId); log.debug("Remove request: {}", registrationId); return !repository.existsByRegistrationId(registrationId); From d5abe337be44763efa85129a47da3f0f9edcfc60 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Fri, 19 Jun 2020 16:04:10 +0300 Subject: [PATCH 009/463] Added OAuth2ClientRegistrationService --- .../OAuth2ClientRegistrationService.java | 30 ++++ .../OAuth2ClientRegistrationServiceImpl.java | 151 ++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationServiceImpl.java diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationService.java new file mode 100644 index 0000000000..a38fb78eb9 --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationService.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.oauth2; + +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; + +import java.util.List; + +public interface OAuth2ClientRegistrationService { + OAuth2ClientRegistration saveClientRegistration(OAuth2ClientRegistration clientRegistration); + + List findClientRegistrations(); + + OAuth2ClientRegistration findClientRegistrationsByRegistrationId(String registrationId); + + void deleteClientRegistrationsByRegistrationId(String registrationId); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationServiceImpl.java new file mode 100644 index 0000000000..699f7acd81 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationServiceImpl.java @@ -0,0 +1,151 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.oauth2; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.oauth2.*; +import org.thingsboard.server.dao.exception.DataValidationException; + +import java.util.List; +import java.util.function.Consumer; + +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; +import static org.thingsboard.server.dao.service.Validator.validateId; +import static org.thingsboard.server.dao.service.Validator.validateString; + +@Slf4j +@Service +public class OAuth2ClientRegistrationServiceImpl implements OAuth2ClientRegistrationService { + public static final String INCORRECT_REGISTRATION_ID = "Incorrect registrationId "; + + @Autowired + private OAuth2ClientRegistrationDao clientRegistrationDao; + + @Override + public OAuth2ClientRegistration saveClientRegistration(OAuth2ClientRegistration clientRegistration) { + log.trace("Executing saveClientRegistration [{}]", clientRegistration); + return null; + } + + @Override + public List findClientRegistrations() { + log.trace("Executing findClientRegistrations []"); + return clientRegistrationDao.find(); + } + + @Override + public OAuth2ClientRegistration findClientRegistrationsByRegistrationId(String registrationId) { + log.trace("Executing findClientRegistrationsByRegistrationId [{}]", registrationId); + validateString(registrationId, INCORRECT_REGISTRATION_ID + registrationId); + return clientRegistrationDao.findByRegistrationId(registrationId); + } + + @Override + public void deleteClientRegistrationsByRegistrationId(String registrationId) { + log.trace("Executing deleteClientRegistrationsByRegistrationId [{}]", registrationId); + validateString(registrationId, INCORRECT_REGISTRATION_ID + registrationId); + clientRegistrationDao.removeByRegistrationId(registrationId); + } + + private Consumer validator = clientRegistration -> { + if (StringUtils.isEmpty(clientRegistration.getRegistrationId())) { + throw new DataValidationException("Registration ID should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getClientId())) { + throw new DataValidationException("Client ID should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getClientSecret())) { + throw new DataValidationException("Client secret should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getAuthorizationUri())) { + throw new DataValidationException("Authorization uri should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getTokenUri())) { + throw new DataValidationException("Token uri should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getRedirectUriTemplate())) { + throw new DataValidationException("Redirect uri template should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getScope())) { + throw new DataValidationException("Scope should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getAuthorizationGrantType())) { + throw new DataValidationException("Authorization grant type should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getUserInfoUri())) { + throw new DataValidationException("User info uri should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getUserNameAttribute())) { + throw new DataValidationException("User name attribute should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getJwkSetUri())) { + throw new DataValidationException("Jwk set uri should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getClientAuthenticationMethod())) { + throw new DataValidationException("Client authentication method should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getClientName())) { + throw new DataValidationException("Client name should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getLoginButtonLabel())) { + throw new DataValidationException("Login button label should be specified!"); + } + OAuth2MapperConfig mapperConfig = clientRegistration.getMapperConfig(); + if (mapperConfig == null) { + throw new DataValidationException("Mapper config should be specified!"); + } + if (mapperConfig.getType() == null) { + throw new DataValidationException("Mapper config type should be specified!"); + } + if (mapperConfig.getType() == MapperType.BASIC) { + OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasicConfig(); + if (basicConfig == null) { + throw new DataValidationException("Basic config should be specified!"); + } + if (StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) { + throw new DataValidationException("Email attribute key should be specified!"); + } + if (basicConfig.getTenantNameStrategy() == null) { + throw new DataValidationException("Tenant name strategy should be specified!"); + } + if (basicConfig.getTenantNameStrategy() == TenantNameStrategyType.CUSTOM + && StringUtils.isEmpty(basicConfig.getTenantNamePattern())) { + throw new DataValidationException("Tenant name pattern should be specified!"); + } + } + if (mapperConfig.getType() == MapperType.CUSTOM) { + OAuth2CustomMapperConfig customConfig = mapperConfig.getCustomConfig(); + if (customConfig == null) { + throw new DataValidationException("Custom config should be specified!"); + } + if (StringUtils.isEmpty(customConfig.getUrl())) { + throw new DataValidationException("Custom mapper URL should be specified!"); + } + if (StringUtils.isEmpty(customConfig.getUsername())) { + throw new DataValidationException("Custom mapper username should be specified!"); + } + if (StringUtils.isEmpty(customConfig.getPassword())) { + throw new DataValidationException("Custom mapper password should be specified!"); + } + } + }; +} From a4a05fcf3058dabb14e1e73b0a3b068462fc405b Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Fri, 19 Jun 2020 16:36:22 +0300 Subject: [PATCH 010/463] Refactored 'getClientByRegistrationId' --- .../server/dao/oauth2/OAuth2Configuration.java | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Configuration.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Configuration.java index e54f9a5b3e..b2acc524b1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Configuration.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Configuration.java @@ -68,15 +68,6 @@ public class OAuth2Configuration { } public OAuth2Client getClientByRegistrationId(String registrationId) { - OAuth2Client result = null; - if (clients != null && !clients.isEmpty()) { - for (String key : clients.keySet()) { - if (key.equals(registrationId)) { - result = clients.get(key); - break; - } - } - } - return result; + return clients != null ? clients.get(registrationId) : null; } } From 6ad3de83b7193c03c15522353a6280639d80f46d Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Fri, 19 Jun 2020 17:35:31 +0300 Subject: [PATCH 011/463] Moved getting ClientInfo to OAuth2Service --- .../auth/oauth2/BasicOAuth2ClientMapper.java | 45 ++++++------- .../auth/oauth2/CustomOAuth2ClientMapper.java | 9 +-- .../auth/oauth2/OAuth2ClientMapper.java | 4 +- .../oauth2/OAuth2ClientMapperProvider.java | 11 +-- .../Oauth2AuthenticationSuccessHandler.java | 16 ++--- .../server/dao/oauth2/OAuth2Service.java | 2 + .../dao/oauth2/OAuth2Configuration.java | 4 -- .../server/dao/oauth2/OAuth2ServiceImpl.java | 67 ++++++++++++++++++- 8 files changed, 110 insertions(+), 48 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java index 2170479f92..7d671767bb 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java @@ -20,7 +20,7 @@ import org.apache.commons.lang3.text.StrSubstitutor; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; -import org.thingsboard.server.dao.oauth2.OAuth2ClientMapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.service.security.model.SecurityUser; @@ -32,50 +32,47 @@ public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implemen private static final String START_PLACEHOLDER_PREFIX = "%{"; private static final String END_PLACEHOLDER_PREFIX = "}"; - private static final String EMAIL_TENANT_STRATEGY = "email"; - private static final String DOMAIN_TENANT_STRATEGY = "domain"; - private static final String CUSTOM_TENANT_STRATEGY = "custom"; @Override - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig config) { + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, OAuth2MapperConfig config) { OAuth2User oauth2User = new OAuth2User(); Map attributes = token.getPrincipal().getAttributes(); - String email = getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); + String email = getStringAttributeByKey(attributes, config.getBasicConfig().getEmailAttributeKey()); oauth2User.setEmail(email); oauth2User.setTenantName(getTenantName(attributes, config)); - if (!StringUtils.isEmpty(config.getBasic().getLastNameAttributeKey())) { - String lastName = getStringAttributeByKey(attributes, config.getBasic().getLastNameAttributeKey()); + if (!StringUtils.isEmpty(config.getBasicConfig().getLastNameAttributeKey())) { + String lastName = getStringAttributeByKey(attributes, config.getBasicConfig().getLastNameAttributeKey()); oauth2User.setLastName(lastName); } - if (!StringUtils.isEmpty(config.getBasic().getFirstNameAttributeKey())) { - String firstName = getStringAttributeByKey(attributes, config.getBasic().getFirstNameAttributeKey()); + if (!StringUtils.isEmpty(config.getBasicConfig().getFirstNameAttributeKey())) { + String firstName = getStringAttributeByKey(attributes, config.getBasicConfig().getFirstNameAttributeKey()); oauth2User.setFirstName(firstName); } - if (!StringUtils.isEmpty(config.getBasic().getCustomerNamePattern())) { + if (!StringUtils.isEmpty(config.getBasicConfig().getCustomerNamePattern())) { StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX); - String customerName = sub.replace(config.getBasic().getCustomerNamePattern()); + String customerName = sub.replace(config.getBasicConfig().getCustomerNamePattern()); oauth2User.setCustomerName(customerName); } - oauth2User.setAlwaysFullScreen(config.getBasic().isAlwaysFullScreen()); - if (!StringUtils.isEmpty(config.getBasic().getDefaultDashboardName())) { - oauth2User.setDefaultDashboardName(config.getBasic().getDefaultDashboardName()); + oauth2User.setAlwaysFullScreen(config.getBasicConfig().isAlwaysFullScreen()); + if (!StringUtils.isEmpty(config.getBasicConfig().getDefaultDashboardName())) { + oauth2User.setDefaultDashboardName(config.getBasicConfig().getDefaultDashboardName()); } return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser()); } - private String getTenantName(Map attributes, OAuth2ClientMapperConfig config) { - switch (config.getBasic().getTenantNameStrategy()) { - case EMAIL_TENANT_STRATEGY: - return getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); - case DOMAIN_TENANT_STRATEGY: - String email = getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); + private String getTenantName(Map attributes, OAuth2MapperConfig config) { + switch (config.getBasicConfig().getTenantNameStrategy()) { + case EMAIL: + return getStringAttributeByKey(attributes, config.getBasicConfig().getEmailAttributeKey()); + case DOMAIN: + String email = getStringAttributeByKey(attributes, config.getBasicConfig().getEmailAttributeKey()); return email.substring(email .indexOf("@") + 1); - case CUSTOM_TENANT_STRATEGY: + case CUSTOM: StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX); - return sub.replace(config.getBasic().getTenantNamePattern()); + return sub.replace(config.getBasicConfig().getTenantNamePattern()); default: - throw new RuntimeException("Tenant Name Strategy with type " + config.getBasic().getTenantNameStrategy() + " is not supported!"); + throw new RuntimeException("Tenant Name Strategy with type " + config.getBasicConfig().getTenantNameStrategy() + " is not supported!"); } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java index 42ba95b4ee..9d4ac4d2e2 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java @@ -23,7 +23,8 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; -import org.thingsboard.server.dao.oauth2.OAuth2ClientMapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.service.security.model.SecurityUser; @@ -36,12 +37,12 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); @Override - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig config) { - OAuth2User oauth2User = getOAuth2User(token, config.getCustom()); + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, OAuth2MapperConfig config) { + OAuth2User oauth2User = getOAuth2User(token, config.getCustomConfig()); return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser()); } - private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig.CustomOAuth2ClientMapperConfig custom) { + private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, OAuth2CustomMapperConfig custom) { if (!StringUtils.isEmpty(custom.getUsername()) && !StringUtils.isEmpty(custom.getPassword())) { restTemplateBuilder = restTemplateBuilder.basicAuthentication(custom.getUsername(), custom.getPassword()); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java index 196bfe7b50..444332d74b 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java @@ -16,9 +16,9 @@ package org.thingsboard.server.service.security.auth.oauth2; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.thingsboard.server.dao.oauth2.OAuth2ClientMapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.service.security.model.SecurityUser; public interface OAuth2ClientMapper { - SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig config); + SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, OAuth2MapperConfig config); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java index e1c5b694bb..64bdf40970 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.oauth2.MapperType; @Component @Slf4j @@ -32,14 +33,14 @@ public class OAuth2ClientMapperProvider { @Qualifier("customOAuth2ClientMapper") private OAuth2ClientMapper customOAuth2ClientMapper; - public OAuth2ClientMapper getOAuth2ClientMapperByType(String oauth2ClientType) { - switch (oauth2ClientType) { - case "custom": + public OAuth2ClientMapper getOAuth2ClientMapperByType(MapperType oauth2MapperType) { + switch (oauth2MapperType) { + case CUSTOM: return customOAuth2ClientMapper; - case "basic": + case BASIC: return basicOAuth2ClientMapper; default: - throw new RuntimeException("OAuth2ClientMapper with type " + oauth2ClientType + " is not supported!"); + throw new RuntimeException("OAuth2ClientRegistrationMapper with type " + oauth2MapperType + " is not supported!"); } } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index 3375d046e2..08ea4dedca 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -21,8 +21,8 @@ import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; -import org.thingsboard.server.dao.oauth2.OAuth2Client; -import org.thingsboard.server.dao.oauth2.OAuth2Configuration; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; +import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.token.JwtToken; @@ -42,17 +42,17 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS private final JwtTokenFactory tokenFactory; private final RefreshTokenRepository refreshTokenRepository; private final OAuth2ClientMapperProvider oauth2ClientMapperProvider; - private final OAuth2Configuration oauth2Configuration; + private final OAuth2Service oAuth2Service; @Autowired public Oauth2AuthenticationSuccessHandler(final JwtTokenFactory tokenFactory, final RefreshTokenRepository refreshTokenRepository, final OAuth2ClientMapperProvider oauth2ClientMapperProvider, - final OAuth2Configuration oauth2Configuration) { + final OAuth2Service oAuth2Service) { this.tokenFactory = tokenFactory; this.refreshTokenRepository = refreshTokenRepository; this.oauth2ClientMapperProvider = oauth2ClientMapperProvider; - this.oauth2Configuration = oauth2Configuration; + this.oAuth2Service = oAuth2Service; } @Override @@ -64,9 +64,9 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS try { OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; - OAuth2Client oauth2Client = oauth2Configuration.getClientByRegistrationId(token.getAuthorizedClientRegistrationId()); - OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(oauth2Client.getMapperConfig().getType()); - SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oauth2Client.getMapperConfig()); + OAuth2ClientRegistration clientRegistration = oAuth2Service.getClientRegistrationByRegistrationId(token.getAuthorizedClientRegistrationId()); + OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(clientRegistration.getMapperConfig().getType()); + SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, clientRegistration.getMapperConfig()); JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java index d72b6ef98c..efea46a416 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java @@ -16,10 +16,12 @@ package org.thingsboard.server.dao.oauth2; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; import java.util.List; public interface OAuth2Service { + OAuth2ClientRegistration getClientRegistrationByRegistrationId(String registrationId); List getOAuth2Clients(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Configuration.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Configuration.java index b2acc524b1..114a96a61b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Configuration.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Configuration.java @@ -66,8 +66,4 @@ public class OAuth2Configuration { } return new InMemoryClientRegistrationRepository(result); } - - public OAuth2Client getClientByRegistrationId(String registrationId) { - return clients != null ? clients.get(registrationId) : null; - } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 43e11244b0..d0dcbae4d5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -18,8 +18,10 @@ package org.thingsboard.server.dao.oauth2; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; +import org.thingsboard.server.common.data.id.OAuth2IntegrationId; +import org.thingsboard.server.common.data.oauth2.*; +import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -32,6 +34,14 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Autowired(required = false) OAuth2Configuration oauth2Configuration; + @Autowired + private OAuth2ClientRegistrationDao clientRegistrationDao; + + @PostConstruct + public void init() { + + } + @Override public List getOAuth2Clients() { if (oauth2Configuration == null || !oauth2Configuration.isEnabled()) { @@ -47,4 +57,59 @@ public class OAuth2ServiceImpl implements OAuth2Service { } return result; } + + @Override + public OAuth2ClientRegistration getClientRegistrationByRegistrationId(String registrationId) { + if (oauth2Configuration == null || oauth2Configuration.getClients() == null) return null; + OAuth2Client oAuth2Client = oauth2Configuration.getClients().get(registrationId); + OAuth2ClientMapperConfig mapperConfig = oAuth2Client.getMapperConfig(); + OAuth2ClientMapperConfig.BasicOAuth2ClientMapperConfig basicConfig = mapperConfig.getBasic(); + OAuth2ClientMapperConfig.CustomOAuth2ClientMapperConfig customConfig = mapperConfig.getCustom(); + + return OAuth2ClientRegistration.builder() + .registrationId(registrationId) + .mapperConfig(OAuth2MapperConfig.builder() + .allowUserCreation(mapperConfig.isAllowUserCreation()) + .activateUser(mapperConfig.isActivateUser()) + .type(MapperType.valueOf( + mapperConfig.getType().toUpperCase() + )) + .basicConfig( + OAuth2BasicMapperConfig.builder() + .emailAttributeKey(basicConfig.getEmailAttributeKey()) + .firstNameAttributeKey(basicConfig.getFirstNameAttributeKey()) + .lastNameAttributeKey(basicConfig.getLastNameAttributeKey()) + .tenantNameStrategy(TenantNameStrategyType.valueOf( + basicConfig.getTenantNameStrategy().toUpperCase() + )) + .tenantNamePattern(basicConfig.getTenantNamePattern()) + .customerNamePattern(basicConfig.getCustomerNamePattern()) + .defaultDashboardName(basicConfig.getDefaultDashboardName()) + .alwaysFullScreen(basicConfig.isAlwaysFullScreen()) + .build() + ) + .customConfig( + OAuth2CustomMapperConfig.builder() + .url(customConfig.getUrl()) + .username(customConfig.getUsername()) + .password(customConfig.getPassword()) + .build() + ) + .build()) + .clientId(oAuth2Client.getClientId()) + .clientSecret(oAuth2Client.getClientSecret()) + .authorizationUri(oAuth2Client.getAuthorizationUri()) + .tokenUri(oAuth2Client.getAccessTokenUri()) + .redirectUriTemplate(oAuth2Client.getRedirectUriTemplate()) + .scope(oAuth2Client.getScope()) + .authorizationGrantType(oAuth2Client.getAuthorizationGrantType()) + .userInfoUri(oAuth2Client.getUserInfoUri()) + .userNameAttribute(oAuth2Client.getUserNameAttributeName()) + .jwkSetUri(oAuth2Client.getJwkSetUri()) + .clientAuthenticationMethod(oAuth2Client.getClientAuthenticationMethod()) + .clientName(oAuth2Client.getClientName()) + .loginButtonLabel(oAuth2Client.getLoginButtonLabel()) + .loginButtonIcon(oAuth2Client.getLoginButtonIcon()) + .build(); + } } From f0a68ff9b636dfc717c419b025af0dbb5e68ae44 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Fri, 19 Jun 2020 18:12:50 +0300 Subject: [PATCH 012/463] Use ClientRegistration dao to load oauth2 configs --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 66 ++++++++++++++----- 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index d0dcbae4d5..760f2554fe 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.oauth2; +import com.google.common.collect.Sets; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -22,24 +23,35 @@ import org.thingsboard.server.common.data.id.OAuth2IntegrationId; import org.thingsboard.server.common.data.oauth2.*; import javax.annotation.PostConstruct; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; @Slf4j @Service public class OAuth2ServiceImpl implements OAuth2Service { + private static final String OAUTH2_AUTHORIZATION_PATH_TEMPLATE = "/oauth2/authorization/%s"; + @Autowired(required = false) OAuth2Configuration oauth2Configuration; @Autowired - private OAuth2ClientRegistrationDao clientRegistrationDao; + private OAuth2ClientRegistrationService clientRegistrationService; @PostConstruct public void init() { - + if (oauth2Configuration == null || !oauth2Configuration.isEnabled()) { + return; + } + Set dbClientRegistration = clientRegistrationService.findClientRegistrations().stream() + .map(OAuth2ClientRegistration::getRegistrationId) + .collect(Collectors.toSet()); + // TODO decide what to do with same registrationIds in DB + Sets.SetView intersection = Sets.intersection(dbClientRegistration, oauth2Configuration.getClients().keySet()); + if (!intersection.isEmpty()) { + throw new RuntimeException("OAuth2 configurations " + intersection + " are already stored in DB."); + } } @Override @@ -47,21 +59,41 @@ public class OAuth2ServiceImpl implements OAuth2Service { if (oauth2Configuration == null || !oauth2Configuration.isEnabled()) { return Collections.emptyList(); } - List result = new ArrayList<>(); - for (Map.Entry entry : oauth2Configuration.getClients().entrySet()) { - OAuth2ClientInfo client = new OAuth2ClientInfo(); - client.setName(entry.getValue().getLoginButtonLabel()); - client.setUrl(String.format("/oauth2/authorization/%s", entry.getKey())); - client.setIcon(entry.getValue().getLoginButtonIcon()); - result.add(client); - } - return result; + + Stream startUpConfiguration = oauth2Configuration.getClients().entrySet().stream() + .map(entry -> { + OAuth2ClientInfo client = new OAuth2ClientInfo(); + client.setName(entry.getValue().getLoginButtonLabel()); + client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, entry.getKey())); + client.setIcon(entry.getValue().getLoginButtonIcon()); + return client; + }); + + Stream dbConfiguration = clientRegistrationService.findClientRegistrations().stream() + .map(clientRegistration -> { + OAuth2ClientInfo client = new OAuth2ClientInfo(); + client.setName(clientRegistration.getLoginButtonLabel()); + client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, clientRegistration.getRegistrationId())); + client.setIcon(clientRegistration.getLoginButtonIcon()); + return client; + }); + + return Stream.concat(startUpConfiguration, dbConfiguration) + .collect(Collectors.toList()); } @Override public OAuth2ClientRegistration getClientRegistrationByRegistrationId(String registrationId) { - if (oauth2Configuration == null || oauth2Configuration.getClients() == null) return null; - OAuth2Client oAuth2Client = oauth2Configuration.getClients().get(registrationId); + if (oauth2Configuration == null || !oauth2Configuration.isEnabled()) return null; + OAuth2Client oAuth2Client = oauth2Configuration.getClients() == null ? null : oauth2Configuration.getClients().get(registrationId); + if (oAuth2Client != null){ + return toClientRegistration(registrationId, oAuth2Client); + } + + return clientRegistrationService.findClientRegistrationsByRegistrationId(registrationId); + } + + private OAuth2ClientRegistration toClientRegistration(String registrationId, OAuth2Client oAuth2Client) { OAuth2ClientMapperConfig mapperConfig = oAuth2Client.getMapperConfig(); OAuth2ClientMapperConfig.BasicOAuth2ClientMapperConfig basicConfig = mapperConfig.getBasic(); OAuth2ClientMapperConfig.CustomOAuth2ClientMapperConfig customConfig = mapperConfig.getCustom(); From 5586eed4b7ad77846a90bde1a7bdd0796d03da10 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Fri, 19 Jun 2020 18:19:24 +0300 Subject: [PATCH 013/463] Created HybridClientRegistrationRepository --- .../HybridClientRegistrationRepository.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java new file mode 100644 index 0000000000..72088e8151 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java @@ -0,0 +1,42 @@ +package org.thingsboard.server.dao.oauth2; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; + +@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true") +@Component +public class HybridClientRegistrationRepository implements ClientRegistrationRepository { + + @Autowired + private OAuth2Service oAuth2Service; + + @Override + public ClientRegistration findByRegistrationId(String registrationId) { + OAuth2ClientRegistration localClientRegistration = oAuth2Service.getClientRegistrationByRegistrationId(registrationId); + return localClientRegistration == null ? + null : toSpringClientRegistration(localClientRegistration); + } + + private ClientRegistration toSpringClientRegistration(OAuth2ClientRegistration localClientRegistration){ + return ClientRegistration.withRegistrationId(localClientRegistration.getRegistrationId()) + .clientId(localClientRegistration.getClientId()) + .authorizationUri(localClientRegistration.getAuthorizationUri()) + .clientSecret(localClientRegistration.getClientSecret()) + .tokenUri(localClientRegistration.getTokenUri()) + .redirectUriTemplate(localClientRegistration.getRedirectUriTemplate()) + .scope(localClientRegistration.getScope().split(",")) + .clientName(localClientRegistration.getClientName()) + .authorizationGrantType(new AuthorizationGrantType(localClientRegistration.getAuthorizationGrantType())) + .userInfoUri(localClientRegistration.getUserInfoUri()) + .userNameAttributeName(localClientRegistration.getUserNameAttribute()) + .jwkSetUri(localClientRegistration.getJwkSetUri()) + .clientAuthenticationMethod(new ClientAuthenticationMethod(localClientRegistration.getClientAuthenticationMethod())) + .build(); + } +} From de7c43d92b3a103086d07d6954f773b3ad6aa146 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Fri, 19 Jun 2020 18:25:01 +0300 Subject: [PATCH 014/463] Renamed 'user_name_attribute' to 'user_name_attribute_name' --- .../src/main/data/upgrade/3.1.0/schema_update.sql | 2 +- .../common/data/oauth2/OAuth2ClientRegistration.java | 6 +++--- .../thingsboard/server/dao/model/ModelConstants.java | 2 +- .../dao/model/sql/OAuth2ClientRegistrationEntity.java | 10 ++++------ .../dao/oauth2/HybridClientRegistrationRepository.java | 2 +- .../oauth2/OAuth2ClientRegistrationServiceImpl.java | 8 ++------ .../server/dao/oauth2/OAuth2ServiceImpl.java | 2 +- dao/src/main/resources/sql/schema-entities-hsql.sql | 2 +- dao/src/main/resources/sql/schema-entities.sql | 2 +- 9 files changed, 15 insertions(+), 21 deletions(-) diff --git a/application/src/main/data/upgrade/3.1.0/schema_update.sql b/application/src/main/data/upgrade/3.1.0/schema_update.sql index fc334929b4..645cc7a6ad 100644 --- a/application/src/main/data/upgrade/3.1.0/schema_update.sql +++ b/application/src/main/data/upgrade/3.1.0/schema_update.sql @@ -27,7 +27,7 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( scope varchar(255), authorization_grant_type varchar(255), user_info_uri varchar(255), - user_name_attribute varchar(255), + user_name_attribute_name varchar(255), jwk_set_uri varchar(255), client_authentication_method varchar(255), client_name varchar(255), diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java index fc855e24f1..84c95df84c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java @@ -19,7 +19,7 @@ public class OAuth2ClientRegistration extends BaseData { private String scope; private String authorizationGrantType; private String userInfoUri; - private String userNameAttribute; + private String userNameAttributeName; private String jwkSetUri; private String clientAuthenticationMethod; private String clientName; @@ -35,7 +35,7 @@ public class OAuth2ClientRegistration extends BaseData { } @Builder(toBuilder = true) - public OAuth2ClientRegistration(OAuth2IntegrationId id, String registrationId, String clientId, String clientSecret, String authorizationUri, String tokenUri, String redirectUriTemplate, String scope, String authorizationGrantType, String userInfoUri, String userNameAttribute, String jwkSetUri, String clientAuthenticationMethod, String clientName, String loginButtonLabel, String loginButtonIcon, OAuth2MapperConfig mapperConfig) { + public OAuth2ClientRegistration(OAuth2IntegrationId id, String registrationId, String clientId, String clientSecret, String authorizationUri, String tokenUri, String redirectUriTemplate, String scope, String authorizationGrantType, String userInfoUri, String userNameAttributeName, String jwkSetUri, String clientAuthenticationMethod, String clientName, String loginButtonLabel, String loginButtonIcon, OAuth2MapperConfig mapperConfig) { super(id); this.registrationId = registrationId; this.clientId = clientId; @@ -46,7 +46,7 @@ public class OAuth2ClientRegistration extends BaseData { this.scope = scope; this.authorizationGrantType = authorizationGrantType; this.userInfoUri = userInfoUri; - this.userNameAttribute = userNameAttribute; + this.userNameAttributeName = userNameAttributeName; this.jwkSetUri = jwkSetUri; this.clientAuthenticationMethod = clientAuthenticationMethod; this.clientName = clientName; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 2f967b648b..4deee9fe78 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -367,7 +367,7 @@ public class ModelConstants { public static final String OAUTH2_SCOPE_PROPERTY = "scope"; public static final String OAUTH2_AUTHORIZATION_GRANT_TYPE_PROPERTY = "authorization_grant_type"; public static final String OAUTH2_USER_INFO_URI_PROPERTY = "user_info_uri"; - public static final String OAUTH2_USER_NAME_ATTRIBUTE_PROPERTY = "user_name_attribute"; + public static final String OAUTH2_USER_NAME_ATTRIBUTE_NAME_PROPERTY = "user_name_attribute_name"; public static final String OAUTH2_JWK_SET_URI_PROPERTY = "jwk_set_uri"; public static final String OAUTH2_CLIENT_AUTHENTICATION_METHOD_PROPERTY = "client_authentication_method"; public static final String OAUTH2_CLIENT_NAME_PROPERTY = "client_name"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java index 70e9c034dc..c3c96c1838 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java @@ -17,9 +17,7 @@ package org.thingsboard.server.dao.model.sql; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; import org.hibernate.annotations.TypeDef; -import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.id.OAuth2IntegrationId; import org.thingsboard.server.common.data.oauth2.*; import org.thingsboard.server.dao.model.BaseSqlEntity; @@ -53,8 +51,8 @@ public class OAuth2ClientRegistrationEntity extends BaseSqlEntity Date: Fri, 19 Jun 2020 18:25:18 +0300 Subject: [PATCH 015/463] Removed unused ClientRegistrationRepository bean creation --- .../dao/oauth2/OAuth2Configuration.java | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Configuration.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Configuration.java index 114a96a61b..7618e6a776 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Configuration.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Configuration.java @@ -42,28 +42,4 @@ public class OAuth2Configuration { private boolean enabled; private String loginProcessingUrl; private Map clients = new HashMap<>(); - - @Bean - public ClientRegistrationRepository clientRegistrationRepository() { - List result = new ArrayList<>(); - for (Map.Entry entry : clients.entrySet()) { - OAuth2Client client = entry.getValue(); - ClientRegistration registration = ClientRegistration.withRegistrationId(entry.getKey()) - .clientId(client.getClientId()) - .authorizationUri(client.getAuthorizationUri()) - .clientSecret(client.getClientSecret()) - .tokenUri(client.getAccessTokenUri()) - .redirectUriTemplate(client.getRedirectUriTemplate()) - .scope(client.getScope().split(",")) - .clientName(client.getClientName()) - .authorizationGrantType(new AuthorizationGrantType(client.getAuthorizationGrantType())) - .userInfoUri(client.getUserInfoUri()) - .userNameAttributeName(client.getUserNameAttributeName()) - .jwkSetUri(client.getJwkSetUri()) - .clientAuthenticationMethod(new ClientAuthenticationMethod(client.getClientAuthenticationMethod())) - .build(); - result.add(registration); - } - return new InMemoryClientRegistrationRepository(result); - } } From 4d2813be8533027c4a8deb4c8db8c6d16a4d898f Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Mon, 22 Jun 2020 13:14:01 +0300 Subject: [PATCH 016/463] Added controller + fixes [almost working version] --- .../server/controller/OAuth2Controller.java | 61 +++++++++++++++++++ .../sql/OAuth2ClientRegistrationEntity.java | 36 ++++++----- .../OAuth2ClientRegistrationServiceImpl.java | 3 +- .../JpaOAuth2ClientRegistrationDao.java | 4 ++ 4 files changed, 87 insertions(+), 17 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java new file mode 100644 index 0000000000..07a0523223 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -0,0 +1,61 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; +import org.thingsboard.server.dao.oauth2.OAuth2ClientRegistrationService; +import org.thingsboard.server.dao.oauth2.OAuth2Service; +import org.thingsboard.server.queue.util.TbCoreComponent; + +@RestController +@TbCoreComponent +@RequestMapping("/api") +@Slf4j +public class OAuth2Controller extends BaseController { + private static final String REGISTRATION_ID = "registrationId"; + + @Autowired + private OAuth2Service oauth2Service; + @Autowired + private OAuth2ClientRegistrationService oAuth2ClientRegistrationService; + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + @RequestMapping(value = "/oauth2/config/{" + REGISTRATION_ID + "}", method = RequestMethod.GET) + @ResponseBody + public OAuth2ClientRegistration getClientRegistrationById(@PathVariable(REGISTRATION_ID) String registrationId) throws ThingsboardException { + try { + return oauth2Service.getClientRegistrationByRegistrationId(registrationId); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + @RequestMapping(value = "/oauth2/config", method = RequestMethod.POST) + @ResponseBody + public OAuth2ClientRegistration saveClientRegistration(@RequestBody OAuth2ClientRegistration clientRegistration) throws ThingsboardException { + try { + return oAuth2ClientRegistrationService.saveClientRegistration(clientRegistration); + } catch (Exception e) { + throw handleException(e); + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java index c3c96c1838..dfca90359f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java @@ -134,7 +134,7 @@ public class OAuth2ClientRegistrationEntity extends BaseSqlEntity Date: Mon, 22 Jun 2020 16:00:52 +0300 Subject: [PATCH 017/463] Removed oauth2 entities from DB --- .../main/data/upgrade/3.1.0/schema_update.sql | 51 ----- .../server/controller/OAuth2Controller.java | 14 -- .../OAuth2ClientRegistrationService.java | 30 --- .../sql/OAuth2ClientRegistrationEntity.java | 194 ------------------ .../oauth2/OAuth2ClientRegistrationDao.java | 15 -- .../OAuth2ClientRegistrationServiceImpl.java | 148 ------------- .../server/dao/oauth2/OAuth2ServiceImpl.java | 40 +--- .../JpaOAuth2ClientRegistrationDao.java | 77 ------- .../OAuth2ClientRegistrationRepository.java | 30 --- .../resources/sql/schema-entities-hsql.sql | 36 +--- .../main/resources/sql/schema-entities.sql | 34 --- 11 files changed, 6 insertions(+), 663 deletions(-) delete mode 100644 application/src/main/data/upgrade/3.1.0/schema_update.sql delete mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationService.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationServiceImpl.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java diff --git a/application/src/main/data/upgrade/3.1.0/schema_update.sql b/application/src/main/data/upgrade/3.1.0/schema_update.sql deleted file mode 100644 index 645cc7a6ad..0000000000 --- a/application/src/main/data/upgrade/3.1.0/schema_update.sql +++ /dev/null @@ -1,51 +0,0 @@ --- --- Copyright © 2016-2020 The Thingsboard Authors --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- - -DROP TABLE IF EXISTS oauth2_client_registration; - -CREATE TABLE IF NOT EXISTS oauth2_client_registration ( - id varchar(31) NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, - registration_id varchar(255), - client_id varchar(255), - client_secret varchar(255), - authorization_uri varchar(255), - token_uri varchar(255), - redirect_uri_template varchar(255), - scope varchar(255), - authorization_grant_type varchar(255), - user_info_uri varchar(255), - user_name_attribute_name varchar(255), - jwk_set_uri varchar(255), - client_authentication_method varchar(255), - client_name varchar(255), - login_button_label varchar(255), - login_button_icon varchar(255), - allow_user_creation boolean, - activate_user boolean, - type varchar(31), - basic_email_attribute_key varchar(31), - basic_first_name_attribute_key varchar(31), - basic_last_name_attribute_key varchar(31), - basic_tenant_name_strategy varchar(31), - basic_tenant_name_pattern varchar(255), - basic_customer_name_pattern varchar(255), - basic_default_dashboard_name varchar(255), - basic_always_full_screen boolean, - custom_url varchar(255), - custom_username varchar(255), - custom_password varchar(255), - CONSTRAINT oauth2_registration_id_unq_key UNIQUE (registration_id) -); \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index 07a0523223..ca3d51587b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -21,7 +21,6 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; -import org.thingsboard.server.dao.oauth2.OAuth2ClientRegistrationService; import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -34,8 +33,6 @@ public class OAuth2Controller extends BaseController { @Autowired private OAuth2Service oauth2Service; - @Autowired - private OAuth2ClientRegistrationService oAuth2ClientRegistrationService; @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @RequestMapping(value = "/oauth2/config/{" + REGISTRATION_ID + "}", method = RequestMethod.GET) @@ -47,15 +44,4 @@ public class OAuth2Controller extends BaseController { throw handleException(e); } } - - @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") - @RequestMapping(value = "/oauth2/config", method = RequestMethod.POST) - @ResponseBody - public OAuth2ClientRegistration saveClientRegistration(@RequestBody OAuth2ClientRegistration clientRegistration) throws ThingsboardException { - try { - return oAuth2ClientRegistrationService.saveClientRegistration(clientRegistration); - } catch (Exception e) { - throw handleException(e); - } - } } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationService.java deleted file mode 100644 index a38fb78eb9..0000000000 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationService.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.oauth2; - -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; - -import java.util.List; - -public interface OAuth2ClientRegistrationService { - OAuth2ClientRegistration saveClientRegistration(OAuth2ClientRegistration clientRegistration); - - List findClientRegistrations(); - - OAuth2ClientRegistration findClientRegistrationsByRegistrationId(String registrationId); - - void deleteClientRegistrationsByRegistrationId(String registrationId); -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java deleted file mode 100644 index dfca90359f..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java +++ /dev/null @@ -1,194 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.model.sql; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.hibernate.annotations.TypeDef; -import org.thingsboard.server.common.data.id.OAuth2IntegrationId; -import org.thingsboard.server.common.data.oauth2.*; -import org.thingsboard.server.dao.model.BaseSqlEntity; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.util.mapping.JsonStringType; - -import javax.persistence.*; - -@Data -@EqualsAndHashCode(callSuper = true) -@Entity -@TypeDef(name = "json", typeClass = JsonStringType.class) -@Table(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_COLUMN_FAMILY_NAME) -public class OAuth2ClientRegistrationEntity extends BaseSqlEntity { - - @Column(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_ID_PROPERTY) - private String registrationId; - @Column(name = ModelConstants.OAUTH2_CLIENT_ID_PROPERTY) - private String clientId; - @Column(name = ModelConstants.OAUTH2_CLIENT_SECRET_PROPERTY) - private String clientSecret; - @Column(name = ModelConstants.OAUTH2_AUTHORIZATION_URI_PROPERTY) - private String authorizationUri; - @Column(name = ModelConstants.OAUTH2_TOKEN_URI_PROPERTY) - private String tokenUri; - @Column(name = ModelConstants.OAUTH2_REDIRECT_URI_TEMPLATE_PROPERTY) - private String redirectUriTemplate; - @Column(name = ModelConstants.OAUTH2_SCOPE_PROPERTY) - private String scope; - @Column(name = ModelConstants.OAUTH2_AUTHORIZATION_GRANT_TYPE_PROPERTY) - private String authorizationGrantType; - @Column(name = ModelConstants.OAUTH2_USER_INFO_URI_PROPERTY) - private String userInfoUri; - @Column(name = ModelConstants.OAUTH2_USER_NAME_ATTRIBUTE_NAME_PROPERTY) - private String userNameAttributeName; - @Column(name = ModelConstants.OAUTH2_JWK_SET_URI_PROPERTY) - private String jwkSetUri; - @Column(name = ModelConstants.OAUTH2_CLIENT_AUTHENTICATION_METHOD_PROPERTY) - private String clientAuthenticationMethod; - @Column(name = ModelConstants.OAUTH2_CLIENT_NAME_PROPERTY) - private String clientName; - @Column(name = ModelConstants.OAUTH2_LOGIN_BUTTON_LABEL_PROPERTY) - private String loginButtonLabel; - @Column(name = ModelConstants.OAUTH2_LOGIN_BUTTON_ICON_PROPERTY) - private String loginButtonIcon; - @Column(name = ModelConstants.OAUTH2_ALLOW_USER_CREATION_PROPERTY) - private Boolean allowUserCreation; - @Column(name = ModelConstants.OAUTH2_ACTIVATE_USER_PROPERTY) - private Boolean activateUser; - @Enumerated(EnumType.STRING) - @Column(name = ModelConstants.OAUTH2_MAPPER_TYPE_PROPERTY) - private MapperType type; - @Column(name = ModelConstants.OAUTH2_EMAIL_ATTRIBUTE_KEY_PROPERTY) - private String emailAttributeKey; - @Column(name = ModelConstants.OAUTH2_FIRST_NAME_ATTRIBUTE_KEY_PROPERTY) - private String firstNameAttributeKey; - @Column(name = ModelConstants.OAUTH2_LAST_NAME_ATTRIBUTE_KEY_PROPERTY) - private String lastNameAttributeKey; - @Enumerated(EnumType.STRING) - @Column(name = ModelConstants.OAUTH2_TENANT_NAME_STRATEGY_PROPERTY) - private TenantNameStrategyType tenantNameStrategy; - @Column(name = ModelConstants.OAUTH2_TENANT_NAME_PATTERN_PROPERTY) - private String tenantNamePattern; - @Column(name = ModelConstants.OAUTH2_CUSTOMER_NAME_PATTERN_PROPERTY) - private String customerNamePattern; - @Column(name = ModelConstants.OAUTH2_DEFAULT_DASHBOARD_NAME_PROPERTY) - private String defaultDashboardName; - @Column(name = ModelConstants.OAUTH2_ALWAYS_FULL_SCREEN_PROPERTY) - private Boolean alwaysFullScreen; - @Column(name = ModelConstants.OAUTH2_MAPPER_URL_PROPERTY) - private String url; - @Column(name = ModelConstants.OAUTH2_MAPPER_USERNAME_PROPERTY) - private String username; - @Column(name = ModelConstants.OAUTH2_MAPPER_PASSWORD_PROPERTY) - private String password; - - public OAuth2ClientRegistrationEntity() { - super(); - } - - public OAuth2ClientRegistrationEntity(OAuth2ClientRegistration clientRegistration) { - if (clientRegistration.getId() != null) { - this.setUuid(clientRegistration.getId().getId()); - } - this.registrationId = clientRegistration.getRegistrationId(); - this.clientId = clientRegistration.getClientId(); - this.clientSecret = clientRegistration.getClientSecret(); - this.authorizationUri = clientRegistration.getAuthorizationUri(); - this.tokenUri = clientRegistration.getTokenUri(); - this.redirectUriTemplate = clientRegistration.getRedirectUriTemplate(); - this.scope = clientRegistration.getScope(); - this.authorizationGrantType = clientRegistration.getAuthorizationGrantType(); - this.userInfoUri = clientRegistration.getUserInfoUri(); - this.userNameAttributeName = clientRegistration.getUserNameAttributeName(); - this.jwkSetUri = clientRegistration.getJwkSetUri(); - this.clientAuthenticationMethod = clientRegistration.getClientAuthenticationMethod(); - this.clientName = clientRegistration.getClientName(); - this.loginButtonLabel = clientRegistration.getLoginButtonLabel(); - this.loginButtonIcon = clientRegistration.getLoginButtonIcon(); - OAuth2MapperConfig mapperConfig = clientRegistration.getMapperConfig(); - if (mapperConfig != null) { - this.allowUserCreation = mapperConfig.isAllowUserCreation(); - this.activateUser = mapperConfig.isActivateUser(); - this.type = mapperConfig.getType(); - OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasicConfig(); - if (basicConfig != null) { - this.emailAttributeKey = basicConfig.getEmailAttributeKey(); - this.firstNameAttributeKey = basicConfig.getFirstNameAttributeKey(); - this.lastNameAttributeKey = basicConfig.getLastNameAttributeKey(); - this.tenantNameStrategy = basicConfig.getTenantNameStrategy(); - this.tenantNamePattern = basicConfig.getTenantNamePattern(); - this.customerNamePattern = basicConfig.getCustomerNamePattern(); - this.defaultDashboardName = basicConfig.getDefaultDashboardName(); - this.alwaysFullScreen = basicConfig.isAlwaysFullScreen(); - } - OAuth2CustomMapperConfig customConfig = mapperConfig.getCustomConfig(); - if (customConfig != null) { - this.url = customConfig.getUrl(); - this.username = customConfig.getUsername(); - this.password = customConfig.getPassword(); - } - } - } - - @Override - public OAuth2ClientRegistration toData() { - return OAuth2ClientRegistration.builder() - .id(new OAuth2IntegrationId(toUUID(id))) - .registrationId(registrationId) - .mapperConfig(OAuth2MapperConfig.builder() - .allowUserCreation(allowUserCreation) - .activateUser(activateUser) - .type(type) - .basicConfig( - type == MapperType.BASIC ? - OAuth2BasicMapperConfig.builder() - .emailAttributeKey(emailAttributeKey) - .firstNameAttributeKey(firstNameAttributeKey) - .lastNameAttributeKey(lastNameAttributeKey) - .tenantNameStrategy(tenantNameStrategy) - .tenantNamePattern(tenantNamePattern) - .customerNamePattern(customerNamePattern) - .defaultDashboardName(defaultDashboardName) - .alwaysFullScreen(alwaysFullScreen) - .build() - : null - ) - .customConfig( - type == MapperType.CUSTOM ? - OAuth2CustomMapperConfig.builder() - .url(url) - .username(username) - .password(password) - .build() - : null - ) - .build()) - .clientId(clientId) - .clientSecret(clientSecret) - .authorizationUri(authorizationUri) - .tokenUri(tokenUri) - .redirectUriTemplate(redirectUriTemplate) - .scope(scope) - .authorizationGrantType(authorizationGrantType) - .userInfoUri(userInfoUri) - .userNameAttributeName(userNameAttributeName) - .jwkSetUri(jwkSetUri) - .clientAuthenticationMethod(clientAuthenticationMethod) - .clientName(clientName) - .loginButtonLabel(loginButtonLabel) - .loginButtonIcon(loginButtonIcon) - .build(); - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java deleted file mode 100644 index dbdcd8f68b..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.thingsboard.server.dao.oauth2; - -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; - -import java.util.List; - -public interface OAuth2ClientRegistrationDao { - List find(); - - OAuth2ClientRegistration findByRegistrationId(String registrationId); - - OAuth2ClientRegistration save(OAuth2ClientRegistration clientRegistration); - - boolean removeByRegistrationId(String registrationId); -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationServiceImpl.java deleted file mode 100644 index f19e819550..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationServiceImpl.java +++ /dev/null @@ -1,148 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.oauth2; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.oauth2.*; -import org.thingsboard.server.dao.exception.DataValidationException; - -import java.util.List; -import java.util.function.Consumer; - -import static org.thingsboard.server.dao.service.Validator.validateId; -import static org.thingsboard.server.dao.service.Validator.validateString; - -@Slf4j -@Service -public class OAuth2ClientRegistrationServiceImpl implements OAuth2ClientRegistrationService { - public static final String INCORRECT_REGISTRATION_ID = "Incorrect registrationId "; - - @Autowired - private OAuth2ClientRegistrationDao clientRegistrationDao; - - @Override - public OAuth2ClientRegistration saveClientRegistration(OAuth2ClientRegistration clientRegistration) { - log.trace("Executing saveClientRegistration [{}]", clientRegistration); - // TODO add checking for duplicates and other validations - return clientRegistrationDao.save(clientRegistration); - } - - @Override - public List findClientRegistrations() { - log.trace("Executing findClientRegistrations []"); - return clientRegistrationDao.find(); - } - - @Override - public OAuth2ClientRegistration findClientRegistrationsByRegistrationId(String registrationId) { - log.trace("Executing findClientRegistrationsByRegistrationId [{}]", registrationId); - validateString(registrationId, INCORRECT_REGISTRATION_ID + registrationId); - return clientRegistrationDao.findByRegistrationId(registrationId); - } - - @Override - public void deleteClientRegistrationsByRegistrationId(String registrationId) { - log.trace("Executing deleteClientRegistrationsByRegistrationId [{}]", registrationId); - validateString(registrationId, INCORRECT_REGISTRATION_ID + registrationId); - clientRegistrationDao.removeByRegistrationId(registrationId); - } - - private Consumer validator = clientRegistration -> { - if (StringUtils.isEmpty(clientRegistration.getRegistrationId())) { - throw new DataValidationException("Registration ID should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getClientId())) { - throw new DataValidationException("Client ID should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getClientSecret())) { - throw new DataValidationException("Client secret should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getAuthorizationUri())) { - throw new DataValidationException("Authorization uri should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getTokenUri())) { - throw new DataValidationException("Token uri should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getRedirectUriTemplate())) { - throw new DataValidationException("Redirect uri template should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getScope())) { - throw new DataValidationException("Scope should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getAuthorizationGrantType())) { - throw new DataValidationException("Authorization grant type should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getUserInfoUri())) { - throw new DataValidationException("User info uri should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getUserNameAttributeName())) { - throw new DataValidationException("User name attribute name should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getJwkSetUri())) { - throw new DataValidationException("Jwk set uri should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getClientAuthenticationMethod())) { - throw new DataValidationException("Client authentication method should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getClientName())) { - throw new DataValidationException("Client name should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getLoginButtonLabel())) { - throw new DataValidationException("Login button label should be specified!"); - } - OAuth2MapperConfig mapperConfig = clientRegistration.getMapperConfig(); - if (mapperConfig == null) { - throw new DataValidationException("Mapper config should be specified!"); - } - if (mapperConfig.getType() == null) { - throw new DataValidationException("Mapper config type should be specified!"); - } - if (mapperConfig.getType() == MapperType.BASIC) { - OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasicConfig(); - if (basicConfig == null) { - throw new DataValidationException("Basic config should be specified!"); - } - if (StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) { - throw new DataValidationException("Email attribute key should be specified!"); - } - if (basicConfig.getTenantNameStrategy() == null) { - throw new DataValidationException("Tenant name strategy should be specified!"); - } - if (basicConfig.getTenantNameStrategy() == TenantNameStrategyType.CUSTOM - && StringUtils.isEmpty(basicConfig.getTenantNamePattern())) { - throw new DataValidationException("Tenant name pattern should be specified!"); - } - } - if (mapperConfig.getType() == MapperType.CUSTOM) { - OAuth2CustomMapperConfig customConfig = mapperConfig.getCustomConfig(); - if (customConfig == null) { - throw new DataValidationException("Custom config should be specified!"); - } - if (StringUtils.isEmpty(customConfig.getUrl())) { - throw new DataValidationException("Custom mapper URL should be specified!"); - } - if (StringUtils.isEmpty(customConfig.getUsername())) { - throw new DataValidationException("Custom mapper username should be specified!"); - } - if (StringUtils.isEmpty(customConfig.getPassword())) { - throw new DataValidationException("Custom mapper password should be specified!"); - } - } - }; -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 3e128a0a52..30fe4d85e6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -15,15 +15,13 @@ */ package org.thingsboard.server.dao.oauth2; -import com.google.common.collect.Sets; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.id.OAuth2IntegrationId; import org.thingsboard.server.common.data.oauth2.*; -import javax.annotation.PostConstruct; -import java.util.*; +import java.util.Collections; +import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -36,24 +34,6 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Autowired(required = false) OAuth2Configuration oauth2Configuration; - @Autowired - private OAuth2ClientRegistrationService clientRegistrationService; - - @PostConstruct - public void init() { - if (oauth2Configuration == null || !oauth2Configuration.isEnabled()) { - return; - } - Set dbClientRegistration = clientRegistrationService.findClientRegistrations().stream() - .map(OAuth2ClientRegistration::getRegistrationId) - .collect(Collectors.toSet()); - // TODO decide what to do with same registrationIds in DB - Sets.SetView intersection = Sets.intersection(dbClientRegistration, oauth2Configuration.getClients().keySet()); - if (!intersection.isEmpty()) { - throw new RuntimeException("OAuth2 configurations " + intersection + " are already stored in DB."); - } - } - @Override public List getOAuth2Clients() { if (oauth2Configuration == null || !oauth2Configuration.isEnabled()) { @@ -69,17 +49,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { return client; }); - Stream dbConfiguration = clientRegistrationService.findClientRegistrations().stream() - .map(clientRegistration -> { - OAuth2ClientInfo client = new OAuth2ClientInfo(); - client.setName(clientRegistration.getLoginButtonLabel()); - client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, clientRegistration.getRegistrationId())); - client.setIcon(clientRegistration.getLoginButtonIcon()); - return client; - }); - - return Stream.concat(startUpConfiguration, dbConfiguration) - .collect(Collectors.toList()); + return startUpConfiguration.collect(Collectors.toList()); } @Override @@ -88,9 +58,9 @@ public class OAuth2ServiceImpl implements OAuth2Service { OAuth2Client oAuth2Client = oauth2Configuration.getClients() == null ? null : oauth2Configuration.getClients().get(registrationId); if (oAuth2Client != null){ return toClientRegistration(registrationId, oAuth2Client); + } else { + return null; } - - return clientRegistrationService.findClientRegistrationsByRegistrationId(registrationId); } private OAuth2ClientRegistration toClientRegistration(String registrationId, OAuth2Client oAuth2Client) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java deleted file mode 100644 index 63068d80fb..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.sql.oauth2; - -import com.datastax.oss.driver.api.core.uuid.Uuids; -import com.google.common.collect.Lists; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationEntity; -import org.thingsboard.server.dao.oauth2.OAuth2ClientRegistrationDao; -import org.thingsboard.server.dao.util.SqlDao; - -import java.util.List; -import java.util.Optional; - -@Slf4j -@Component -@SqlDao -@RequiredArgsConstructor -public class JpaOAuth2ClientRegistrationDao implements OAuth2ClientRegistrationDao { - private final OAuth2ClientRegistrationRepository repository; - - @Override - @Transactional - public OAuth2ClientRegistration save(OAuth2ClientRegistration clientRegistration) { - OAuth2ClientRegistrationEntity entity; - try { - entity = new OAuth2ClientRegistrationEntity(clientRegistration); - } catch (Exception e) { - log.error("Can't create entity for domain object {}", clientRegistration, e); - throw new IllegalArgumentException("Can't create entity for domain object {" + clientRegistration + "}", e); - } - log.debug("Saving entity {}", entity); - if (entity.getUuid() == null) { - entity.setUuid(Uuids.timeBased()); - } - entity = repository.save(entity); - return DaoUtil.getData(entity); - } - - @Override - public List find() { - List entities = Lists.newArrayList(repository.findAll()); - return DaoUtil.convertDataList(entities); - } - - @Override - public OAuth2ClientRegistration findByRegistrationId(String registrationId) { - log.debug("Get entity by registration id {}", registrationId); - Optional entity = repository.findByRegistrationId(registrationId); - return DaoUtil.getData(entity); - } - - @Override - public boolean removeByRegistrationId(String registrationId) { - repository.deleteByRegistrationId(registrationId); - log.debug("Remove request: {}", registrationId); - return !repository.existsByRegistrationId(registrationId); - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java deleted file mode 100644 index 2e6eb57209..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.sql.oauth2; - -import org.springframework.data.repository.CrudRepository; -import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationEntity; -import org.thingsboard.server.dao.util.SqlDao; - -import java.util.Optional; - -@SqlDao -public interface OAuth2ClientRegistrationRepository extends CrudRepository { - Optional findByRegistrationId(String registrationId); - int deleteByRegistrationId(String registrationId); - boolean existsByRegistrationId(String registrationId); - -} diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index 72785c1bb0..1e379afc6f 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -251,38 +251,4 @@ CREATE TABLE IF NOT EXISTS entity_view ( end_ts bigint, search_text varchar(255), additional_info varchar -); - -CREATE TABLE IF NOT EXISTS oauth2_client_registration ( - id varchar(31) NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, - registration_id varchar(255), - client_id varchar(255), - client_secret varchar(255), - authorization_uri varchar(255), - token_uri varchar(255), - redirect_uri_template varchar(255), - scope varchar(255), - authorization_grant_type varchar(255), - user_info_uri varchar(255), - user_name_attribute_name varchar(255), - jwk_set_uri varchar(255), - client_authentication_method varchar(255), - client_name varchar(255), - login_button_label varchar(255), - login_button_icon varchar(255), - allow_user_creation boolean, - activate_user boolean, - type varchar(31), - basic_email_attribute_key varchar(31), - basic_first_name_attribute_key varchar(31), - basic_last_name_attribute_key varchar(31), - basic_tenant_name_strategy varchar(31), - basic_tenant_name_pattern varchar(255), - basic_customer_name_pattern varchar(255), - basic_default_dashboard_name varchar(255), - basic_always_full_screen boolean, - custom_url varchar(255), - custom_username varchar(255), - custom_password varchar(255), - CONSTRAINT oauth2_registration_id_unq_key UNIQUE (registration_id) -); +); \ No newline at end of file diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index e83e4720ca..931856e1df 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -253,40 +253,6 @@ CREATE TABLE IF NOT EXISTS entity_view ( additional_info varchar ); -CREATE TABLE IF NOT EXISTS oauth2_client_registration ( - id varchar(31) NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, - registration_id varchar(255), - client_id varchar(255), - client_secret varchar(255), - authorization_uri varchar(255), - token_uri varchar(255), - redirect_uri_template varchar(255), - scope varchar(255), - authorization_grant_type varchar(255), - user_info_uri varchar(255), - user_name_attribute_name varchar(255), - jwk_set_uri varchar(255), - client_authentication_method varchar(255), - client_name varchar(255), - login_button_label varchar(255), - login_button_icon varchar(255), - allow_user_creation boolean, - activate_user boolean, - type varchar(31), - basic_email_attribute_key varchar(31), - basic_first_name_attribute_key varchar(31), - basic_last_name_attribute_key varchar(31), - basic_tenant_name_strategy varchar(31), - basic_tenant_name_pattern varchar(255), - basic_customer_name_pattern varchar(255), - basic_default_dashboard_name varchar(255), - basic_always_full_screen boolean, - custom_url varchar(255), - custom_username varchar(255), - custom_password varchar(255), - CONSTRAINT oauth2_registration_id_unq_key UNIQUE (registration_id) -); - CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint) LANGUAGE plpgsql AS $$ From 6e7d0c633f6fee6203f18408fea4f0eafd01aa37 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Mon, 22 Jun 2020 16:19:03 +0300 Subject: [PATCH 018/463] Small refactoring --- .../org/thingsboard/server/controller/OAuth2Controller.java | 2 +- .../auth/oauth2/Oauth2AuthenticationSuccessHandler.java | 2 +- .../java/org/thingsboard/server/dao/oauth2/OAuth2Service.java | 2 +- .../server/dao/oauth2/HybridClientRegistrationRepository.java | 2 +- .../org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index ca3d51587b..c0c3034c7b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -39,7 +39,7 @@ public class OAuth2Controller extends BaseController { @ResponseBody public OAuth2ClientRegistration getClientRegistrationById(@PathVariable(REGISTRATION_ID) String registrationId) throws ThingsboardException { try { - return oauth2Service.getClientRegistrationByRegistrationId(registrationId); + return oauth2Service.getClientRegistration(registrationId); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index 08ea4dedca..4dda36a0fb 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -64,7 +64,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS try { OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; - OAuth2ClientRegistration clientRegistration = oAuth2Service.getClientRegistrationByRegistrationId(token.getAuthorizedClientRegistrationId()); + OAuth2ClientRegistration clientRegistration = oAuth2Service.getClientRegistration(token.getAuthorizedClientRegistrationId()); OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(clientRegistration.getMapperConfig().getType()); SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, clientRegistration.getMapperConfig()); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java index efea46a416..49b6c6e98b 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java @@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; import java.util.List; public interface OAuth2Service { - OAuth2ClientRegistration getClientRegistrationByRegistrationId(String registrationId); + OAuth2ClientRegistration getClientRegistration(String registrationId); List getOAuth2Clients(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java index e2e38a01e6..f04c739ac1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java @@ -18,7 +18,7 @@ public class HybridClientRegistrationRepository implements ClientRegistrationRep @Override public ClientRegistration findByRegistrationId(String registrationId) { - OAuth2ClientRegistration localClientRegistration = oAuth2Service.getClientRegistrationByRegistrationId(registrationId); + OAuth2ClientRegistration localClientRegistration = oAuth2Service.getClientRegistration(registrationId); return localClientRegistration == null ? null : toSpringClientRegistration(localClientRegistration); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 30fe4d85e6..ebc66b9453 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -53,7 +53,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { } @Override - public OAuth2ClientRegistration getClientRegistrationByRegistrationId(String registrationId) { + public OAuth2ClientRegistration getClientRegistration(String registrationId) { if (oauth2Configuration == null || !oauth2Configuration.isEnabled()) return null; OAuth2Client oAuth2Client = oauth2Configuration.getClients() == null ? null : oauth2Configuration.getClients().get(registrationId); if (oAuth2Client != null){ From a6eefa903e9a386deb289a64a2b86d17cb17b99f Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Mon, 22 Jun 2020 17:37:35 +0300 Subject: [PATCH 019/463] Added required methods to OAuth2Service --- .../server/dao/oauth2/OAuth2Service.java | 31 ++++++++++-- .../server/dao/oauth2/OAuth2ServiceImpl.java | 48 +++++++++++++++++++ 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java index 49b6c6e98b..1eef5fb948 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java @@ -1,12 +1,12 @@ /** * Copyright © 2016-2020 The Thingsboard Authors - * + *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,6 +15,9 @@ */ package org.thingsboard.server.dao.oauth2; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; @@ -24,4 +27,24 @@ public interface OAuth2Service { OAuth2ClientRegistration getClientRegistration(String registrationId); List getOAuth2Clients(); + + List getSystemOAuth2ClientRegistrations(TenantId tenantId); + + List getTenantOAuth2ClientRegistrations(TenantId tenantId); + + List getCustomerOAuth2ClientRegistrations(TenantId tenantId, CustomerId customerId); + + OAuth2ClientRegistration saveSystemOAuth2ClientRegistration(OAuth2ClientRegistration clientRegistration); + + OAuth2ClientRegistration saveTenantOAuth2ClientRegistration(TenantId tenantId, OAuth2ClientRegistration clientRegistration); + + OAuth2ClientRegistration saveCustomerOAuth2ClientRegistration(TenantId tenantId, CustomerId customerId, OAuth2ClientRegistration clientRegistration); + + void deleteDomainOAuth2ClientRegistrationByEntityId(TenantId tenantId, EntityId entityId); + + boolean isOAuth2ClientRegistrationAllowed(TenantId tenantId, EntityId entityId); + + boolean isCustomerOAuth2ClientRegistrationAllowed(TenantId tenantId); + + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index ebc66b9453..8ec883d55a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -18,6 +18,9 @@ package org.thingsboard.server.dao.oauth2; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.*; import java.util.Collections; @@ -52,6 +55,51 @@ public class OAuth2ServiceImpl implements OAuth2Service { return startUpConfiguration.collect(Collectors.toList()); } + @Override + public List getSystemOAuth2ClientRegistrations(TenantId tenantId) { + return null; + } + + @Override + public List getTenantOAuth2ClientRegistrations(TenantId tenantId) { + return null; + } + + @Override + public List getCustomerOAuth2ClientRegistrations(TenantId tenantId, CustomerId customerId) { + return null; + } + + @Override + public OAuth2ClientRegistration saveSystemOAuth2ClientRegistration(OAuth2ClientRegistration clientRegistration) { + return null; + } + + @Override + public OAuth2ClientRegistration saveTenantOAuth2ClientRegistration(TenantId tenantId, OAuth2ClientRegistration clientRegistration) { + return null; + } + + @Override + public OAuth2ClientRegistration saveCustomerOAuth2ClientRegistration(TenantId tenantId, CustomerId customerId, OAuth2ClientRegistration clientRegistration) { + return null; + } + + @Override + public void deleteDomainOAuth2ClientRegistrationByEntityId(TenantId tenantId, EntityId entityId) { + + } + + @Override + public boolean isOAuth2ClientRegistrationAllowed(TenantId tenantId, EntityId entityId) { + return false; + } + + @Override + public boolean isCustomerOAuth2ClientRegistrationAllowed(TenantId tenantId) { + return false; + } + @Override public OAuth2ClientRegistration getClientRegistration(String registrationId) { if (oauth2Configuration == null || !oauth2Configuration.isEnabled()) return null; From ac8cfebe31e91fc8758df03b213bf16af25b156a Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Mon, 22 Jun 2020 18:20:51 +0300 Subject: [PATCH 020/463] Removed .yml oauth2 configs --- .../ThingsboardSecurityConfiguration.java | 2 +- .../src/main/resources/thingsboard.yml | 54 ------------- .../server/dao/oauth2/OAuth2Client.java | 39 ---------- .../dao/oauth2/OAuth2ClientMapperConfig.java | 47 ------------ .../dao/oauth2/OAuth2Configuration.java | 16 ---- .../server/dao/oauth2/OAuth2ServiceImpl.java | 75 +------------------ 6 files changed, 3 insertions(+), 230 deletions(-) delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Client.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientMapperConfig.java diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java index a96843ce2c..fb71bcb994 100644 --- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java @@ -207,7 +207,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt .addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(buildWsJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class) .addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class); - if (oauth2Configuration != null && oauth2Configuration.isEnabled()) { + if (oauth2Configuration != null) { http.oauth2Login() .loginPage("/oauth2Login") .loginProcessingUrl(oauth2Configuration.getLoginProcessingUrl()) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 90b84dfbad..53fc316e56 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -107,62 +107,8 @@ security: basic: enabled: "${SECURITY_BASIC_ENABLED:false}" oauth2: - # Enable/disable OAuth 2 login functionality - # For details please refer to https://thingsboard.io/docs/user-guide/oauth-2-support/ - enabled: "${SECURITY_OAUTH2_ENABLED:false}" # Redirect URL where access code from external user management system will be processed loginProcessingUrl: "${SECURITY_OAUTH2_LOGIN_PROCESSING_URL:/login/oauth2/code/}" - # List of SSO clients - clients: - default: - # Label that going to be show on login button - 'Login with {loginButtonLabel}' - loginButtonLabel: "${SECURITY_OAUTH2_DEFAULT_LOGIN_BUTTON_LABEL:Default}" - # Icon that going to be show on login button. Material design icon ID (https://material.angularjs.org/latest/api/directive/mdIcon) - loginButtonIcon: "${SECURITY_OAUTH2_DEFAULT_LOGIN_BUTTON_ICON:}" - clientName: "${SECURITY_OAUTH2_DEFAULT_CLIENT_NAME:ClientName}" - clientId: "${SECURITY_OAUTH2_DEFAULT_CLIENT_ID:}" - clientSecret: "${SECURITY_OAUTH2_DEFAULT_CLIENT_SECRET:}" - accessTokenUri: "${SECURITY_OAUTH2_DEFAULT_ACCESS_TOKEN_URI:}" - authorizationUri: "${SECURITY_OAUTH2_DEFAULT_AUTHORIZATION_URI:}" - scope: "${SECURITY_OAUTH2_DEFAULT_SCOPE:}" - # Redirect URL that must be in sync with 'security.oauth2.loginProcessingUrl', but domain name added - redirectUriTemplate: "${SECURITY_OAUTH2_DEFAULT_REDIRECT_URI_TEMPLATE:http://localhost:8080/login/oauth2/code/}" - jwkSetUri: "${SECURITY_OAUTH2_DEFAULT_JWK_SET_URI:}" - # 'authorization_code', 'implicit', 'refresh_token' or 'client_credentials' - authorizationGrantType: "${SECURITY_OAUTH2_DEFAULT_AUTHORIZATION_GRANT_TYPE:authorization_code}" - clientAuthenticationMethod: "${SECURITY_OAUTH2_DEFAULT_CLIENT_AUTHENTICATION_METHOD:post}" # basic or post - userInfoUri: "${SECURITY_OAUTH2_DEFAULT_USER_INFO_URI:}" - userNameAttributeName: "${SECURITY_OAUTH2_DEFAULT_USER_NAME_ATTRIBUTE_NAME:email}" - mapperConfig: - # Allows to create user if it not exists - allowUserCreation: "${SECURITY_OAUTH2_DEFAULT_MAPPER_ALLOW_USER_CREATION:true}" - # Allows user to setup ThingsBoard internal password and login over default Login window - activateUser: "${SECURITY_OAUTH2_DEFAULT_MAPPER_ACTIVATE_USER:false}" - # Mapper type of converter from external user into internal - 'basic' or 'custom' - type: "${SECURITY_OAUTH2_DEFAULT_MAPPER_TYPE:basic}" - basic: - # Key from attributes of external user object to use as email - emailAttributeKey: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_EMAIL_ATTRIBUTE_KEY:email}" - firstNameAttributeKey: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_FIRST_NAME_ATTRIBUTE_KEY:}" - lastNameAttributeKey: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_LAST_NAME_ATTRIBUTE_KEY:}" - # Strategy for generating Tenant from external user object - 'domain', 'email' or 'custom' - # 'domain' - name of the Tenant will be extracted as domain from the email of the user - # 'email' - name of the Tenant will email of the user - # 'custom' - please configure 'tenantNamePattern' for custom mapping - tenantNameStrategy: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_TENANT_NAME_STRATEGY:domain}" - # %{attribute_key} as placeholder for attribute value of attributes of external user object - tenantNamePattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_TENANT_NAME_PATTERN:}" - # If this field is not empty, user will be created as a user under defined Customer - # %{attribute_key} as placeholder for attribute value of attributes of external user object - customerNamePattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_CUSTOMER_NAME_PATTERN:}" - # If this field is not empty, user will be created with default defined Dashboard - defaultDashboardName: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_DEFAULT_DASHBOARD_NAME:}" - # If this field is set 'true' along with non-empty 'defaultDashboardName', user will start from the defined Dashboard in fullscreen mode - alwaysFullScreen: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_ALWAYS_FULL_SCREEN:false}" - custom: - url: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_URL:}" - username: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_USERNAME:}" - password: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_PASSWORD:}" # Dashboard parameters dashboard: diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Client.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Client.java deleted file mode 100644 index 9676d55f5f..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Client.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.oauth2; - -import lombok.Data; - -@Data -public class OAuth2Client { - - private String loginButtonLabel; - private String loginButtonIcon; - private String clientName; - private String clientId; - private String clientSecret; - private String accessTokenUri; - private String authorizationUri; - private String scope; - private String redirectUriTemplate; - private String jwkSetUri; - private String authorizationGrantType; - private String clientAuthenticationMethod; - private String userInfoUri; - private String userNameAttributeName; - private OAuth2ClientMapperConfig mapperConfig; - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientMapperConfig.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientMapperConfig.java deleted file mode 100644 index f2f23843f2..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientMapperConfig.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.oauth2; - -import lombok.Data; - -@Data -public class OAuth2ClientMapperConfig { - - private boolean allowUserCreation; - private boolean activateUser; - private String type; - private BasicOAuth2ClientMapperConfig basic; - private CustomOAuth2ClientMapperConfig custom; - - @Data - public static class BasicOAuth2ClientMapperConfig { - private String emailAttributeKey; - private String firstNameAttributeKey; - private String lastNameAttributeKey; - private String tenantNameStrategy; - private String tenantNamePattern; - private String customerNamePattern; - private boolean alwaysFullScreen; - private String defaultDashboardName; - } - - @Data - public static class CustomOAuth2ClientMapperConfig { - private String url; - private String username; - private String password; - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Configuration.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Configuration.java index 7618e6a776..d78ec66273 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Configuration.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Configuration.java @@ -17,29 +17,13 @@ package org.thingsboard.server.dao.oauth2; import lombok.Data; import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; @Configuration -@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true") @ConfigurationProperties(prefix = "security.oauth2") @Data @Slf4j public class OAuth2Configuration { - - private boolean enabled; private String loginProcessingUrl; - private Map clients = new HashMap<>(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 8ec883d55a..88167bb019 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -39,20 +39,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Override public List getOAuth2Clients() { - if (oauth2Configuration == null || !oauth2Configuration.isEnabled()) { - return Collections.emptyList(); - } - - Stream startUpConfiguration = oauth2Configuration.getClients().entrySet().stream() - .map(entry -> { - OAuth2ClientInfo client = new OAuth2ClientInfo(); - client.setName(entry.getValue().getLoginButtonLabel()); - client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, entry.getKey())); - client.setIcon(entry.getValue().getLoginButtonIcon()); - return client; - }); - - return startUpConfiguration.collect(Collectors.toList()); + return Collections.emptyList(); } @Override @@ -102,64 +89,6 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Override public OAuth2ClientRegistration getClientRegistration(String registrationId) { - if (oauth2Configuration == null || !oauth2Configuration.isEnabled()) return null; - OAuth2Client oAuth2Client = oauth2Configuration.getClients() == null ? null : oauth2Configuration.getClients().get(registrationId); - if (oAuth2Client != null){ - return toClientRegistration(registrationId, oAuth2Client); - } else { - return null; - } - } - - private OAuth2ClientRegistration toClientRegistration(String registrationId, OAuth2Client oAuth2Client) { - OAuth2ClientMapperConfig mapperConfig = oAuth2Client.getMapperConfig(); - OAuth2ClientMapperConfig.BasicOAuth2ClientMapperConfig basicConfig = mapperConfig.getBasic(); - OAuth2ClientMapperConfig.CustomOAuth2ClientMapperConfig customConfig = mapperConfig.getCustom(); - - return OAuth2ClientRegistration.builder() - .registrationId(registrationId) - .mapperConfig(OAuth2MapperConfig.builder() - .allowUserCreation(mapperConfig.isAllowUserCreation()) - .activateUser(mapperConfig.isActivateUser()) - .type(MapperType.valueOf( - mapperConfig.getType().toUpperCase() - )) - .basicConfig( - OAuth2BasicMapperConfig.builder() - .emailAttributeKey(basicConfig.getEmailAttributeKey()) - .firstNameAttributeKey(basicConfig.getFirstNameAttributeKey()) - .lastNameAttributeKey(basicConfig.getLastNameAttributeKey()) - .tenantNameStrategy(TenantNameStrategyType.valueOf( - basicConfig.getTenantNameStrategy().toUpperCase() - )) - .tenantNamePattern(basicConfig.getTenantNamePattern()) - .customerNamePattern(basicConfig.getCustomerNamePattern()) - .defaultDashboardName(basicConfig.getDefaultDashboardName()) - .alwaysFullScreen(basicConfig.isAlwaysFullScreen()) - .build() - ) - .customConfig( - OAuth2CustomMapperConfig.builder() - .url(customConfig.getUrl()) - .username(customConfig.getUsername()) - .password(customConfig.getPassword()) - .build() - ) - .build()) - .clientId(oAuth2Client.getClientId()) - .clientSecret(oAuth2Client.getClientSecret()) - .authorizationUri(oAuth2Client.getAuthorizationUri()) - .tokenUri(oAuth2Client.getAccessTokenUri()) - .redirectUriTemplate(oAuth2Client.getRedirectUriTemplate()) - .scope(oAuth2Client.getScope()) - .authorizationGrantType(oAuth2Client.getAuthorizationGrantType()) - .userInfoUri(oAuth2Client.getUserInfoUri()) - .userNameAttributeName(oAuth2Client.getUserNameAttributeName()) - .jwkSetUri(oAuth2Client.getJwkSetUri()) - .clientAuthenticationMethod(oAuth2Client.getClientAuthenticationMethod()) - .clientName(oAuth2Client.getClientName()) - .loginButtonLabel(oAuth2Client.getLoginButtonLabel()) - .loginButtonIcon(oAuth2Client.getLoginButtonIcon()) - .build(); + return null; } } From b20b032f0b1f61345b19ff132b78e18ffedb5d86 Mon Sep 17 00:00:00 2001 From: viktor Date: Tue, 23 Jun 2020 11:05:46 +0300 Subject: [PATCH 021/463] Added saveSystemOAuth2ClientRegistration method --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 88167bb019..18a390e783 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -15,13 +15,20 @@ */ package org.thingsboard.server.dao.oauth2; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.*; +import org.thingsboard.server.dao.exception.IncorrectParameterException; +import org.thingsboard.server.dao.settings.AdminSettingsService; import java.util.Collections; import java.util.List; @@ -32,10 +39,14 @@ import java.util.stream.Stream; @Service public class OAuth2ServiceImpl implements OAuth2Service { + private static final ObjectMapper mapper = new ObjectMapper(); + + private static final String OAUTH2_CLIENT_REGISTRATIONS_PARAMS = "oauth2ClientRegistrationsParams"; + private static final String OAUTH2_AUTHORIZATION_PATH_TEMPLATE = "/oauth2/authorization/%s"; - @Autowired(required = false) - OAuth2Configuration oauth2Configuration; + @Autowired + private AdminSettingsService adminSettingsService; @Override public List getOAuth2Clients() { @@ -59,11 +70,31 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Override public OAuth2ClientRegistration saveSystemOAuth2ClientRegistration(OAuth2ClientRegistration clientRegistration) { - return null; + // TODO check by registration ID in entities + AdminSettings clientRegistrationParamsSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); + if (clientRegistrationParamsSettings == null) { + clientRegistrationParamsSettings = new AdminSettings(); + clientRegistrationParamsSettings.setKey(OAUTH2_CLIENT_REGISTRATIONS_PARAMS); + ObjectNode node = mapper.createObjectNode(); + clientRegistrationParamsSettings.setJsonValue(node); + } + String json; + try { + json = mapper.writeValueAsString(clientRegistration); + } catch (JsonProcessingException e) { + log.error("Unable to convert OAuth2 Client Registration Params to JSON!", e); + throw new IncorrectParameterException("Unable to convert OAuth2 Client Registration Params to JSON!"); + } + ObjectNode oldClientRegistrations = (ObjectNode) clientRegistrationParamsSettings.getJsonValue(); + oldClientRegistrations.put(clientRegistration.getRegistrationId(), json); + adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, clientRegistrationParamsSettings); + // TODO ask if that's worth it + return getClientRegistration(clientRegistration.getRegistrationId()); } @Override public OAuth2ClientRegistration saveTenantOAuth2ClientRegistration(TenantId tenantId, OAuth2ClientRegistration clientRegistration) { + // TODO check by registration ID in system return null; } From 334361577f84fdf5a29ccf846fbfcff10684d953 Mon Sep 17 00:00:00 2001 From: viktor Date: Tue, 23 Jun 2020 12:39:30 +0300 Subject: [PATCH 022/463] Changed OAuth2Service, clear Tenant oauth2 config on deletion --- .../server/controller/AuthController.java | 5 ++- .../server/dao/oauth2/OAuth2Service.java | 15 ++----- .../data/oauth2/OAuth2ClientRegistration.java | 36 +++------------- .../data/oauth2/OAuth2ClientsParams.java | 18 ++++++++ .../server/dao/oauth2/OAuth2ServiceImpl.java | 41 +++++++++---------- .../server/dao/tenant/TenantServiceImpl.java | 5 +++ 6 files changed, 54 insertions(+), 66 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java index 9f6c6f3b44..2cf9e5d86b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java @@ -337,11 +337,12 @@ public class AuthController extends BaseController { } } + // TODO ask why POST @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) @ResponseBody - public List getOAuth2Clients() throws ThingsboardException { + public List getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { try { - return oauth2Service.getOAuth2Clients(); + return oauth2Service.getOAuth2Clients(request.getServerName()); } catch (Exception e) { throw handleException(e); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java index 1eef5fb948..81a1abbc47 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java @@ -26,25 +26,18 @@ import java.util.List; public interface OAuth2Service { OAuth2ClientRegistration getClientRegistration(String registrationId); - List getOAuth2Clients(); + List getOAuth2Clients(String domainName); List getSystemOAuth2ClientRegistrations(TenantId tenantId); List getTenantOAuth2ClientRegistrations(TenantId tenantId); - List getCustomerOAuth2ClientRegistrations(TenantId tenantId, CustomerId customerId); - OAuth2ClientRegistration saveSystemOAuth2ClientRegistration(OAuth2ClientRegistration clientRegistration); - OAuth2ClientRegistration saveTenantOAuth2ClientRegistration(TenantId tenantId, OAuth2ClientRegistration clientRegistration); - - OAuth2ClientRegistration saveCustomerOAuth2ClientRegistration(TenantId tenantId, CustomerId customerId, OAuth2ClientRegistration clientRegistration); - - void deleteDomainOAuth2ClientRegistrationByEntityId(TenantId tenantId, EntityId entityId); - - boolean isOAuth2ClientRegistrationAllowed(TenantId tenantId, EntityId entityId); + OAuth2ClientRegistration saveTenantOAuth2ClientRegistration(TenantId tenantId, String domainName, OAuth2ClientRegistration clientRegistration); - boolean isCustomerOAuth2ClientRegistrationAllowed(TenantId tenantId); + void deleteDomainOAuth2ClientRegistrationByTenant(TenantId tenantId); + boolean isOAuth2ClientRegistrationAllowed(TenantId tenantId); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java index 84c95df84c..a9fe5ed060 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java @@ -4,10 +4,13 @@ import lombok.*; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.id.OAuth2IntegrationId; -@EqualsAndHashCode(callSuper = true) +@EqualsAndHashCode @Data @ToString(exclude = {"clientSecret"}) -public class OAuth2ClientRegistration extends BaseData { +@Builder(toBuilder = true) +@NoArgsConstructor +@AllArgsConstructor +public class OAuth2ClientRegistration { private String registrationId; private OAuth2MapperConfig mapperConfig; @@ -25,33 +28,4 @@ public class OAuth2ClientRegistration extends BaseData { private String clientName; private String loginButtonLabel; private String loginButtonIcon; - - public OAuth2ClientRegistration() { - super(); - } - - public OAuth2ClientRegistration(OAuth2IntegrationId id) { - super(id); - } - - @Builder(toBuilder = true) - public OAuth2ClientRegistration(OAuth2IntegrationId id, String registrationId, String clientId, String clientSecret, String authorizationUri, String tokenUri, String redirectUriTemplate, String scope, String authorizationGrantType, String userInfoUri, String userNameAttributeName, String jwkSetUri, String clientAuthenticationMethod, String clientName, String loginButtonLabel, String loginButtonIcon, OAuth2MapperConfig mapperConfig) { - super(id); - this.registrationId = registrationId; - this.clientId = clientId; - this.clientSecret = clientSecret; - this.authorizationUri = authorizationUri; - this.tokenUri = tokenUri; - this.redirectUriTemplate = redirectUriTemplate; - this.scope = scope; - this.authorizationGrantType = authorizationGrantType; - this.userInfoUri = userInfoUri; - this.userNameAttributeName = userNameAttributeName; - this.jwkSetUri = jwkSetUri; - this.clientAuthenticationMethod = clientAuthenticationMethod; - this.clientName = clientName; - this.loginButtonLabel = loginButtonLabel; - this.loginButtonIcon = loginButtonIcon; - this.mapperConfig = mapperConfig; - } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java new file mode 100644 index 0000000000..1adcd51f3b --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java @@ -0,0 +1,18 @@ +package org.thingsboard.server.common.data.oauth2; + +import lombok.*; + +import java.util.List; + +@EqualsAndHashCode +@Data +@ToString +@Builder(toBuilder = true) +@NoArgsConstructor +@AllArgsConstructor +public class OAuth2ClientsParams { + private String domainName; + private String adminSettingsId; + + private List clientRegistrations; +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 18a390e783..6ce4ae1599 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -17,12 +17,13 @@ package org.thingsboard.server.dao.oauth2; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.id.AdminSettingsId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -32,8 +33,7 @@ import org.thingsboard.server.dao.settings.AdminSettingsService; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import java.util.UUID; @Slf4j @Service @@ -42,6 +42,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { private static final ObjectMapper mapper = new ObjectMapper(); private static final String OAUTH2_CLIENT_REGISTRATIONS_PARAMS = "oauth2ClientRegistrationsParams"; + private static final String OAUTH2_CLIENT_REGISTRATIONS_DOMAIN_NAME_PREFIX = "oauth2ClientRegistrationsDomainNamePrefix"; private static final String OAUTH2_AUTHORIZATION_PATH_TEMPLATE = "/oauth2/authorization/%s"; @@ -49,7 +50,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { private AdminSettingsService adminSettingsService; @Override - public List getOAuth2Clients() { + public List getOAuth2Clients(String domainName) { return Collections.emptyList(); } @@ -63,11 +64,6 @@ public class OAuth2ServiceImpl implements OAuth2Service { return null; } - @Override - public List getCustomerOAuth2ClientRegistrations(TenantId tenantId, CustomerId customerId) { - return null; - } - @Override public OAuth2ClientRegistration saveSystemOAuth2ClientRegistration(OAuth2ClientRegistration clientRegistration) { // TODO check by registration ID in entities @@ -93,28 +89,19 @@ public class OAuth2ServiceImpl implements OAuth2Service { } @Override - public OAuth2ClientRegistration saveTenantOAuth2ClientRegistration(TenantId tenantId, OAuth2ClientRegistration clientRegistration) { + public OAuth2ClientRegistration saveTenantOAuth2ClientRegistration(TenantId tenantId, String domainName, OAuth2ClientRegistration clientRegistration) { + // TODO ask what if tenant saves config for several different domain names, do we need to check it // TODO check by registration ID in system return null; } @Override - public OAuth2ClientRegistration saveCustomerOAuth2ClientRegistration(TenantId tenantId, CustomerId customerId, OAuth2ClientRegistration clientRegistration) { - return null; - } - - @Override - public void deleteDomainOAuth2ClientRegistrationByEntityId(TenantId tenantId, EntityId entityId) { + public void deleteDomainOAuth2ClientRegistrationByTenant(TenantId tenantId) { } @Override - public boolean isOAuth2ClientRegistrationAllowed(TenantId tenantId, EntityId entityId) { - return false; - } - - @Override - public boolean isCustomerOAuth2ClientRegistrationAllowed(TenantId tenantId) { + public boolean isOAuth2ClientRegistrationAllowed(TenantId tenantId) { return false; } @@ -122,4 +109,14 @@ public class OAuth2ServiceImpl implements OAuth2Service { public OAuth2ClientRegistration getClientRegistration(String registrationId) { return null; } + + private String constructClientRegistrationsKey(String domainName) { + String clientRegistrationsKey; + if (StringUtils.isEmpty(domainName)) { + clientRegistrationsKey = OAUTH2_CLIENT_REGISTRATIONS_PARAMS; + } else { + clientRegistrationsKey = OAUTH2_CLIENT_REGISTRATIONS_DOMAIN_NAME_PREFIX + "_" + domainName; + } + return clientRegistrationsKey; + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index 33407db373..a6cfdee8c2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -33,6 +33,7 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; @@ -78,6 +79,9 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe @Autowired private RuleChainService ruleChainService; + @Autowired + private OAuth2Service oAuth2Service; + @Override public Tenant findTenantById(TenantId tenantId) { log.trace("Executing findTenantById [{}]", tenantId); @@ -104,6 +108,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe public void deleteTenant(TenantId tenantId) { log.trace("Executing deleteTenant [{}]", tenantId); Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + oAuth2Service.deleteDomainOAuth2ClientRegistrationByTenant(tenantId); customerService.deleteCustomersByTenantId(tenantId); widgetsBundleService.deleteWidgetsBundlesByTenantId(tenantId); dashboardService.deleteDashboardsByTenantId(tenantId); From c3c889bbbf843c7fe18d10f28ff6f307475fd2eb Mon Sep 17 00:00:00 2001 From: viktor Date: Tue, 23 Jun 2020 14:37:56 +0300 Subject: [PATCH 023/463] Added 'deleteAdminSettingByKey' method to AdminSettingsService --- .../server/dao/settings/AdminSettingsService.java | 4 +++- .../server/dao/settings/AdminSettingsServiceImpl.java | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java index ee263c2564..58b25f2dee 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java @@ -24,7 +24,9 @@ public interface AdminSettingsService { AdminSettings findAdminSettingsById(TenantId tenantId, AdminSettingsId adminSettingsId); AdminSettings findAdminSettingsByKey(TenantId tenantId, String key); - + + void deleteAdminSettingsByKey(TenantId tenantId, String key); + AdminSettings saveAdminSettings(TenantId tenantId, AdminSettings adminSettings); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java index 6c7495cc19..5e5e5e2751 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java @@ -48,6 +48,15 @@ public class AdminSettingsServiceImpl implements AdminSettingsService { return adminSettingsDao.findByKey(tenantId, key); } + @Override + public void deleteAdminSettingsByKey(TenantId tenantId, String key) { + log.trace("Executing deleteAdminSettingsByKey [{}]", key); + AdminSettings adminSettings = findAdminSettingsByKey(tenantId, key); + if (adminSettings != null) { + adminSettingsDao.removeById(tenantId, adminSettings.getId().getId()); + } + } + @Override public AdminSettings saveAdminSettings(TenantId tenantId, AdminSettings adminSettings) { log.trace("Executing saveAdminSettings [{}]", adminSettings); From 5a00973b47eb2d807a014d8fd0f279e6e37814a2 Mon Sep 17 00:00:00 2001 From: viktor Date: Tue, 23 Jun 2020 14:38:50 +0300 Subject: [PATCH 024/463] Implemented part of OAuth2Service --- .../server/dao/oauth2/OAuth2Service.java | 9 +- .../server/dao/oauth2/OAuth2ServiceImpl.java | 292 ++++++++++++++++-- 2 files changed, 267 insertions(+), 34 deletions(-) diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java index 81a1abbc47..da289bfd54 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; import java.util.List; @@ -28,13 +29,13 @@ public interface OAuth2Service { List getOAuth2Clients(String domainName); - List getSystemOAuth2ClientRegistrations(TenantId tenantId); + OAuth2ClientsParams saveSystemOAuth2ClientsParams(OAuth2ClientsParams oAuth2ClientsParams); - List getTenantOAuth2ClientRegistrations(TenantId tenantId); + OAuth2ClientsParams saveTenantOAuth2ClientsParams(TenantId tenantId, OAuth2ClientsParams oAuth2ClientsParams); - OAuth2ClientRegistration saveSystemOAuth2ClientRegistration(OAuth2ClientRegistration clientRegistration); + OAuth2ClientsParams getSystemOAuth2ClientsParams(TenantId tenantId); - OAuth2ClientRegistration saveTenantOAuth2ClientRegistration(TenantId tenantId, String domainName, OAuth2ClientRegistration clientRegistration); + OAuth2ClientsParams getTenantOAuth2ClientsParams(TenantId tenantId); void deleteDomainOAuth2ClientRegistrationByTenant(TenantId tenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 6ce4ae1599..3dd3c7d42a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -16,24 +16,35 @@ package org.thingsboard.server.dao.oauth2; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.*; import org.thingsboard.server.common.data.id.AdminSettingsId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.oauth2.*; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.settings.AdminSettingsService; +import org.thingsboard.server.dao.tenant.TenantService; -import java.util.Collections; -import java.util.List; -import java.util.UUID; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; @Slf4j @Service @@ -44,65 +55,158 @@ public class OAuth2ServiceImpl implements OAuth2Service { private static final String OAUTH2_CLIENT_REGISTRATIONS_PARAMS = "oauth2ClientRegistrationsParams"; private static final String OAUTH2_CLIENT_REGISTRATIONS_DOMAIN_NAME_PREFIX = "oauth2ClientRegistrationsDomainNamePrefix"; + private static final String ALLOW_OAUTH2_CONFIGURATION = "allowOAuth2Configuration"; + + + private static final String SYSTEM_SETTINGS_OAUTH2_VALUE = "value"; + private static final String OAUTH2_AUTHORIZATION_PATH_TEMPLATE = "/oauth2/authorization/%s"; @Autowired private AdminSettingsService adminSettingsService; - @Override - public List getOAuth2Clients(String domainName) { - return Collections.emptyList(); - } + @Autowired + private AttributesService attributesService; - @Override - public List getSystemOAuth2ClientRegistrations(TenantId tenantId) { - return null; - } + @Autowired + private TenantService tenantService; @Override - public List getTenantOAuth2ClientRegistrations(TenantId tenantId) { - return null; + public List getOAuth2Clients(String domainName) { + return Collections.emptyList(); } @Override - public OAuth2ClientRegistration saveSystemOAuth2ClientRegistration(OAuth2ClientRegistration clientRegistration) { + public OAuth2ClientsParams saveSystemOAuth2ClientsParams(OAuth2ClientsParams oAuth2ClientsParams) { // TODO check by registration ID in entities - AdminSettings clientRegistrationParamsSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); - if (clientRegistrationParamsSettings == null) { - clientRegistrationParamsSettings = new AdminSettings(); - clientRegistrationParamsSettings.setKey(OAUTH2_CLIENT_REGISTRATIONS_PARAMS); - ObjectNode node = mapper.createObjectNode(); - clientRegistrationParamsSettings.setJsonValue(node); + for (OAuth2ClientRegistration clientRegistration : oAuth2ClientsParams.getClientRegistrations()) { + validator.accept(clientRegistration); } + AdminSettings clientRegistrationParamsSettings = new AdminSettings(); + clientRegistrationParamsSettings.setKey(OAUTH2_CLIENT_REGISTRATIONS_PARAMS); + ObjectNode clientRegistrationsNode = mapper.createObjectNode(); + + oAuth2ClientsParams.setDomainName(""); String json; try { - json = mapper.writeValueAsString(clientRegistration); + json = mapper.writeValueAsString(oAuth2ClientsParams); } catch (JsonProcessingException e) { log.error("Unable to convert OAuth2 Client Registration Params to JSON!", e); throw new IncorrectParameterException("Unable to convert OAuth2 Client Registration Params to JSON!"); } - ObjectNode oldClientRegistrations = (ObjectNode) clientRegistrationParamsSettings.getJsonValue(); - oldClientRegistrations.put(clientRegistration.getRegistrationId(), json); + clientRegistrationsNode.put(SYSTEM_SETTINGS_OAUTH2_VALUE, json); + + clientRegistrationParamsSettings.setJsonValue(clientRegistrationsNode); + adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, clientRegistrationParamsSettings); - // TODO ask if that's worth it - return getClientRegistration(clientRegistration.getRegistrationId()); + + return getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); } @Override - public OAuth2ClientRegistration saveTenantOAuth2ClientRegistration(TenantId tenantId, String domainName, OAuth2ClientRegistration clientRegistration) { + public OAuth2ClientsParams saveTenantOAuth2ClientsParams(TenantId tenantId, OAuth2ClientsParams oAuth2ClientsParams) { // TODO ask what if tenant saves config for several different domain names, do we need to check it // TODO check by registration ID in system - return null; + for (OAuth2ClientRegistration clientRegistration : oAuth2ClientsParams.getClientRegistrations()) { + validator.accept(clientRegistration); + } + String clientRegistrationsKey = constructClientRegistrationsKey(oAuth2ClientsParams.getDomainName()); + AdminSettings existentAdminSettingsByKey = adminSettingsService.findAdminSettingsByKey(tenantId, clientRegistrationsKey); + if (StringUtils.isEmpty(oAuth2ClientsParams.getAdminSettingsId())) { + if (existentAdminSettingsByKey == null) { + existentAdminSettingsByKey = saveOAuth2ClientSettings(tenantId, clientRegistrationsKey); + oAuth2ClientsParams.setAdminSettingsId(existentAdminSettingsByKey.getId().getId().toString()); + } else { + log.error("Current domain name [{}] already registered in the system!", oAuth2ClientsParams.getDomainName()); + throw new IncorrectParameterException("Current domain name [" + oAuth2ClientsParams.getDomainName() + "] already registered in the system!"); + } + } else { + AdminSettings existentOAuth2ClientsSettingsById = adminSettingsService.findAdminSettingsById( + tenantId, + new AdminSettingsId(UUID.fromString(oAuth2ClientsParams.getAdminSettingsId())) + ); + + if (existentOAuth2ClientsSettingsById == null) { + log.error("Admin setting ID is already set in login white labeling object, but doesn't exist in the database"); + throw new IllegalStateException("Admin setting ID is already set in login white labeling object, but doesn't exist in the database"); + } + + if (!existentOAuth2ClientsSettingsById.getKey().equals(clientRegistrationsKey)) { + if (existentAdminSettingsByKey == null) { + adminSettingsService.deleteAdminSettingsByKey(tenantId, existentOAuth2ClientsSettingsById.getKey()); + AdminSettings newOAuth2ClientsSettings = saveOAuth2ClientSettings(tenantId, clientRegistrationsKey); + oAuth2ClientsParams.setAdminSettingsId(newOAuth2ClientsSettings.getId().getId().toString()); + } else { + log.error("Current domain name [{}] already registered in the system!", oAuth2ClientsParams.getDomainName()); + throw new IncorrectParameterException("Current domain name [" + oAuth2ClientsParams.getDomainName() + "] already registered in the system!"); + } + } + } + String json; + try { + json = mapper.writeValueAsString(oAuth2ClientsParams); + } catch (JsonProcessingException e) { + log.error("Unable to convert OAuth2 Client Registration Params to JSON!", e); + throw new IncorrectParameterException("Unable to convert OAuth2 Client Registration Params to JSON!"); + } + List attributes = new ArrayList<>(); + long ts = System.currentTimeMillis(); + attributes.add(new BaseAttributeKvEntry(new StringDataEntry(OAUTH2_CLIENT_REGISTRATIONS_PARAMS, json), ts)); + try { + // TODO ask if I need here .get() + attributesService.save(tenantId, tenantId, DataConstants.SERVER_SCOPE, attributes).get(); + } catch (Exception e) { + log.error("Unable to save OAuth2 Client Registration Params to attributes!", e); + throw new IncorrectParameterException("Unable to save OAuth2 Client Registration Params to attributes!"); + } + return getTenantOAuth2ClientsParams(tenantId); } @Override - public void deleteDomainOAuth2ClientRegistrationByTenant(TenantId tenantId) { + public OAuth2ClientsParams getSystemOAuth2ClientsParams(TenantId tenantId) { + AdminSettings oauth2ClientsParamsSettings = adminSettingsService.findAdminSettingsByKey(tenantId, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); + String json = null; + if (oauth2ClientsParamsSettings != null) { + json = oauth2ClientsParamsSettings.getJsonValue().get(SYSTEM_SETTINGS_OAUTH2_VALUE).asText(); + } + return constructOAuth2ClientsParams(json); + } + @Override + public OAuth2ClientsParams getTenantOAuth2ClientsParams(TenantId tenantId) { + ListenableFuture jsonFuture; + if (isOAuth2ClientRegistrationAllowed(tenantId)) { + jsonFuture = getOAuth2ClientsParamsAttribute(tenantId); + } else { + jsonFuture = Futures.immediateFuture(""); + } + try { + return Futures.transform(jsonFuture, this::constructOAuth2ClientsParams, MoreExecutors.directExecutor()).get(); + } catch (InterruptedException | ExecutionException e) { + log.error("Failed to read OAuth2 Clients Params from attributes!", e); + throw new RuntimeException("Failed to read OAuth2 Clients Params from attributes!", e); + } + } + + @Override + public void deleteDomainOAuth2ClientRegistrationByTenant(TenantId tenantId) { + OAuth2ClientsParams params = getTenantOAuth2ClientsParams(tenantId); + if (!StringUtils.isEmpty(params.getDomainName())) { + // TODO don't we need to delete from attributes? + String oauth2ClientsParamsKey = constructClientRegistrationsKey(params.getDomainName()); + adminSettingsService.deleteAdminSettingsByKey(tenantId, oauth2ClientsParamsKey); + } } @Override public boolean isOAuth2ClientRegistrationAllowed(TenantId tenantId) { - return false; + Tenant tenant = tenantService.findTenantById(tenantId); + JsonNode allowOAuth2ConfigurationJsonNode = tenant.getAdditionalInfo() != null ? tenant.getAdditionalInfo().get(ALLOW_OAUTH2_CONFIGURATION) : null; + if (allowOAuth2ConfigurationJsonNode == null) { + return true; + } else { + return allowOAuth2ConfigurationJsonNode.asBoolean(); + } } @Override @@ -110,6 +214,35 @@ public class OAuth2ServiceImpl implements OAuth2Service { return null; } + private ListenableFuture getOAuth2ClientsParamsAttribute(TenantId tenantId) { + ListenableFuture> attributeKvEntriesFuture; + try { + attributeKvEntriesFuture = attributesService.find(tenantId, tenantId, DataConstants.SERVER_SCOPE, + Collections.singletonList(OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); + } catch (Exception e) { + log.error("Unable to read OAuth2 Clients Params from attributes!", e); + throw new IncorrectParameterException("Unable to read OAuth2 Clients Params from attributes!"); + } + return Futures.transform(attributeKvEntriesFuture, attributeKvEntries -> { + if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) { + AttributeKvEntry kvEntry = attributeKvEntries.get(0); + return kvEntry.getValueAsString(); + } else { + return ""; + } + }, MoreExecutors.directExecutor()); + } + + private AdminSettings saveOAuth2ClientSettings(TenantId tenantId, String clientRegistrationsKey) { + AdminSettings oauth2ClientsSettings = new AdminSettings(); + oauth2ClientsSettings.setKey(clientRegistrationsKey); + ObjectNode node = mapper.createObjectNode(); + node.put("entityType", EntityType.TENANT.name()); + node.put("entityId", tenantId.toString()); + oauth2ClientsSettings.setJsonValue(node); + return adminSettingsService.saveAdminSettings(tenantId, oauth2ClientsSettings); + } + private String constructClientRegistrationsKey(String domainName) { String clientRegistrationsKey; if (StringUtils.isEmpty(domainName)) { @@ -119,4 +252,103 @@ public class OAuth2ServiceImpl implements OAuth2Service { } return clientRegistrationsKey; } + + private OAuth2ClientsParams constructOAuth2ClientsParams(String json) { + OAuth2ClientsParams result = null; + if (!StringUtils.isEmpty(json)) { + try { + result = mapper.readValue(json, OAuth2ClientsParams.class); + } catch (IOException e) { + log.error("Unable to read OAuth2 Clients Params from JSON!", e); + throw new IncorrectParameterException("Unable to read OAuth2 Clients Params from JSON!"); + } + } + if (result == null) { + result = new OAuth2ClientsParams(); + } + return result; + } + + private final Consumer validator = clientRegistration -> { + if (StringUtils.isEmpty(clientRegistration.getRegistrationId())) { + throw new DataValidationException("Registration ID should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getClientId())) { + throw new DataValidationException("Client ID should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getClientSecret())) { + throw new DataValidationException("Client secret should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getAuthorizationUri())) { + throw new DataValidationException("Authorization uri should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getTokenUri())) { + throw new DataValidationException("Token uri should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getRedirectUriTemplate())) { + throw new DataValidationException("Redirect uri template should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getScope())) { + throw new DataValidationException("Scope should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getAuthorizationGrantType())) { + throw new DataValidationException("Authorization grant type should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getUserInfoUri())) { + throw new DataValidationException("User info uri should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getUserNameAttributeName())) { + throw new DataValidationException("User name attribute name should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getJwkSetUri())) { + throw new DataValidationException("Jwk set uri should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getClientAuthenticationMethod())) { + throw new DataValidationException("Client authentication method should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getClientName())) { + throw new DataValidationException("Client name should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getLoginButtonLabel())) { + throw new DataValidationException("Login button label should be specified!"); + } + OAuth2MapperConfig mapperConfig = clientRegistration.getMapperConfig(); + if (mapperConfig == null) { + throw new DataValidationException("Mapper config should be specified!"); + } + if (mapperConfig.getType() == null) { + throw new DataValidationException("Mapper config type should be specified!"); + } + if (mapperConfig.getType() == MapperType.BASIC) { + OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasicConfig(); + if (basicConfig == null) { + throw new DataValidationException("Basic config should be specified!"); + } + if (StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) { + throw new DataValidationException("Email attribute key should be specified!"); + } + if (basicConfig.getTenantNameStrategy() == null) { + throw new DataValidationException("Tenant name strategy should be specified!"); + } + if (basicConfig.getTenantNameStrategy() == TenantNameStrategyType.CUSTOM + && StringUtils.isEmpty(basicConfig.getTenantNamePattern())) { + throw new DataValidationException("Tenant name pattern should be specified!"); + } + } + if (mapperConfig.getType() == MapperType.CUSTOM) { + OAuth2CustomMapperConfig customConfig = mapperConfig.getCustomConfig(); + if (customConfig == null) { + throw new DataValidationException("Custom config should be specified!"); + } + if (StringUtils.isEmpty(customConfig.getUrl())) { + throw new DataValidationException("Custom mapper URL should be specified!"); + } + if (StringUtils.isEmpty(customConfig.getUsername())) { + throw new DataValidationException("Custom mapper username should be specified!"); + } + if (StringUtils.isEmpty(customConfig.getPassword())) { + throw new DataValidationException("Custom mapper password should be specified!"); + } + } + }; } From 6a2bc5a9ffcd0a309b87d2bfbd6db48885b3846e Mon Sep 17 00:00:00 2001 From: viktor Date: Tue, 23 Jun 2020 15:03:24 +0300 Subject: [PATCH 025/463] Implemented 'getOAuth2Clients' method --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 59 +++++++++++++++---- 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 3dd3c7d42a..e9d3b76395 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -19,18 +19,17 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.ListUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.*; -import org.thingsboard.server.common.data.id.AdminSettingsId; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; @@ -41,10 +40,13 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.tenant.TenantService; +import javax.annotation.PostConstruct; import java.io.IOException; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; +import java.util.stream.Collectors; @Slf4j @Service @@ -71,9 +73,30 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Autowired private TenantService tenantService; + private final Map clientRegistrationsByRegistrationId = new ConcurrentHashMap<>(); + + @PostConstruct + public void init(){ + + } + + @Override + public OAuth2ClientRegistration getClientRegistration(String registrationId) { + return null; + } + @Override public List getOAuth2Clients(String domainName) { - return Collections.emptyList(); + OAuth2ClientsParams oAuth2ClientsParams = getMergedOAuth2ClientsParams(domainName); + return oAuth2ClientsParams.getClientRegistrations().stream() + .map(clientRegistration -> { + OAuth2ClientInfo client = new OAuth2ClientInfo(); + client.setName(clientRegistration.getLoginButtonLabel()); + client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, clientRegistration.getRegistrationId())); + client.setIcon(clientRegistration.getLoginButtonIcon()); + return client; + }) + .collect(Collectors.toList()); } @Override @@ -209,11 +232,6 @@ public class OAuth2ServiceImpl implements OAuth2Service { } } - @Override - public OAuth2ClientRegistration getClientRegistration(String registrationId) { - return null; - } - private ListenableFuture getOAuth2ClientsParamsAttribute(TenantId tenantId) { ListenableFuture> attributeKvEntriesFuture; try { @@ -269,6 +287,27 @@ public class OAuth2ServiceImpl implements OAuth2Service { return result; } + private OAuth2ClientsParams getMergedOAuth2ClientsParams(String domainName) { + AdminSettings oauth2ClientsSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, constructClientRegistrationsKey(domainName)); + OAuth2ClientsParams result; + if (oauth2ClientsSettings != null) { + String strEntityType = oauth2ClientsSettings.getJsonValue().get("entityType").asText(); + String strEntityId = oauth2ClientsSettings.getJsonValue().get("entityId").asText(); + EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId); + if (!entityId.getEntityType().equals(EntityType.TENANT)) { + log.error("Only tenant can configure OAuth2 for certain domain!"); + throw new IllegalStateException("Only tenant can configure OAuth2 for certain domain!"); + } + TenantId tenantId = (TenantId) entityId; + result = getTenantOAuth2ClientsParams(tenantId); + OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); + result.getClientRegistrations().addAll(systemOAuth2ClientsParams.getClientRegistrations()); + } else { + result = getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); + } + return result; + } + private final Consumer validator = clientRegistration -> { if (StringUtils.isEmpty(clientRegistration.getRegistrationId())) { throw new DataValidationException("Registration ID should be specified!"); From 79cd0c273eda51029749e9f7783cd03763a3a068 Mon Sep 17 00:00:00 2001 From: viktor Date: Tue, 23 Jun 2020 15:39:19 +0300 Subject: [PATCH 026/463] Implemented OAuth2Controller --- .../server/controller/AuthController.java | 14 ---- .../server/controller/OAuth2Controller.java | 73 ++++++++++++++++++- .../service/security/permission/Resource.java | 4 +- .../server/dao/oauth2/OAuth2ServiceImpl.java | 7 +- 4 files changed, 77 insertions(+), 21 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java index 2cf9e5d86b..adffcba3d6 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java @@ -84,9 +84,6 @@ public class AuthController extends BaseController { @Autowired private AuditLogService auditLogService; - @Autowired - private OAuth2Service oauth2Service; - @PreAuthorize("isAuthenticated()") @RequestMapping(value = "/auth/user", method = RequestMethod.GET) public @ResponseBody User getUser() throws ThingsboardException { @@ -336,15 +333,4 @@ public class AuthController extends BaseController { throw handleException(e); } } - - // TODO ask why POST - @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) - @ResponseBody - public List getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { - try { - return oauth2Service.getOAuth2Clients(request.getServerName()); - } catch (Exception e) { - throw handleException(e); - } - } } diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index c0c3034c7b..31bc3e9fff 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -17,12 +17,23 @@ package org.thingsboard.server.controller; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; +import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; @RestController @TbCoreComponent @@ -34,14 +45,68 @@ public class OAuth2Controller extends BaseController { @Autowired private OAuth2Service oauth2Service; - @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") - @RequestMapping(value = "/oauth2/config/{" + REGISTRATION_ID + "}", method = RequestMethod.GET) + // TODO ask why POST + @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) + @ResponseBody + public List getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { + try { + return oauth2Service.getOAuth2Clients(request.getServerName()); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(value = "/oauth2/currentOAuth2Configuration", method = RequestMethod.GET, produces = "application/json") + @ResponseBody + public OAuth2ClientsParams getCurrentOAuth2ClientsParams() throws ThingsboardException { + try { + Authority authority = getCurrentUser().getAuthority(); + checkOAuth2ConfigPermissions(Operation.READ); + OAuth2ClientsParams oAuth2ClientsParams = null; + if (Authority.SYS_ADMIN.equals(authority)) { + oAuth2ClientsParams = oauth2Service.getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); + } else if (Authority.TENANT_ADMIN.equals(authority)) { + oAuth2ClientsParams = oauth2Service.getTenantOAuth2ClientsParams(getCurrentUser().getTenantId()); + } + return oAuth2ClientsParams; + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(value = "/oauth2/oAuth2Configuration", method = RequestMethod.POST) + @ResponseStatus(value = HttpStatus.OK) + public OAuth2ClientsParams saveLoginWhiteLabelParams(@RequestBody OAuth2ClientsParams oAuth2ClientsParams) throws ThingsboardException { + try { + Authority authority = getCurrentUser().getAuthority(); + checkOAuth2ConfigPermissions(Operation.WRITE); + OAuth2ClientsParams savedOAuth2ClientsParams = null; + if (Authority.SYS_ADMIN.equals(authority)) { + savedOAuth2ClientsParams = oauth2Service.saveSystemOAuth2ClientsParams(oAuth2ClientsParams); + } else if (Authority.TENANT_ADMIN.equals(authority)) { + savedOAuth2ClientsParams = oauth2Service.saveTenantOAuth2ClientsParams(getCurrentUser().getTenantId(), oAuth2ClientsParams); + } + return savedOAuth2ClientsParams; + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/oauth2/isOAuth2ConfigurationAllowed", method = RequestMethod.GET) @ResponseBody - public OAuth2ClientRegistration getClientRegistrationById(@PathVariable(REGISTRATION_ID) String registrationId) throws ThingsboardException { + public Boolean isOAuth2ConfigurationAllowed() throws ThingsboardException { try { - return oauth2Service.getClientRegistration(registrationId); + return oauth2Service.isOAuth2ClientRegistrationAllowed(getTenantId()); } catch (Exception e) { throw handleException(e); } } + + + private void checkOAuth2ConfigPermissions(Operation operation) throws ThingsboardException { + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION, operation); + } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java index a66b822fca..dfdea59e05 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java @@ -31,7 +31,9 @@ public enum Resource { RULE_CHAIN(EntityType.RULE_CHAIN), USER(EntityType.USER), WIDGETS_BUNDLE(EntityType.WIDGETS_BUNDLE), - WIDGET_TYPE(EntityType.WIDGET_TYPE); + WIDGET_TYPE(EntityType.WIDGET_TYPE), + OAUTH2_CONFIGURATION(), + ; private final EntityType entityType; diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index e9d3b76395..da783ea297 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -75,14 +75,17 @@ public class OAuth2ServiceImpl implements OAuth2Service { private final Map clientRegistrationsByRegistrationId = new ConcurrentHashMap<>(); + + // TODO add field that invalidates cache in case write to cache fails after successful saving in DB @PostConstruct public void init(){ - + OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); + // TODO get all attributes with key OAUTH2_CLIENT_REGISTRATIONS_PARAMS and put into the map } @Override public OAuth2ClientRegistration getClientRegistration(String registrationId) { - return null; + return clientRegistrationsByRegistrationId.get(registrationId); } @Override From 1d56a1937bf971a1bc5f0c1928b9fafa74e5bd85 Mon Sep 17 00:00:00 2001 From: viktor Date: Tue, 23 Jun 2020 16:21:24 +0300 Subject: [PATCH 027/463] Added license headers to new classes --- .../server/controller/OAuth2Controller.java | 8 ++++---- .../server/dao/oauth2/OAuth2Service.java | 8 ++++---- .../server/common/data/oauth2/MapperType.java | 15 +++++++++++++++ .../data/oauth2/OAuth2BasicMapperConfig.java | 15 +++++++++++++++ .../data/oauth2/OAuth2ClientRegistration.java | 15 +++++++++++++++ .../common/data/oauth2/OAuth2ClientsParams.java | 15 +++++++++++++++ .../data/oauth2/OAuth2CustomMapperConfig.java | 15 +++++++++++++++ .../common/data/oauth2/OAuth2MapperConfig.java | 15 +++++++++++++++ .../data/oauth2/TenantNameStrategyType.java | 15 +++++++++++++++ .../HybridClientRegistrationRepository.java | 15 +++++++++++++++ 10 files changed, 128 insertions(+), 8 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index 31bc3e9fff..18e7a4eacb 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -1,12 +1,12 @@ /** * Copyright © 2016-2020 The Thingsboard Authors - *

+ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

+ * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java index da289bfd54..05e06cc3ae 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java @@ -1,12 +1,12 @@ /** * Copyright © 2016-2020 The Thingsboard Authors - *

+ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

+ * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java index d33563b668..48f0a77a41 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.common.data.oauth2; public enum MapperType { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java index 18bab333fb..3f9f1c6dc2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2BasicMapperConfig.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.common.data.oauth2; import lombok.*; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java index a9fe5ed060..1fc2743e73 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.common.data.oauth2; import lombok.*; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java index 1adcd51f3b..3c0da5a9a0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.common.data.oauth2; import lombok.*; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java index ad401e0aff..8ac72e9d8a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.common.data.oauth2; import lombok.*; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java index 1e45edc4de..e0aaf72ee3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.common.data.oauth2; import lombok.Builder; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/TenantNameStrategyType.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/TenantNameStrategyType.java index e38c7e37dc..91562a883a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/TenantNameStrategyType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/TenantNameStrategyType.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.common.data.oauth2; public enum TenantNameStrategyType { diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java index f04c739ac1..3c9743e4d3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.dao.oauth2; import org.springframework.beans.factory.annotation.Autowired; From e650cb0c243a4046c2031f56cff9d4835a8fb6d7 Mon Sep 17 00:00:00 2001 From: viktor Date: Tue, 23 Jun 2020 19:20:00 +0300 Subject: [PATCH 028/463] Added reading attributes by attribute_key --- .../org/thingsboard/server/controller/AuthController.java | 2 -- .../server/dao/attributes/AttributesService.java | 2 ++ .../thingsboard/server/dao/attributes/AttributesDao.java | 2 ++ .../server/dao/attributes/BaseAttributesService.java | 6 ++++++ .../server/dao/sql/attributes/AttributeKvRepository.java | 2 ++ .../server/dao/sql/attributes/JpaAttributeDao.java | 6 ++++++ 6 files changed, 18 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java index adffcba3d6..798b71e114 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java @@ -38,10 +38,8 @@ import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.dao.audit.AuditLogService; -import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails; diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java index 3af4737f06..cb7cbd09c6 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java @@ -35,6 +35,8 @@ public interface AttributesService { ListenableFuture> findAll(TenantId tenantId, EntityId entityId, String scope); + ListenableFuture> findAllByAttributeKey(String attributeKey); + ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes); ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, String scope, List attributeKeys); diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java index 8ec43f49a5..6101a70b54 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java @@ -35,6 +35,8 @@ public interface AttributesDao { ListenableFuture> findAll(TenantId tenantId, EntityId entityId, String attributeType); + ListenableFuture> findAllByAttributeKey(String attributeKey); + ListenableFuture save(TenantId tenantId, EntityId entityId, String attributeType, AttributeKvEntry attribute); ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, String attributeType, List keys); diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java index a6924960ca..fbb44ac8c6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java @@ -59,6 +59,12 @@ public class BaseAttributesService implements AttributesService { return attributesDao.findAll(tenantId, entityId, scope); } + @Override + public ListenableFuture> findAllByAttributeKey(String attributeKey) { + Validator.validateString(attributeKey, "Incorrect attribute key " + attributeKey); + return attributesDao.findAllByAttributeKey(attributeKey); + } + @Override public ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes) { validate(entityId, scope); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java index 0bd667b790..37b30607ab 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java @@ -47,5 +47,7 @@ public interface AttributeKvRepository extends CrudRepository findAllByAttributeKey(String attributeKey); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java index c14e2dd7d0..420ab0a42d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java @@ -119,6 +119,12 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl attributeType)))); } + @Override + public ListenableFuture> findAllByAttributeKey(String attributeKey) { + return Futures.immediateFuture( + DaoUtil.convertDataList(attributeKvRepository.findAllByAttributeKey(attributeKey))); + } + @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, String attributeType, AttributeKvEntry attribute) { AttributeKvEntity entity = new AttributeKvEntity(); From db617b404e04c45f5567943e345ba71b2b365a10 Mon Sep 17 00:00:00 2001 From: viktor Date: Tue, 23 Jun 2020 19:20:30 +0300 Subject: [PATCH 029/463] Configured permissions for oauth feature --- .../security/permission/SysAdminPermissions.java | 1 + .../security/permission/TenantAdminPermissions.java | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java index cd79a29f0b..869217e4cf 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java @@ -39,6 +39,7 @@ public class SysAdminPermissions extends AbstractPermissions { put(Resource.USER, userPermissionChecker); put(Resource.WIDGETS_BUNDLE, systemEntityPermissionChecker); put(Resource.WIDGET_TYPE, systemEntityPermissionChecker); + put(Resource.OAUTH2_CONFIGURATION, PermissionChecker.allowAllPermissionChecker); } private static final PermissionChecker systemEntityPermissionChecker = new PermissionChecker() { diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java index 794fb72398..703c238c64 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.service.security.permission; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.User; @@ -22,6 +23,7 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.service.security.model.SecurityUser; import java.util.HashMap; @@ -29,6 +31,9 @@ import java.util.HashMap; @Component(value="tenantAdminPermissions") public class TenantAdminPermissions extends AbstractPermissions { + @Autowired + private OAuth2Service oAuth2Service; + public TenantAdminPermissions() { super(); put(Resource.ALARM, tenantEntityPermissionChecker); @@ -42,6 +47,7 @@ public class TenantAdminPermissions extends AbstractPermissions { put(Resource.USER, userPermissionChecker); put(Resource.WIDGETS_BUNDLE, widgetsPermissionChecker); put(Resource.WIDGET_TYPE, widgetsPermissionChecker); + put(Resource.OAUTH2_CONFIGURATION, tenantOAuth2ConfigPermissionChecker); } public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() { @@ -101,4 +107,11 @@ public class TenantAdminPermissions extends AbstractPermissions { } }; + + private final PermissionChecker tenantOAuth2ConfigPermissionChecker = new PermissionChecker() { + @Override + public boolean hasPermission(SecurityUser user, Operation operation) { + return oAuth2Service.isOAuth2ClientRegistrationAllowed(user.getTenantId()); + } + }; } From b3050b9aa55145208099ae84515738dcc953e91e Mon Sep 17 00:00:00 2001 From: viktor Date: Tue, 23 Jun 2020 19:21:09 +0300 Subject: [PATCH 030/463] Init cache on start-up --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 73 ++++++++++++++++--- 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index da783ea297..32221b2b73 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -26,6 +26,7 @@ import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.ListUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.*; @@ -47,6 +48,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; import java.util.stream.Collectors; +import java.util.stream.Stream; @Slf4j @Service @@ -64,6 +66,9 @@ public class OAuth2ServiceImpl implements OAuth2Service { private static final String OAUTH2_AUTHORIZATION_PATH_TEMPLATE = "/oauth2/authorization/%s"; + @Autowired + private Environment environment; + @Autowired private AdminSettingsService adminSettingsService; @@ -76,11 +81,25 @@ public class OAuth2ServiceImpl implements OAuth2Service { private final Map clientRegistrationsByRegistrationId = new ConcurrentHashMap<>(); + private boolean isInstall() { + return environment.acceptsProfiles("install"); + } + // TODO add field that invalidates cache in case write to cache fails after successful saving in DB @PostConstruct - public void init(){ + public void init() { + if (isInstall()) return; + OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); - // TODO get all attributes with key OAUTH2_CLIENT_REGISTRATIONS_PARAMS and put into the map + OAuth2ClientsParams tenantsOAuth2ClientsParams = getAllOAuth2ClientsParams(); + + Stream.concat( + systemOAuth2ClientsParams.getClientRegistrations().stream(), + tenantsOAuth2ClientsParams.getClientRegistrations().stream() + ) + .forEach(clientRegistration -> { + clientRegistrationsByRegistrationId.put(clientRegistration.getRegistrationId(), clientRegistration); + }); } @Override @@ -91,15 +110,18 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Override public List getOAuth2Clients(String domainName) { OAuth2ClientsParams oAuth2ClientsParams = getMergedOAuth2ClientsParams(domainName); - return oAuth2ClientsParams.getClientRegistrations().stream() - .map(clientRegistration -> { - OAuth2ClientInfo client = new OAuth2ClientInfo(); - client.setName(clientRegistration.getLoginButtonLabel()); - client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, clientRegistration.getRegistrationId())); - client.setIcon(clientRegistration.getLoginButtonIcon()); - return client; - }) - .collect(Collectors.toList()); + return oAuth2ClientsParams != null && oAuth2ClientsParams.getClientRegistrations() != null ? + oAuth2ClientsParams.getClientRegistrations().stream() + .map(clientRegistration -> { + OAuth2ClientInfo client = new OAuth2ClientInfo(); + client.setName(clientRegistration.getLoginButtonLabel()); + client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, clientRegistration.getRegistrationId())); + client.setIcon(clientRegistration.getLoginButtonIcon()); + return client; + }) + .collect(Collectors.toList()) + : Collections.emptyList() + ; } @Override @@ -168,6 +190,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { } } } + // TODO refactor String json; try { json = mapper.writeValueAsString(oAuth2ClientsParams); @@ -214,6 +237,16 @@ public class OAuth2ServiceImpl implements OAuth2Service { } } + private OAuth2ClientsParams getAllOAuth2ClientsParams() { + ListenableFuture jsonFuture = getOAuth2ClientsParamsAttribute(); + try { + return Futures.transform(jsonFuture, this::constructOAuth2ClientsParams, MoreExecutors.directExecutor()).get(); + } catch (InterruptedException | ExecutionException e) { + log.error("Failed to read OAuth2 Clients Params from attributes!", e); + throw new RuntimeException("Failed to read OAuth2 Clients Params from attributes!", e); + } + } + @Override public void deleteDomainOAuth2ClientRegistrationByTenant(TenantId tenantId) { OAuth2ClientsParams params = getTenantOAuth2ClientsParams(tenantId); @@ -254,6 +287,24 @@ public class OAuth2ServiceImpl implements OAuth2Service { }, MoreExecutors.directExecutor()); } + private ListenableFuture getOAuth2ClientsParamsAttribute() { + ListenableFuture> attributeKvEntriesFuture; + try { + attributeKvEntriesFuture = attributesService.findAllByAttributeKey(OAUTH2_CLIENT_REGISTRATIONS_PARAMS); + } catch (Exception e) { + log.error("Unable to read OAuth2 Clients Params from attributes!", e); + throw new IncorrectParameterException("Unable to read OAuth2 Clients Params from attributes!"); + } + return Futures.transform(attributeKvEntriesFuture, attributeKvEntries -> { + if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) { + AttributeKvEntry kvEntry = attributeKvEntries.get(0); + return kvEntry.getValueAsString(); + } else { + return ""; + } + }, MoreExecutors.directExecutor()); + } + private AdminSettings saveOAuth2ClientSettings(TenantId tenantId, String clientRegistrationsKey) { AdminSettings oauth2ClientsSettings = new AdminSettings(); oauth2ClientsSettings.setKey(clientRegistrationsKey); From ba0f8a8ea5e507bd840f11b4d3183e93c3c32a63 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Wed, 24 Jun 2020 12:41:19 +0300 Subject: [PATCH 031/463] Updated loading AttributeKvEntity by attribute_key --- .../dao/attributes/AttributesService.java | 3 +- .../data/kv/BaseEntityAttributeKvEntry.java | 44 +++++++++++++++++++ .../data/kv/EntityAttributeKvEntry.java | 24 ++++++++++ .../server/dao/attributes/AttributesDao.java | 3 +- .../dao/attributes/BaseAttributesService.java | 3 +- .../dao/model/sql/AttributeKvEntity.java | 33 +++++++++----- .../sql/attributes/AttributeKvRepository.java | 6 ++- .../dao/sql/attributes/JpaAttributeDao.java | 13 ++++-- 8 files changed, 109 insertions(+), 20 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseEntityAttributeKvEntry.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/kv/EntityAttributeKvEntry.java diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java index cb7cbd09c6..75122aba57 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.EntityAttributeKvEntry; import java.util.Collection; import java.util.List; @@ -35,7 +36,7 @@ public interface AttributesService { ListenableFuture> findAll(TenantId tenantId, EntityId entityId, String scope); - ListenableFuture> findAllByAttributeKey(String attributeKey); + ListenableFuture> findAllByAttributeKey(String attributeKey); ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseEntityAttributeKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseEntityAttributeKvEntry.java new file mode 100644 index 0000000000..aac91f5613 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseEntityAttributeKvEntry.java @@ -0,0 +1,44 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.kv; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.thingsboard.server.common.data.EntityType; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class BaseEntityAttributeKvEntry extends BaseAttributeKvEntry implements EntityAttributeKvEntry { + + private final EntityType entityType; + private final String entityId; + + public BaseEntityAttributeKvEntry(EntityType entityType, String entityId, long lastUpdateTs, KvEntry kv) { + super(kv, lastUpdateTs); + this.entityType = entityType; + this.entityId = entityId; + } + + @Override + public EntityType getEntityType() { + return entityType; + } + + @Override + public String getEntityId() { + return entityId; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/EntityAttributeKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/EntityAttributeKvEntry.java new file mode 100644 index 0000000000..b62576b8e0 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/EntityAttributeKvEntry.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.kv; + +import org.thingsboard.server.common.data.EntityType; + +public interface EntityAttributeKvEntry extends AttributeKvEntry { + + EntityType getEntityType(); + String getEntityId(); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java index 6101a70b54..85f6b2de44 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.EntityAttributeKvEntry; import java.util.Collection; import java.util.List; @@ -35,7 +36,7 @@ public interface AttributesDao { ListenableFuture> findAll(TenantId tenantId, EntityId entityId, String attributeType); - ListenableFuture> findAllByAttributeKey(String attributeKey); + ListenableFuture> findAllByAttributeKey(String attributeKey); ListenableFuture save(TenantId tenantId, EntityId entityId, String attributeType, AttributeKvEntry attribute); diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java index fbb44ac8c6..9956da347d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java @@ -23,6 +23,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.EntityAttributeKvEntry; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.Validator; @@ -60,7 +61,7 @@ public class BaseAttributesService implements AttributesService { } @Override - public ListenableFuture> findAllByAttributeKey(String attributeKey) { + public ListenableFuture> findAllByAttributeKey(String attributeKey) { Validator.validateString(attributeKey, "Incorrect attribute key " + attributeKey); return attributesDao.findAllByAttributeKey(attributeKey); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java index f0de269cea..7787f21830 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java @@ -16,20 +16,10 @@ package org.thingsboard.server.dao.model.sql; import lombok.Data; -import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; -import org.thingsboard.server.common.data.kv.BooleanDataEntry; -import org.thingsboard.server.common.data.kv.DoubleDataEntry; -import org.thingsboard.server.common.data.kv.JsonDataEntry; -import org.thingsboard.server.common.data.kv.KvEntry; -import org.thingsboard.server.common.data.kv.LongDataEntry; -import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.kv.*; import org.thingsboard.server.dao.model.ToData; -import javax.persistence.Column; -import javax.persistence.EmbeddedId; -import javax.persistence.Entity; -import javax.persistence.Table; +import javax.persistence.*; import java.io.Serializable; import static org.thingsboard.server.dao.model.ModelConstants.BOOLEAN_VALUE_COLUMN; @@ -42,6 +32,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.STRING_VALUE_COLUM @Data @Entity @Table(name = "attribute_kv") +// TODO maybe move ToData to local field as well (or implement ToData differently) public class AttributeKvEntity implements ToData, Serializable { @EmbeddedId @@ -82,4 +73,22 @@ public class AttributeKvEntity implements ToData, Serializable return new BaseAttributeKvEntry(kvEntry, lastUpdateTs); } + + @Transient + public final ToData toEntityAttributeKvEntry = () -> { + KvEntry kvEntry = null; + if (strValue != null) { + kvEntry = new StringDataEntry(id.getAttributeKey(), strValue); + } else if (booleanValue != null) { + kvEntry = new BooleanDataEntry(id.getAttributeKey(), booleanValue); + } else if (doubleValue != null) { + kvEntry = new DoubleDataEntry(id.getAttributeKey(), doubleValue); + } else if (longValue != null) { + kvEntry = new LongDataEntry(id.getAttributeKey(), longValue); + } else if (jsonValue != null) { + kvEntry = new JsonDataEntry(id.getAttributeKey(), jsonValue); + } + + return new BaseEntityAttributeKvEntry(id.getEntityType(), id.getEntityId(), lastUpdateTs, kvEntry); + }; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java index 37b30607ab..86616d6d8f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java @@ -37,6 +37,10 @@ public interface AttributeKvRepository extends CrudRepository findAllByAttributeKey(@Param("attributeKey") String attributeKey); + @Transactional @Modifying @Query("DELETE FROM AttributeKvEntity a WHERE a.id.entityType = :entityType " + @@ -47,7 +51,5 @@ public interface AttributeKvRepository extends CrudRepository findAllByAttributeKey(String attributeKey); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java index 420ab0a42d..80119f23bf 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java @@ -25,9 +25,10 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.*; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.attributes.AttributesDao; +import org.thingsboard.server.dao.model.ToData; import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey; import org.thingsboard.server.dao.model.sql.AttributeKvEntity; import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; @@ -120,9 +121,15 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl } @Override - public ListenableFuture> findAllByAttributeKey(String attributeKey) { + public ListenableFuture> findAllByAttributeKey(String attributeKey) { return Futures.immediateFuture( - DaoUtil.convertDataList(attributeKvRepository.findAllByAttributeKey(attributeKey))); + DaoUtil.convertDataList( + attributeKvRepository.findAllByAttributeKey(attributeKey).stream() + .map(attributeKvEntity -> attributeKvEntity.toEntityAttributeKvEntry) + .collect(Collectors.toList()) + ) + + ); } @Override From 7bedf10bd22f1827a23b3b1692679d74ec6d5db1 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Wed, 24 Jun 2020 12:41:58 +0300 Subject: [PATCH 032/463] Load all OAuth2 config in cache on startup --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 92 +++++++++++-------- 1 file changed, 52 insertions(+), 40 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 32221b2b73..e06fe523ee 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -19,21 +19,17 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections.ListUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.*; import org.thingsboard.server.common.data.id.*; -import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; -import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.kv.*; import org.thingsboard.server.common.data.oauth2.*; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.exception.DataValidationException; @@ -47,6 +43,7 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -78,7 +75,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Autowired private TenantService tenantService; - private final Map clientRegistrationsByRegistrationId = new ConcurrentHashMap<>(); + private final Map clientsParams = new ConcurrentHashMap<>(); private boolean isInstall() { @@ -90,21 +87,18 @@ public class OAuth2ServiceImpl implements OAuth2Service { public void init() { if (isInstall()) return; - OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); - OAuth2ClientsParams tenantsOAuth2ClientsParams = getAllOAuth2ClientsParams(); - - Stream.concat( - systemOAuth2ClientsParams.getClientRegistrations().stream(), - tenantsOAuth2ClientsParams.getClientRegistrations().stream() - ) - .forEach(clientRegistration -> { - clientRegistrationsByRegistrationId.put(clientRegistration.getRegistrationId(), clientRegistration); - }); + Map allOAuth2ClientsParams = getAllOAuth2ClientsParams(); + + allOAuth2ClientsParams.forEach(clientsParams::put); } @Override public OAuth2ClientRegistration getClientRegistration(String registrationId) { - return clientRegistrationsByRegistrationId.get(registrationId); + return clientsParams.values().stream() + .flatMap(oAuth2ClientsParams -> oAuth2ClientsParams.getClientRegistrations().stream()) + .filter(clientRegistration -> registrationId.equals(clientRegistration.getRegistrationId())) + .findFirst() + .orElse(null); } @Override @@ -112,24 +106,26 @@ public class OAuth2ServiceImpl implements OAuth2Service { OAuth2ClientsParams oAuth2ClientsParams = getMergedOAuth2ClientsParams(domainName); return oAuth2ClientsParams != null && oAuth2ClientsParams.getClientRegistrations() != null ? oAuth2ClientsParams.getClientRegistrations().stream() - .map(clientRegistration -> { - OAuth2ClientInfo client = new OAuth2ClientInfo(); - client.setName(clientRegistration.getLoginButtonLabel()); - client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, clientRegistration.getRegistrationId())); - client.setIcon(clientRegistration.getLoginButtonIcon()); - return client; - }) + .map(this::toClientInfo) .collect(Collectors.toList()) : Collections.emptyList() ; } + private OAuth2ClientInfo toClientInfo(OAuth2ClientRegistration clientRegistration) { + OAuth2ClientInfo client = new OAuth2ClientInfo(); + client.setName(clientRegistration.getLoginButtonLabel()); + client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, clientRegistration.getRegistrationId())); + client.setIcon(clientRegistration.getLoginButtonIcon()); + return client; + } + @Override public OAuth2ClientsParams saveSystemOAuth2ClientsParams(OAuth2ClientsParams oAuth2ClientsParams) { // TODO check by registration ID in entities - for (OAuth2ClientRegistration clientRegistration : oAuth2ClientsParams.getClientRegistrations()) { - validator.accept(clientRegistration); - } + validate(oAuth2ClientsParams); + + AdminSettings clientRegistrationParamsSettings = new AdminSettings(); clientRegistrationParamsSettings.setKey(OAUTH2_CLIENT_REGISTRATIONS_PARAMS); ObjectNode clientRegistrationsNode = mapper.createObjectNode(); @@ -155,9 +151,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { public OAuth2ClientsParams saveTenantOAuth2ClientsParams(TenantId tenantId, OAuth2ClientsParams oAuth2ClientsParams) { // TODO ask what if tenant saves config for several different domain names, do we need to check it // TODO check by registration ID in system - for (OAuth2ClientRegistration clientRegistration : oAuth2ClientsParams.getClientRegistrations()) { - validator.accept(clientRegistration); - } + validate(oAuth2ClientsParams); String clientRegistrationsKey = constructClientRegistrationsKey(oAuth2ClientsParams.getDomainName()); AdminSettings existentAdminSettingsByKey = adminSettingsService.findAdminSettingsByKey(tenantId, clientRegistrationsKey); if (StringUtils.isEmpty(oAuth2ClientsParams.getAdminSettingsId())) { @@ -211,6 +205,12 @@ public class OAuth2ServiceImpl implements OAuth2Service { return getTenantOAuth2ClientsParams(tenantId); } + private void validate(OAuth2ClientsParams oAuth2ClientsParams) { + for (OAuth2ClientRegistration clientRegistration : oAuth2ClientsParams.getClientRegistrations()) { + validator.accept(clientRegistration); + } + } + @Override public OAuth2ClientsParams getSystemOAuth2ClientsParams(TenantId tenantId) { AdminSettings oauth2ClientsParamsSettings = adminSettingsService.findAdminSettingsByKey(tenantId, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); @@ -237,10 +237,22 @@ public class OAuth2ServiceImpl implements OAuth2Service { } } - private OAuth2ClientsParams getAllOAuth2ClientsParams() { - ListenableFuture jsonFuture = getOAuth2ClientsParamsAttribute(); + private Map getAllOAuth2ClientsParams() { + OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); + ListenableFuture> jsonFuture = getAllOAuth2ClientsParamsAttribute(); try { - return Futures.transform(jsonFuture, this::constructOAuth2ClientsParams, MoreExecutors.directExecutor()).get(); + return Futures.transform(jsonFuture, + clientsParamsByKvEntryKey -> { + Map tenantClientParams = clientsParamsByKvEntryKey.entrySet().stream() + .collect(Collectors.toMap( + entry -> new TenantId(UUIDConverter.fromString(entry.getKey())), + entry -> constructOAuth2ClientsParams(entry.getValue()) + )); + tenantClientParams.put(TenantId.SYS_TENANT_ID, systemOAuth2ClientsParams); + return tenantClientParams; + }, + MoreExecutors.directExecutor() + ).get(); } catch (InterruptedException | ExecutionException e) { log.error("Failed to read OAuth2 Clients Params from attributes!", e); throw new RuntimeException("Failed to read OAuth2 Clients Params from attributes!", e); @@ -287,20 +299,20 @@ public class OAuth2ServiceImpl implements OAuth2Service { }, MoreExecutors.directExecutor()); } - private ListenableFuture getOAuth2ClientsParamsAttribute() { - ListenableFuture> attributeKvEntriesFuture; + private ListenableFuture> getAllOAuth2ClientsParamsAttribute() { + ListenableFuture> entityAttributeKvEntriesFuture; try { - attributeKvEntriesFuture = attributesService.findAllByAttributeKey(OAUTH2_CLIENT_REGISTRATIONS_PARAMS); + entityAttributeKvEntriesFuture = attributesService.findAllByAttributeKey(OAUTH2_CLIENT_REGISTRATIONS_PARAMS); } catch (Exception e) { log.error("Unable to read OAuth2 Clients Params from attributes!", e); throw new IncorrectParameterException("Unable to read OAuth2 Clients Params from attributes!"); } - return Futures.transform(attributeKvEntriesFuture, attributeKvEntries -> { + return Futures.transform(entityAttributeKvEntriesFuture, attributeKvEntries -> { if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) { - AttributeKvEntry kvEntry = attributeKvEntries.get(0); - return kvEntry.getValueAsString(); + return attributeKvEntries.stream() + .collect(Collectors.toMap(EntityAttributeKvEntry::getEntityId, EntityAttributeKvEntry::getValueAsString)); } else { - return ""; + return Collections.emptyMap(); } }, MoreExecutors.directExecutor()); } From 8e1e1ec3eb1bd1952aa2a84028ad68c111ea550d Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Wed, 24 Jun 2020 12:49:53 +0300 Subject: [PATCH 033/463] Get OAuth2Params from cache (not DB) --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 39 +------------------ 1 file changed, 2 insertions(+), 37 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index e06fe523ee..b800426b9d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -213,28 +213,12 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Override public OAuth2ClientsParams getSystemOAuth2ClientsParams(TenantId tenantId) { - AdminSettings oauth2ClientsParamsSettings = adminSettingsService.findAdminSettingsByKey(tenantId, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); - String json = null; - if (oauth2ClientsParamsSettings != null) { - json = oauth2ClientsParamsSettings.getJsonValue().get(SYSTEM_SETTINGS_OAUTH2_VALUE).asText(); - } - return constructOAuth2ClientsParams(json); + return clientsParams.get(tenantId); } @Override public OAuth2ClientsParams getTenantOAuth2ClientsParams(TenantId tenantId) { - ListenableFuture jsonFuture; - if (isOAuth2ClientRegistrationAllowed(tenantId)) { - jsonFuture = getOAuth2ClientsParamsAttribute(tenantId); - } else { - jsonFuture = Futures.immediateFuture(""); - } - try { - return Futures.transform(jsonFuture, this::constructOAuth2ClientsParams, MoreExecutors.directExecutor()).get(); - } catch (InterruptedException | ExecutionException e) { - log.error("Failed to read OAuth2 Clients Params from attributes!", e); - throw new RuntimeException("Failed to read OAuth2 Clients Params from attributes!", e); - } + return clientsParams.get(tenantId); } private Map getAllOAuth2ClientsParams() { @@ -280,25 +264,6 @@ public class OAuth2ServiceImpl implements OAuth2Service { } } - private ListenableFuture getOAuth2ClientsParamsAttribute(TenantId tenantId) { - ListenableFuture> attributeKvEntriesFuture; - try { - attributeKvEntriesFuture = attributesService.find(tenantId, tenantId, DataConstants.SERVER_SCOPE, - Collections.singletonList(OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); - } catch (Exception e) { - log.error("Unable to read OAuth2 Clients Params from attributes!", e); - throw new IncorrectParameterException("Unable to read OAuth2 Clients Params from attributes!"); - } - return Futures.transform(attributeKvEntriesFuture, attributeKvEntries -> { - if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) { - AttributeKvEntry kvEntry = attributeKvEntries.get(0); - return kvEntry.getValueAsString(); - } else { - return ""; - } - }, MoreExecutors.directExecutor()); - } - private ListenableFuture> getAllOAuth2ClientsParamsAttribute() { ListenableFuture> entityAttributeKvEntriesFuture; try { From 2d42d4e3fd538bd59a64c1d17d690834cb67b768 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Wed, 24 Jun 2020 13:21:01 +0300 Subject: [PATCH 034/463] Added saving SystemOAuth2 config in cache --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index b800426b9d..2b8e5d88d2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -42,6 +42,7 @@ import java.io.IOException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -75,6 +76,8 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Autowired private TenantService tenantService; + private final ReentrantLock lock = new ReentrantLock(); + private final Map clientsParams = new ConcurrentHashMap<>(); @@ -122,15 +125,43 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Override public OAuth2ClientsParams saveSystemOAuth2ClientsParams(OAuth2ClientsParams oAuth2ClientsParams) { - // TODO check by registration ID in entities validate(oAuth2ClientsParams); + validateUniqueRegistrationId(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); + lock.lock(); + try { + validateUniqueRegistrationId(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); + AdminSettings clientRegistrationParamsSettings = createSystemOAuth2Settings(oAuth2ClientsParams); + adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, clientRegistrationParamsSettings); + clientsParams.put(TenantId.SYS_TENANT_ID, oAuth2ClientsParams); + } finally { + lock.unlock(); + } + + return getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); + } + private void validateUniqueRegistrationId(OAuth2ClientsParams inputOAuth2ClientsParams, TenantId tenantId) { + inputOAuth2ClientsParams.getClientRegistrations().stream() + .map(OAuth2ClientRegistration::getRegistrationId) + .forEach(registrationId -> { + clientsParams.forEach((paramsTenantId, oAuth2ClientsParams) -> { + boolean registrationExists = oAuth2ClientsParams.getClientRegistrations().stream() + .map(OAuth2ClientRegistration::getRegistrationId) + .anyMatch(registrationId::equals); + if (registrationExists && !tenantId.equals(paramsTenantId)) { + log.error("Current registrationId [{}] already registered in the system!", registrationId); + throw new IncorrectParameterException("Current registrationId [" + registrationId + "] already registered in the system!"); + } + }); + }); + } + + private AdminSettings createSystemOAuth2Settings(OAuth2ClientsParams oAuth2ClientsParams) { AdminSettings clientRegistrationParamsSettings = new AdminSettings(); clientRegistrationParamsSettings.setKey(OAUTH2_CLIENT_REGISTRATIONS_PARAMS); ObjectNode clientRegistrationsNode = mapper.createObjectNode(); - oAuth2ClientsParams.setDomainName(""); String json; try { json = mapper.writeValueAsString(oAuth2ClientsParams); @@ -139,12 +170,9 @@ public class OAuth2ServiceImpl implements OAuth2Service { throw new IncorrectParameterException("Unable to convert OAuth2 Client Registration Params to JSON!"); } clientRegistrationsNode.put(SYSTEM_SETTINGS_OAUTH2_VALUE, json); - clientRegistrationParamsSettings.setJsonValue(clientRegistrationsNode); - adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, clientRegistrationParamsSettings); - - return getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); + return clientRegistrationParamsSettings; } @Override From 5f1de39fddbb5cc341e91917046fe3f76e93895f Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Wed, 24 Jun 2020 14:59:30 +0300 Subject: [PATCH 035/463] Refactored saving system and tenant OAuth2 config --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 179 ++++++++++-------- 1 file changed, 97 insertions(+), 82 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 2b8e5d88d2..6228f0d8d8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -44,9 +44,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; -import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; @Slf4j @Service @@ -131,7 +129,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { lock.lock(); try { validateUniqueRegistrationId(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); - AdminSettings clientRegistrationParamsSettings = createSystemOAuth2Settings(oAuth2ClientsParams); + AdminSettings clientRegistrationParamsSettings = createSystemAdminSettings(oAuth2ClientsParams); adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, clientRegistrationParamsSettings); clientsParams.put(TenantId.SYS_TENANT_ID, oAuth2ClientsParams); } finally { @@ -141,59 +139,50 @@ public class OAuth2ServiceImpl implements OAuth2Service { return getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); } - private void validateUniqueRegistrationId(OAuth2ClientsParams inputOAuth2ClientsParams, TenantId tenantId) { - inputOAuth2ClientsParams.getClientRegistrations().stream() - .map(OAuth2ClientRegistration::getRegistrationId) - .forEach(registrationId -> { - clientsParams.forEach((paramsTenantId, oAuth2ClientsParams) -> { - boolean registrationExists = oAuth2ClientsParams.getClientRegistrations().stream() - .map(OAuth2ClientRegistration::getRegistrationId) - .anyMatch(registrationId::equals); - if (registrationExists && !tenantId.equals(paramsTenantId)) { - log.error("Current registrationId [{}] already registered in the system!", registrationId); - throw new IncorrectParameterException("Current registrationId [" + registrationId + "] already registered in the system!"); - } - }); - }); - } + @Override + public OAuth2ClientsParams saveTenantOAuth2ClientsParams(TenantId tenantId, OAuth2ClientsParams oAuth2ClientsParams) { + // TODO what if tenant saves config for several different domain names, do we need to check it + validate(oAuth2ClientsParams); + // TODO check by registration ID in system - private AdminSettings createSystemOAuth2Settings(OAuth2ClientsParams oAuth2ClientsParams) { - AdminSettings clientRegistrationParamsSettings = new AdminSettings(); - clientRegistrationParamsSettings.setKey(OAUTH2_CLIENT_REGISTRATIONS_PARAMS); - ObjectNode clientRegistrationsNode = mapper.createObjectNode(); + String adminSettingsId = processTenantAdminSettings(tenantId, oAuth2ClientsParams.getDomainName(), oAuth2ClientsParams.getAdminSettingsId()); + oAuth2ClientsParams.setAdminSettingsId(adminSettingsId); - String json; + List attributes = createOAuth2ClientsParamsAttributes(oAuth2ClientsParams); try { - json = mapper.writeValueAsString(oAuth2ClientsParams); - } catch (JsonProcessingException e) { - log.error("Unable to convert OAuth2 Client Registration Params to JSON!", e); - throw new IncorrectParameterException("Unable to convert OAuth2 Client Registration Params to JSON!"); + // TODO ask if I need .get() here + attributesService.save(tenantId, tenantId, DataConstants.SERVER_SCOPE, attributes).get(); + } catch (Exception e) { + log.error("Unable to save OAuth2 Client Registration Params to attributes!", e); + throw new IncorrectParameterException("Unable to save OAuth2 Client Registration Params to attributes!"); } - clientRegistrationsNode.put(SYSTEM_SETTINGS_OAUTH2_VALUE, json); - clientRegistrationParamsSettings.setJsonValue(clientRegistrationsNode); + return getTenantOAuth2ClientsParams(tenantId); + } - return clientRegistrationParamsSettings; + private List createOAuth2ClientsParamsAttributes(OAuth2ClientsParams oAuth2ClientsParams) { + String json = toJson(oAuth2ClientsParams); + List attributes = new ArrayList<>(); + long ts = System.currentTimeMillis(); + attributes.add(new BaseAttributeKvEntry(new StringDataEntry(OAUTH2_CLIENT_REGISTRATIONS_PARAMS, json), ts)); + return attributes; } - @Override - public OAuth2ClientsParams saveTenantOAuth2ClientsParams(TenantId tenantId, OAuth2ClientsParams oAuth2ClientsParams) { - // TODO ask what if tenant saves config for several different domain names, do we need to check it - // TODO check by registration ID in system - validate(oAuth2ClientsParams); - String clientRegistrationsKey = constructClientRegistrationsKey(oAuth2ClientsParams.getDomainName()); - AdminSettings existentAdminSettingsByKey = adminSettingsService.findAdminSettingsByKey(tenantId, clientRegistrationsKey); - if (StringUtils.isEmpty(oAuth2ClientsParams.getAdminSettingsId())) { + private String processTenantAdminSettings(TenantId tenantId, String domainName, String prevAdminSettingsId) { + String selectedDomainSettingsKey = constructAdminSettingsDomainKey(domainName); + AdminSettings existentAdminSettingsByKey = adminSettingsService.findAdminSettingsByKey(tenantId, selectedDomainSettingsKey); + if (StringUtils.isEmpty(prevAdminSettingsId)) { if (existentAdminSettingsByKey == null) { - existentAdminSettingsByKey = saveOAuth2ClientSettings(tenantId, clientRegistrationsKey); - oAuth2ClientsParams.setAdminSettingsId(existentAdminSettingsByKey.getId().getId().toString()); + AdminSettings tenantAdminSettings = createTenantAdminSettings(tenantId, selectedDomainSettingsKey); + existentAdminSettingsByKey = adminSettingsService.saveAdminSettings(tenantId, tenantAdminSettings); + return existentAdminSettingsByKey.getId().getId().toString(); } else { - log.error("Current domain name [{}] already registered in the system!", oAuth2ClientsParams.getDomainName()); - throw new IncorrectParameterException("Current domain name [" + oAuth2ClientsParams.getDomainName() + "] already registered in the system!"); + log.error("Current domain name [{}] already registered in the system!", domainName); + throw new IncorrectParameterException("Current domain name [" + domainName + "] already registered in the system!"); } } else { AdminSettings existentOAuth2ClientsSettingsById = adminSettingsService.findAdminSettingsById( tenantId, - new AdminSettingsId(UUID.fromString(oAuth2ClientsParams.getAdminSettingsId())) + new AdminSettingsId(UUID.fromString(prevAdminSettingsId)) ); if (existentOAuth2ClientsSettingsById == null) { @@ -201,36 +190,61 @@ public class OAuth2ServiceImpl implements OAuth2Service { throw new IllegalStateException("Admin setting ID is already set in login white labeling object, but doesn't exist in the database"); } - if (!existentOAuth2ClientsSettingsById.getKey().equals(clientRegistrationsKey)) { + if (!existentOAuth2ClientsSettingsById.getKey().equals(selectedDomainSettingsKey)) { if (existentAdminSettingsByKey == null) { - adminSettingsService.deleteAdminSettingsByKey(tenantId, existentOAuth2ClientsSettingsById.getKey()); - AdminSettings newOAuth2ClientsSettings = saveOAuth2ClientSettings(tenantId, clientRegistrationsKey); - oAuth2ClientsParams.setAdminSettingsId(newOAuth2ClientsSettings.getId().getId().toString()); + AdminSettings newOAuth2ClientsSettings = replaceExistentAdminSettings(tenantId, selectedDomainSettingsKey, existentOAuth2ClientsSettingsById.getKey()); + return newOAuth2ClientsSettings.getId().getId().toString(); } else { - log.error("Current domain name [{}] already registered in the system!", oAuth2ClientsParams.getDomainName()); - throw new IncorrectParameterException("Current domain name [" + oAuth2ClientsParams.getDomainName() + "] already registered in the system!"); + log.error("Current domain name [{}] already registered in the system!", domainName); + throw new IncorrectParameterException("Current domain name [" + domainName + "] already registered in the system!"); } } + return prevAdminSettingsId; } - // TODO refactor - String json; - try { - json = mapper.writeValueAsString(oAuth2ClientsParams); - } catch (JsonProcessingException e) { - log.error("Unable to convert OAuth2 Client Registration Params to JSON!", e); - throw new IncorrectParameterException("Unable to convert OAuth2 Client Registration Params to JSON!"); - } - List attributes = new ArrayList<>(); - long ts = System.currentTimeMillis(); - attributes.add(new BaseAttributeKvEntry(new StringDataEntry(OAUTH2_CLIENT_REGISTRATIONS_PARAMS, json), ts)); - try { - // TODO ask if I need here .get() - attributesService.save(tenantId, tenantId, DataConstants.SERVER_SCOPE, attributes).get(); - } catch (Exception e) { - log.error("Unable to save OAuth2 Client Registration Params to attributes!", e); - throw new IncorrectParameterException("Unable to save OAuth2 Client Registration Params to attributes!"); - } - return getTenantOAuth2ClientsParams(tenantId); + } + + private AdminSettings replaceExistentAdminSettings(TenantId tenantId, String newKey, String oldKey) { + adminSettingsService.deleteAdminSettingsByKey(tenantId, oldKey); + AdminSettings tenantAdminSettings = createTenantAdminSettings(tenantId, newKey); + return adminSettingsService.saveAdminSettings(tenantId, tenantAdminSettings); + } + + private AdminSettings createTenantAdminSettings(TenantId tenantId, String clientRegistrationsKey) { + AdminSettings clientRegistrationParamsSettings = new AdminSettings(); + clientRegistrationParamsSettings.setKey(clientRegistrationsKey); + ObjectNode node = mapper.createObjectNode(); + node.put("entityType", EntityType.TENANT.name()); + node.put("entityId", tenantId.toString()); + clientRegistrationParamsSettings.setJsonValue(node); + return clientRegistrationParamsSettings; + } + + private AdminSettings createSystemAdminSettings(OAuth2ClientsParams oAuth2ClientsParams) { + AdminSettings clientRegistrationParamsSettings = new AdminSettings(); + clientRegistrationParamsSettings.setKey(OAUTH2_CLIENT_REGISTRATIONS_PARAMS); + ObjectNode clientRegistrationsNode = mapper.createObjectNode(); + + String json = toJson(oAuth2ClientsParams); + clientRegistrationsNode.put(SYSTEM_SETTINGS_OAUTH2_VALUE, json); + clientRegistrationParamsSettings.setJsonValue(clientRegistrationsNode); + + return clientRegistrationParamsSettings; + } + + private void validateUniqueRegistrationId(OAuth2ClientsParams inputOAuth2ClientsParams, TenantId tenantId) { + inputOAuth2ClientsParams.getClientRegistrations().stream() + .map(OAuth2ClientRegistration::getRegistrationId) + .forEach(registrationId -> { + clientsParams.forEach((paramsTenantId, oAuth2ClientsParams) -> { + boolean registrationExists = oAuth2ClientsParams.getClientRegistrations().stream() + .map(OAuth2ClientRegistration::getRegistrationId) + .anyMatch(registrationId::equals); + if (registrationExists && !tenantId.equals(paramsTenantId)) { + log.error("Current registrationId [{}] already registered in the system!", registrationId); + throw new IncorrectParameterException("Current registrationId [" + registrationId + "] already registered in the system!"); + } + }); + }); } private void validate(OAuth2ClientsParams oAuth2ClientsParams) { @@ -276,8 +290,8 @@ public class OAuth2ServiceImpl implements OAuth2Service { OAuth2ClientsParams params = getTenantOAuth2ClientsParams(tenantId); if (!StringUtils.isEmpty(params.getDomainName())) { // TODO don't we need to delete from attributes? - String oauth2ClientsParamsKey = constructClientRegistrationsKey(params.getDomainName()); - adminSettingsService.deleteAdminSettingsByKey(tenantId, oauth2ClientsParamsKey); + String settingsKey = constructAdminSettingsDomainKey(params.getDomainName()); + adminSettingsService.deleteAdminSettingsByKey(tenantId, settingsKey); } } @@ -310,17 +324,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { }, MoreExecutors.directExecutor()); } - private AdminSettings saveOAuth2ClientSettings(TenantId tenantId, String clientRegistrationsKey) { - AdminSettings oauth2ClientsSettings = new AdminSettings(); - oauth2ClientsSettings.setKey(clientRegistrationsKey); - ObjectNode node = mapper.createObjectNode(); - node.put("entityType", EntityType.TENANT.name()); - node.put("entityId", tenantId.toString()); - oauth2ClientsSettings.setJsonValue(node); - return adminSettingsService.saveAdminSettings(tenantId, oauth2ClientsSettings); - } - - private String constructClientRegistrationsKey(String domainName) { + private String constructAdminSettingsDomainKey(String domainName) { String clientRegistrationsKey; if (StringUtils.isEmpty(domainName)) { clientRegistrationsKey = OAUTH2_CLIENT_REGISTRATIONS_PARAMS; @@ -347,7 +351,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { } private OAuth2ClientsParams getMergedOAuth2ClientsParams(String domainName) { - AdminSettings oauth2ClientsSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, constructClientRegistrationsKey(domainName)); + AdminSettings oauth2ClientsSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, constructAdminSettingsDomainKey(domainName)); OAuth2ClientsParams result; if (oauth2ClientsSettings != null) { String strEntityType = oauth2ClientsSettings.getJsonValue().get("entityType").asText(); @@ -367,6 +371,17 @@ public class OAuth2ServiceImpl implements OAuth2Service { return result; } + private String toJson(OAuth2ClientsParams oAuth2ClientsParams) { + String json; + try { + json = mapper.writeValueAsString(oAuth2ClientsParams); + } catch (JsonProcessingException e) { + log.error("Unable to convert OAuth2 Client Registration Params to JSON!", e); + throw new IncorrectParameterException("Unable to convert OAuth2 Client Registration Params to JSON!"); + } + return json; + } + private final Consumer validator = clientRegistration -> { if (StringUtils.isEmpty(clientRegistration.getRegistrationId())) { throw new DataValidationException("Registration ID should be specified!"); From 3a21235593443a96bebf12957cef15d898bba2bf Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Wed, 24 Jun 2020 15:05:56 +0300 Subject: [PATCH 036/463] Added saving TenantOAuth2 config in cache --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 6228f0d8d8..61c55c9dad 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -143,19 +143,27 @@ public class OAuth2ServiceImpl implements OAuth2Service { public OAuth2ClientsParams saveTenantOAuth2ClientsParams(TenantId tenantId, OAuth2ClientsParams oAuth2ClientsParams) { // TODO what if tenant saves config for several different domain names, do we need to check it validate(oAuth2ClientsParams); - // TODO check by registration ID in system - String adminSettingsId = processTenantAdminSettings(tenantId, oAuth2ClientsParams.getDomainName(), oAuth2ClientsParams.getAdminSettingsId()); - oAuth2ClientsParams.setAdminSettingsId(adminSettingsId); - - List attributes = createOAuth2ClientsParamsAttributes(oAuth2ClientsParams); + validateUniqueRegistrationId(oAuth2ClientsParams, tenantId); + lock.lock(); try { - // TODO ask if I need .get() here - attributesService.save(tenantId, tenantId, DataConstants.SERVER_SCOPE, attributes).get(); - } catch (Exception e) { - log.error("Unable to save OAuth2 Client Registration Params to attributes!", e); - throw new IncorrectParameterException("Unable to save OAuth2 Client Registration Params to attributes!"); + String adminSettingsId = processTenantAdminSettings(tenantId, oAuth2ClientsParams.getDomainName(), oAuth2ClientsParams.getAdminSettingsId()); + oAuth2ClientsParams.setAdminSettingsId(adminSettingsId); + + List attributes = createOAuth2ClientsParamsAttributes(oAuth2ClientsParams); + try { + // TODO ask if I need .get() here + attributesService.save(tenantId, tenantId, DataConstants.SERVER_SCOPE, attributes).get(); + } catch (Exception e) { + log.error("Unable to save OAuth2 Client Registration Params to attributes!", e); + throw new IncorrectParameterException("Unable to save OAuth2 Client Registration Params to attributes!"); + } + + clientsParams.put(tenantId, oAuth2ClientsParams); + } finally { + lock.unlock(); } + return getTenantOAuth2ClientsParams(tenantId); } From 1628f79873849c6cba0cef6b6a0e05fb28255f24 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Wed, 24 Jun 2020 15:27:46 +0300 Subject: [PATCH 037/463] Returned reading system OAuth2 config from DB (for startup cache filling) --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 61c55c9dad..5a8b907c93 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -266,13 +266,22 @@ public class OAuth2ServiceImpl implements OAuth2Service { return clientsParams.get(tenantId); } + private OAuth2ClientsParams getSystemOAuth2ClientsParamsFromDb(TenantId tenantId) { + AdminSettings oauth2ClientsParamsSettings = adminSettingsService.findAdminSettingsByKey(tenantId, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); + String json = null; + if (oauth2ClientsParamsSettings != null) { + json = oauth2ClientsParamsSettings.getJsonValue().get(SYSTEM_SETTINGS_OAUTH2_VALUE).asText(); + } + return constructOAuth2ClientsParams(json); + } + @Override public OAuth2ClientsParams getTenantOAuth2ClientsParams(TenantId tenantId) { return clientsParams.get(tenantId); } private Map getAllOAuth2ClientsParams() { - OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); + OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParamsFromDb(TenantId.SYS_TENANT_ID); ListenableFuture> jsonFuture = getAllOAuth2ClientsParamsAttribute(); try { return Futures.transform(jsonFuture, From 32b9f589979ae8e6df50eea0ec7bc0253b79e62b Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Wed, 24 Jun 2020 15:29:48 +0300 Subject: [PATCH 038/463] Refactored --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 88 +++++++++---------- 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 5a8b907c93..5cb2e90ba1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -54,14 +54,13 @@ public class OAuth2ServiceImpl implements OAuth2Service { private static final String OAUTH2_CLIENT_REGISTRATIONS_PARAMS = "oauth2ClientRegistrationsParams"; private static final String OAUTH2_CLIENT_REGISTRATIONS_DOMAIN_NAME_PREFIX = "oauth2ClientRegistrationsDomainNamePrefix"; - private static final String ALLOW_OAUTH2_CONFIGURATION = "allowOAuth2Configuration"; - - private static final String SYSTEM_SETTINGS_OAUTH2_VALUE = "value"; - private static final String OAUTH2_AUTHORIZATION_PATH_TEMPLATE = "/oauth2/authorization/%s"; + private final ReentrantLock lock = new ReentrantLock(); + private final Map clientsParams = new ConcurrentHashMap<>(); + @Autowired private Environment environment; @@ -74,16 +73,11 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Autowired private TenantService tenantService; - private final ReentrantLock lock = new ReentrantLock(); - - private final Map clientsParams = new ConcurrentHashMap<>(); - - private boolean isInstall() { return environment.acceptsProfiles("install"); } - // TODO add field that invalidates cache in case write to cache fails after successful saving in DB + // TODO do I need to add a field that invalidates cache in case write to cache fails after successful saving in DB? @PostConstruct public void init() { if (isInstall()) return; @@ -95,7 +89,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Override public OAuth2ClientRegistration getClientRegistration(String registrationId) { - return clientsParams.values().stream() + return clientsParams.values().stream() .flatMap(oAuth2ClientsParams -> oAuth2ClientsParams.getClientRegistrations().stream()) .filter(clientRegistration -> registrationId.equals(clientRegistration.getRegistrationId())) .findFirst() @@ -113,14 +107,6 @@ public class OAuth2ServiceImpl implements OAuth2Service { ; } - private OAuth2ClientInfo toClientInfo(OAuth2ClientRegistration clientRegistration) { - OAuth2ClientInfo client = new OAuth2ClientInfo(); - client.setName(clientRegistration.getLoginButtonLabel()); - client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, clientRegistration.getRegistrationId())); - client.setIcon(clientRegistration.getLoginButtonIcon()); - return client; - } - @Override public OAuth2ClientsParams saveSystemOAuth2ClientsParams(OAuth2ClientsParams oAuth2ClientsParams) { validate(oAuth2ClientsParams); @@ -286,11 +272,13 @@ public class OAuth2ServiceImpl implements OAuth2Service { try { return Futures.transform(jsonFuture, clientsParamsByKvEntryKey -> { - Map tenantClientParams = clientsParamsByKvEntryKey.entrySet().stream() - .collect(Collectors.toMap( - entry -> new TenantId(UUIDConverter.fromString(entry.getKey())), - entry -> constructOAuth2ClientsParams(entry.getValue()) - )); + Map tenantClientParams = clientsParamsByKvEntryKey != null ? + clientsParamsByKvEntryKey.entrySet().stream() + .collect(Collectors.toMap( + entry -> new TenantId(UUIDConverter.fromString(entry.getKey())), + entry -> constructOAuth2ClientsParams(entry.getValue()) + )) + : new HashMap<>(); tenantClientParams.put(TenantId.SYS_TENANT_ID, systemOAuth2ClientsParams); return tenantClientParams; }, @@ -341,6 +329,29 @@ public class OAuth2ServiceImpl implements OAuth2Service { }, MoreExecutors.directExecutor()); } + private OAuth2ClientsParams getMergedOAuth2ClientsParams(String domainName) { + AdminSettings oauth2ClientsSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, constructAdminSettingsDomainKey(domainName)); + OAuth2ClientsParams result; + if (oauth2ClientsSettings != null) { + String strEntityType = oauth2ClientsSettings.getJsonValue().get("entityType").asText(); + String strEntityId = oauth2ClientsSettings.getJsonValue().get("entityId").asText(); + EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId); + if (!entityId.getEntityType().equals(EntityType.TENANT)) { + log.error("Only tenant can configure OAuth2 for certain domain!"); + throw new IllegalStateException("Only tenant can configure OAuth2 for certain domain!"); + } + TenantId tenantId = (TenantId) entityId; + result = getTenantOAuth2ClientsParams(tenantId); + OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); + if (systemOAuth2ClientsParams != null) { + result.getClientRegistrations().addAll(systemOAuth2ClientsParams.getClientRegistrations()); + } + } else { + result = getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); + } + return result; + } + private String constructAdminSettingsDomainKey(String domainName) { String clientRegistrationsKey; if (StringUtils.isEmpty(domainName)) { @@ -367,27 +378,6 @@ public class OAuth2ServiceImpl implements OAuth2Service { return result; } - private OAuth2ClientsParams getMergedOAuth2ClientsParams(String domainName) { - AdminSettings oauth2ClientsSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, constructAdminSettingsDomainKey(domainName)); - OAuth2ClientsParams result; - if (oauth2ClientsSettings != null) { - String strEntityType = oauth2ClientsSettings.getJsonValue().get("entityType").asText(); - String strEntityId = oauth2ClientsSettings.getJsonValue().get("entityId").asText(); - EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId); - if (!entityId.getEntityType().equals(EntityType.TENANT)) { - log.error("Only tenant can configure OAuth2 for certain domain!"); - throw new IllegalStateException("Only tenant can configure OAuth2 for certain domain!"); - } - TenantId tenantId = (TenantId) entityId; - result = getTenantOAuth2ClientsParams(tenantId); - OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); - result.getClientRegistrations().addAll(systemOAuth2ClientsParams.getClientRegistrations()); - } else { - result = getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); - } - return result; - } - private String toJson(OAuth2ClientsParams oAuth2ClientsParams) { String json; try { @@ -399,6 +389,14 @@ public class OAuth2ServiceImpl implements OAuth2Service { return json; } + private OAuth2ClientInfo toClientInfo(OAuth2ClientRegistration clientRegistration) { + OAuth2ClientInfo client = new OAuth2ClientInfo(); + client.setName(clientRegistration.getLoginButtonLabel()); + client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, clientRegistration.getRegistrationId())); + client.setIcon(clientRegistration.getLoginButtonIcon()); + return client; + } + private final Consumer validator = clientRegistration -> { if (StringUtils.isEmpty(clientRegistration.getRegistrationId())) { throw new DataValidationException("Registration ID should be specified!"); From 84a06e65fa33bb92e5d5d73dac8d973c8b080a47 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Wed, 24 Jun 2020 15:58:08 +0300 Subject: [PATCH 039/463] Added removing full OAuth2 config on tenant deletion --- .../org/thingsboard/server/dao/oauth2/OAuth2Service.java | 2 +- .../org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java | 5 +++-- .../org/thingsboard/server/dao/tenant/TenantServiceImpl.java | 5 +---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java index 05e06cc3ae..b02a48650f 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java @@ -37,7 +37,7 @@ public interface OAuth2Service { OAuth2ClientsParams getTenantOAuth2ClientsParams(TenantId tenantId); - void deleteDomainOAuth2ClientRegistrationByTenant(TenantId tenantId); + void deleteTenantOAuth2ClientsParams(TenantId tenantId); boolean isOAuth2ClientRegistrationAllowed(TenantId tenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 5cb2e90ba1..910c043faf 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -291,12 +291,13 @@ public class OAuth2ServiceImpl implements OAuth2Service { } @Override - public void deleteDomainOAuth2ClientRegistrationByTenant(TenantId tenantId) { + public void deleteTenantOAuth2ClientsParams(TenantId tenantId) { OAuth2ClientsParams params = getTenantOAuth2ClientsParams(tenantId); if (!StringUtils.isEmpty(params.getDomainName())) { - // TODO don't we need to delete from attributes? String settingsKey = constructAdminSettingsDomainKey(params.getDomainName()); adminSettingsService.deleteAdminSettingsByKey(tenantId, settingsKey); + attributesService.removeAll(tenantId, tenantId, DataConstants.SERVER_SCOPE, Collections.singletonList(OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); + clientsParams.remove(tenantId); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index a6cfdee8c2..4708694cee 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -21,7 +21,6 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -41,8 +40,6 @@ import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetsBundleService; -import java.util.List; - import static org.thingsboard.server.dao.service.Validator.validateId; @Service @@ -108,7 +105,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe public void deleteTenant(TenantId tenantId) { log.trace("Executing deleteTenant [{}]", tenantId); Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - oAuth2Service.deleteDomainOAuth2ClientRegistrationByTenant(tenantId); + oAuth2Service.deleteTenantOAuth2ClientsParams(tenantId); customerService.deleteCustomersByTenantId(tenantId); widgetsBundleService.deleteWidgetsBundlesByTenantId(tenantId); dashboardService.deleteDashboardsByTenantId(tenantId); From f2a9939e88953e6c6e0c6669da885f5b498b086b Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Wed, 24 Jun 2020 16:57:40 +0300 Subject: [PATCH 040/463] Fixed bug --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 910c043faf..b2824fdf4e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -23,6 +23,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.ListUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; @@ -111,10 +112,10 @@ public class OAuth2ServiceImpl implements OAuth2Service { public OAuth2ClientsParams saveSystemOAuth2ClientsParams(OAuth2ClientsParams oAuth2ClientsParams) { validate(oAuth2ClientsParams); - validateUniqueRegistrationId(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); + validateRegistrationIdUniqueness(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); lock.lock(); try { - validateUniqueRegistrationId(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); + validateRegistrationIdUniqueness(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); AdminSettings clientRegistrationParamsSettings = createSystemAdminSettings(oAuth2ClientsParams); adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, clientRegistrationParamsSettings); clientsParams.put(TenantId.SYS_TENANT_ID, oAuth2ClientsParams); @@ -130,7 +131,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { // TODO what if tenant saves config for several different domain names, do we need to check it validate(oAuth2ClientsParams); - validateUniqueRegistrationId(oAuth2ClientsParams, tenantId); + validateRegistrationIdUniqueness(oAuth2ClientsParams, tenantId); lock.lock(); try { String adminSettingsId = processTenantAdminSettings(tenantId, oAuth2ClientsParams.getDomainName(), oAuth2ClientsParams.getAdminSettingsId()); @@ -225,7 +226,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { return clientRegistrationParamsSettings; } - private void validateUniqueRegistrationId(OAuth2ClientsParams inputOAuth2ClientsParams, TenantId tenantId) { + private void validateRegistrationIdUniqueness(OAuth2ClientsParams inputOAuth2ClientsParams, TenantId tenantId) { inputOAuth2ClientsParams.getClientRegistrations().stream() .map(OAuth2ClientRegistration::getRegistrationId) .forEach(registrationId -> { @@ -345,7 +346,11 @@ public class OAuth2ServiceImpl implements OAuth2Service { result = getTenantOAuth2ClientsParams(tenantId); OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); if (systemOAuth2ClientsParams != null) { - result.getClientRegistrations().addAll(systemOAuth2ClientsParams.getClientRegistrations()); + ArrayList tenantClientRegistrations = new ArrayList<>(result.getClientRegistrations()); + tenantClientRegistrations.addAll(systemOAuth2ClientsParams.getClientRegistrations()); + result = result.toBuilder() + .clientRegistrations(tenantClientRegistrations) + .build(); } } else { result = getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); From 55a2fe890e3ef2090179812f015f83d380ffd968 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Wed, 24 Jun 2020 17:36:26 +0300 Subject: [PATCH 041/463] Added method to load oauth2 config with tenantId --- .../Oauth2AuthenticationSuccessHandler.java | 2 +- .../server/dao/oauth2/OAuth2Service.java | 3 ++ .../server/dao/oauth2/OAuth2ServiceImpl.java | 35 ++++++++++++++----- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index 4dda36a0fb..03c9db757f 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -64,7 +64,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS try { OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; - OAuth2ClientRegistration clientRegistration = oAuth2Service.getClientRegistration(token.getAuthorizedClientRegistrationId()); + OAuth2ClientRegistration clientRegistration = oAuth2Service.getClientRegistrationWithTenant(token.getAuthorizedClientRegistrationId()).getRight(); OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(clientRegistration.getMapperConfig().getType()); SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, clientRegistration.getMapperConfig()); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java index b02a48650f..17dc6d5f61 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.oauth2; +import org.apache.commons.lang3.tuple.Pair; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -25,6 +26,8 @@ import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; import java.util.List; public interface OAuth2Service { + Pair getClientRegistrationWithTenant(String registrationId); + OAuth2ClientRegistration getClientRegistration(String registrationId); List getOAuth2Clients(String domainName); diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index b2824fdf4e..e23dbf4a20 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -1,12 +1,12 @@ /** * Copyright © 2016-2020 The Thingsboard Authors - * + *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -24,6 +24,8 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.ListUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; @@ -89,12 +91,27 @@ public class OAuth2ServiceImpl implements OAuth2Service { } @Override - public OAuth2ClientRegistration getClientRegistration(String registrationId) { - return clientsParams.values().stream() - .flatMap(oAuth2ClientsParams -> oAuth2ClientsParams.getClientRegistrations().stream()) - .filter(clientRegistration -> registrationId.equals(clientRegistration.getRegistrationId())) + public Pair getClientRegistrationWithTenant(String registrationId) { + return clientsParams.entrySet().stream() + .map(entry -> { + TenantId tenantId = entry.getKey(); + OAuth2ClientRegistration clientRegistration = entry.getValue().getClientRegistrations().stream() + .filter(registration -> registrationId.equals(registration.getRegistrationId())) + .findFirst() + .orElse(null); + return clientRegistration != null ? + ImmutablePair.of(tenantId, clientRegistration) : null; + }) + .filter(Objects::nonNull) .findFirst() - .orElse(null); + .orElse(null) + ; + } + + @Override + public OAuth2ClientRegistration getClientRegistration(String registrationId) { + Pair clientRegistrationPair = getClientRegistrationWithTenant(registrationId); + return clientRegistrationPair != null ? clientRegistrationPair.getRight() : null; } @Override From 235dc0292c733bed4297d9bbe6f53efd4cf6015d Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Wed, 24 Jun 2020 17:40:21 +0300 Subject: [PATCH 042/463] Small refactoring --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index e23dbf4a20..ca85f79511 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -23,7 +23,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections.ListUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.springframework.beans.factory.annotation.Autowired; @@ -61,7 +60,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { private static final String SYSTEM_SETTINGS_OAUTH2_VALUE = "value"; private static final String OAUTH2_AUTHORIZATION_PATH_TEMPLATE = "/oauth2/authorization/%s"; - private final ReentrantLock lock = new ReentrantLock(); + private final ReentrantLock cacheWriteLock = new ReentrantLock(); private final Map clientsParams = new ConcurrentHashMap<>(); @Autowired @@ -130,14 +129,14 @@ public class OAuth2ServiceImpl implements OAuth2Service { validate(oAuth2ClientsParams); validateRegistrationIdUniqueness(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); - lock.lock(); + cacheWriteLock.lock(); try { validateRegistrationIdUniqueness(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); AdminSettings clientRegistrationParamsSettings = createSystemAdminSettings(oAuth2ClientsParams); adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, clientRegistrationParamsSettings); clientsParams.put(TenantId.SYS_TENANT_ID, oAuth2ClientsParams); } finally { - lock.unlock(); + cacheWriteLock.unlock(); } return getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); @@ -149,7 +148,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { validate(oAuth2ClientsParams); validateRegistrationIdUniqueness(oAuth2ClientsParams, tenantId); - lock.lock(); + cacheWriteLock.lock(); try { String adminSettingsId = processTenantAdminSettings(tenantId, oAuth2ClientsParams.getDomainName(), oAuth2ClientsParams.getAdminSettingsId()); oAuth2ClientsParams.setAdminSettingsId(adminSettingsId); @@ -165,7 +164,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { clientsParams.put(tenantId, oAuth2ClientsParams); } finally { - lock.unlock(); + cacheWriteLock.unlock(); } return getTenantOAuth2ClientsParams(tenantId); From a4355ace2840163833947acf2669af8e8b67316f Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 25 Jun 2020 10:52:49 +0300 Subject: [PATCH 043/463] Added reading old system settings before saving new --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index ca85f79511..da649404e1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -129,11 +129,17 @@ public class OAuth2ServiceImpl implements OAuth2Service { validate(oAuth2ClientsParams); validateRegistrationIdUniqueness(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); + cacheWriteLock.lock(); try { validateRegistrationIdUniqueness(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); - AdminSettings clientRegistrationParamsSettings = createSystemAdminSettings(oAuth2ClientsParams); - adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, clientRegistrationParamsSettings); + AdminSettings oauth2SystemAdminSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); + if (oauth2SystemAdminSettings == null) { + oauth2SystemAdminSettings = createSystemAdminSettings(); + } + String json = toJson(oAuth2ClientsParams); + ((ObjectNode) oauth2SystemAdminSettings.getJsonValue()).put(SYSTEM_SETTINGS_OAUTH2_VALUE, json); + adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, oauth2SystemAdminSettings); clientsParams.put(TenantId.SYS_TENANT_ID, oAuth2ClientsParams); } finally { cacheWriteLock.unlock(); @@ -155,7 +161,6 @@ public class OAuth2ServiceImpl implements OAuth2Service { List attributes = createOAuth2ClientsParamsAttributes(oAuth2ClientsParams); try { - // TODO ask if I need .get() here attributesService.save(tenantId, tenantId, DataConstants.SERVER_SCOPE, attributes).get(); } catch (Exception e) { log.error("Unable to save OAuth2 Client Registration Params to attributes!", e); @@ -230,13 +235,11 @@ public class OAuth2ServiceImpl implements OAuth2Service { return clientRegistrationParamsSettings; } - private AdminSettings createSystemAdminSettings(OAuth2ClientsParams oAuth2ClientsParams) { + private AdminSettings createSystemAdminSettings() { AdminSettings clientRegistrationParamsSettings = new AdminSettings(); clientRegistrationParamsSettings.setKey(OAUTH2_CLIENT_REGISTRATIONS_PARAMS); ObjectNode clientRegistrationsNode = mapper.createObjectNode(); - String json = toJson(oAuth2ClientsParams); - clientRegistrationsNode.put(SYSTEM_SETTINGS_OAUTH2_VALUE, json); clientRegistrationParamsSettings.setJsonValue(clientRegistrationsNode); return clientRegistrationParamsSettings; From 9c7f82e91e1c4bae966dfac238be3bb479eb82fd Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 25 Jun 2020 10:53:55 +0300 Subject: [PATCH 044/463] Set parent tenant id in OAuth2ClientMapper --- .../auth/oauth2/AbstractOAuth2ClientMapper.java | 11 ++++++++--- .../security/auth/oauth2/BasicOAuth2ClientMapper.java | 5 +++-- .../auth/oauth2/CustomOAuth2ClientMapper.java | 5 +++-- .../security/auth/oauth2/OAuth2ClientMapper.java | 3 ++- .../oauth2/Oauth2AuthenticationSuccessHandler.java | 9 ++++++--- 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java index c69721542f..61d288cdff 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java @@ -79,7 +79,7 @@ public abstract class AbstractOAuth2ClientMapper { private final Lock userCreationLock = new ReentrantLock(); - protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, boolean allowUserCreation, boolean activateUser) { + protected SecurityUser getOrCreateSecurityUserFromOAuth2User(TenantId parentTenantId, OAuth2User oauth2User, boolean allowUserCreation, boolean activateUser) { UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, oauth2User.getEmail()); User user = userService.findUserByEmail(TenantId.SYS_TENANT_ID, oauth2User.getEmail()); @@ -99,8 +99,13 @@ public abstract class AbstractOAuth2ClientMapper { } else { user.setAuthority(Authority.CUSTOMER_USER); } - TenantId tenantId = oauth2User.getTenantId() != null ? - oauth2User.getTenantId() : getTenantId(oauth2User.getTenantName()); + TenantId tenantId; + if (TenantId.SYS_TENANT_ID.equals(parentTenantId)) { + tenantId = oauth2User.getTenantId() != null ? + oauth2User.getTenantId() : getTenantId(oauth2User.getTenantName()); + } else { + tenantId = parentTenantId; + } user.setTenantId(tenantId); CustomerId customerId = oauth2User.getCustomerId() != null ? oauth2User.getCustomerId() : getCustomerId(user.getTenantId(), oauth2User.getCustomerName()); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java index 7d671767bb..19ed540543 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java @@ -20,6 +20,7 @@ import org.apache.commons.lang3.text.StrSubstitutor; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.service.security.model.SecurityUser; @@ -34,7 +35,7 @@ public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implemen private static final String END_PLACEHOLDER_PREFIX = "}"; @Override - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, OAuth2MapperConfig config) { + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, TenantId parentTenantId, OAuth2MapperConfig config) { OAuth2User oauth2User = new OAuth2User(); Map attributes = token.getPrincipal().getAttributes(); String email = getStringAttributeByKey(attributes, config.getBasicConfig().getEmailAttributeKey()); @@ -58,7 +59,7 @@ public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implemen oauth2User.setDefaultDashboardName(config.getBasicConfig().getDefaultDashboardName()); } - return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser()); + return getOrCreateSecurityUserFromOAuth2User(parentTenantId, oauth2User, config.isAllowUserCreation(), config.isActivateUser()); } private String getTenantName(Map attributes, OAuth2MapperConfig config) { diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java index 9d4ac4d2e2..27286e454a 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java @@ -23,6 +23,7 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.dao.oauth2.OAuth2User; @@ -37,9 +38,9 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); @Override - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, OAuth2MapperConfig config) { + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, TenantId parentTenantId, OAuth2MapperConfig config) { OAuth2User oauth2User = getOAuth2User(token, config.getCustomConfig()); - return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser()); + return getOrCreateSecurityUserFromOAuth2User(parentTenantId, oauth2User, config.isAllowUserCreation(), config.isActivateUser()); } private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, OAuth2CustomMapperConfig custom) { diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java index 444332d74b..6490322b0b 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java @@ -16,9 +16,10 @@ package org.thingsboard.server.service.security.auth.oauth2; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.service.security.model.SecurityUser; public interface OAuth2ClientMapper { - SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, OAuth2MapperConfig config); + SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, TenantId parentTenantId, OAuth2MapperConfig config); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index 03c9db757f..0cff27ae2e 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -15,12 +15,14 @@ */ package org.thingsboard.server.service.security.auth.oauth2; +import org.apache.commons.lang3.tuple.Pair; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; @@ -36,7 +38,6 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @Component(value = "oauth2AuthenticationSuccessHandler") -@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true") public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { private final JwtTokenFactory tokenFactory; @@ -64,9 +65,11 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS try { OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; - OAuth2ClientRegistration clientRegistration = oAuth2Service.getClientRegistrationWithTenant(token.getAuthorizedClientRegistrationId()).getRight(); + Pair clientRegistrationPair = oAuth2Service.getClientRegistrationWithTenant(token.getAuthorizedClientRegistrationId()); + TenantId tenantId = clientRegistrationPair.getKey(); + OAuth2ClientRegistration clientRegistration = clientRegistrationPair.getValue(); OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(clientRegistration.getMapperConfig().getType()); - SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, clientRegistration.getMapperConfig()); + SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, tenantId, clientRegistration.getMapperConfig()); JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); From 997821591a5c1fcf15ac2dc59b2a82a901b629ff Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 25 Jun 2020 13:53:25 +0300 Subject: [PATCH 045/463] Fix work with cacheWriteLock in saveTenantOAuth2 method --- .../org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index da649404e1..62436bab96 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -156,6 +156,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { validateRegistrationIdUniqueness(oAuth2ClientsParams, tenantId); cacheWriteLock.lock(); try { + validateRegistrationIdUniqueness(oAuth2ClientsParams, tenantId); String adminSettingsId = processTenantAdminSettings(tenantId, oAuth2ClientsParams.getDomainName(), oAuth2ClientsParams.getAdminSettingsId()); oAuth2ClientsParams.setAdminSettingsId(adminSettingsId); From d22237796ea1e1a6aa8f7446d1bfafb9d56f6882 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 25 Jun 2020 16:03:22 +0300 Subject: [PATCH 046/463] Allow to pass several domains for oauth2 config (for sys_admin only) --- .../oauth2/OAuth2ClientsDomainParams.java | 33 +++++++ .../data/oauth2/OAuth2ClientsParams.java | 5 +- .../server/dao/oauth2/OAuth2ServiceImpl.java | 98 ++++++++++++++----- 3 files changed, 106 insertions(+), 30 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java new file mode 100644 index 0000000000..d9dc07d8ea --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.oauth2; + +import lombok.*; + +import java.util.List; + +@EqualsAndHashCode +@Data +@ToString +@Builder(toBuilder = true) +@NoArgsConstructor +@AllArgsConstructor +public class OAuth2ClientsDomainParams { + private String domainName; + private String adminSettingsId; + + private List clientRegistrations; +} \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java index 3c0da5a9a0..ef08a892a1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java @@ -26,8 +26,5 @@ import java.util.List; @NoArgsConstructor @AllArgsConstructor public class OAuth2ClientsParams { - private String domainName; - private String adminSettingsId; - - private List clientRegistrations; + private List clientsDomainsParams; } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 62436bab96..a3bd1c90b8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -47,6 +47,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; import java.util.stream.Collectors; +import java.util.stream.Stream; @Slf4j @Service @@ -79,7 +80,6 @@ public class OAuth2ServiceImpl implements OAuth2Service { return environment.acceptsProfiles("install"); } - // TODO do I need to add a field that invalidates cache in case write to cache fails after successful saving in DB? @PostConstruct public void init() { if (isInstall()) return; @@ -94,7 +94,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { return clientsParams.entrySet().stream() .map(entry -> { TenantId tenantId = entry.getKey(); - OAuth2ClientRegistration clientRegistration = entry.getValue().getClientRegistrations().stream() + OAuth2ClientRegistration clientRegistration = toClientRegistrationStream(entry.getValue()) .filter(registration -> registrationId.equals(registration.getRegistrationId())) .findFirst() .orElse(null); @@ -115,9 +115,9 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Override public List getOAuth2Clients(String domainName) { - OAuth2ClientsParams oAuth2ClientsParams = getMergedOAuth2ClientsParams(domainName); - return oAuth2ClientsParams != null && oAuth2ClientsParams.getClientRegistrations() != null ? - oAuth2ClientsParams.getClientRegistrations().stream() + OAuth2ClientsDomainParams oAuth2ClientsDomainParams = getMergedOAuth2ClientsParams(domainName); + return oAuth2ClientsDomainParams != null && oAuth2ClientsDomainParams.getClientRegistrations() != null ? + oAuth2ClientsDomainParams.getClientRegistrations().stream() .map(this::toClientInfo) .collect(Collectors.toList()) : Collections.emptyList() @@ -150,15 +150,19 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Override public OAuth2ClientsParams saveTenantOAuth2ClientsParams(TenantId tenantId, OAuth2ClientsParams oAuth2ClientsParams) { - // TODO what if tenant saves config for several different domain names, do we need to check it + if (oAuth2ClientsParams.getClientsDomainsParams().size() != 1) { + throw new DataValidationException("Tenant can configure OAuth2 only for one domain!"); + } validate(oAuth2ClientsParams); validateRegistrationIdUniqueness(oAuth2ClientsParams, tenantId); cacheWriteLock.lock(); try { validateRegistrationIdUniqueness(oAuth2ClientsParams, tenantId); - String adminSettingsId = processTenantAdminSettings(tenantId, oAuth2ClientsParams.getDomainName(), oAuth2ClientsParams.getAdminSettingsId()); - oAuth2ClientsParams.setAdminSettingsId(adminSettingsId); + + OAuth2ClientsDomainParams oAuth2ClientsDomainParams = oAuth2ClientsParams.getClientsDomainsParams().get(0); + String adminSettingsId = processTenantAdminSettings(tenantId, oAuth2ClientsDomainParams.getDomainName(), oAuth2ClientsDomainParams.getAdminSettingsId()); + oAuth2ClientsDomainParams.setAdminSettingsId(adminSettingsId); List attributes = createOAuth2ClientsParamsAttributes(oAuth2ClientsParams); try { @@ -247,11 +251,20 @@ public class OAuth2ServiceImpl implements OAuth2Service { } private void validateRegistrationIdUniqueness(OAuth2ClientsParams inputOAuth2ClientsParams, TenantId tenantId) { - inputOAuth2ClientsParams.getClientRegistrations().stream() + long distinctRegistrationIds = toClientRegistrationStream(inputOAuth2ClientsParams) + .map(OAuth2ClientRegistration::getRegistrationId) + .distinct() + .count(); + long actualRegistrationIds = toClientRegistrationStream(inputOAuth2ClientsParams).count(); + if (distinctRegistrationIds != actualRegistrationIds) { + throw new DataValidationException("All registration IDs should be unique!"); + } + + toClientRegistrationStream(inputOAuth2ClientsParams) .map(OAuth2ClientRegistration::getRegistrationId) .forEach(registrationId -> { clientsParams.forEach((paramsTenantId, oAuth2ClientsParams) -> { - boolean registrationExists = oAuth2ClientsParams.getClientRegistrations().stream() + boolean registrationExists = toClientRegistrationStream(oAuth2ClientsParams) .map(OAuth2ClientRegistration::getRegistrationId) .anyMatch(registrationId::equals); if (registrationExists && !tenantId.equals(paramsTenantId)) { @@ -263,8 +276,27 @@ public class OAuth2ServiceImpl implements OAuth2Service { } private void validate(OAuth2ClientsParams oAuth2ClientsParams) { - for (OAuth2ClientRegistration clientRegistration : oAuth2ClientsParams.getClientRegistrations()) { - validator.accept(clientRegistration); + validateDomainNames(oAuth2ClientsParams); + + toClientRegistrationStream(oAuth2ClientsParams) + .forEach(validator); + } + + private void validateDomainNames(OAuth2ClientsParams oAuth2ClientsParams) { + oAuth2ClientsParams.getClientsDomainsParams() + .forEach(oAuth2ClientsDomainParams -> { + if (StringUtils.isEmpty(oAuth2ClientsDomainParams.getDomainName())) { + throw new DataValidationException("Domain name should be specified!"); + } + }); + + long distinctDomainNames = oAuth2ClientsParams.getClientsDomainsParams().stream() + .map(OAuth2ClientsDomainParams::getDomainName) + .distinct() + .count(); + long actualDomainNames = oAuth2ClientsParams.getClientsDomainsParams().size(); + if (distinctDomainNames != actualDomainNames) { + throw new DataValidationException("All domain names should be unique!"); } } @@ -300,7 +332,9 @@ public class OAuth2ServiceImpl implements OAuth2Service { entry -> constructOAuth2ClientsParams(entry.getValue()) )) : new HashMap<>(); - tenantClientParams.put(TenantId.SYS_TENANT_ID, systemOAuth2ClientsParams); + if (systemOAuth2ClientsParams.getClientsDomainsParams() != null) { + tenantClientParams.put(TenantId.SYS_TENANT_ID, systemOAuth2ClientsParams); + } return tenantClientParams; }, MoreExecutors.directExecutor() @@ -314,12 +348,12 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Override public void deleteTenantOAuth2ClientsParams(TenantId tenantId) { OAuth2ClientsParams params = getTenantOAuth2ClientsParams(tenantId); - if (!StringUtils.isEmpty(params.getDomainName())) { - String settingsKey = constructAdminSettingsDomainKey(params.getDomainName()); - adminSettingsService.deleteAdminSettingsByKey(tenantId, settingsKey); - attributesService.removeAll(tenantId, tenantId, DataConstants.SERVER_SCOPE, Collections.singletonList(OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); - clientsParams.remove(tenantId); - } + if (params == null) return; + OAuth2ClientsDomainParams domainParams = params.getClientsDomainsParams().get(0); + String settingsKey = constructAdminSettingsDomainKey(domainParams.getDomainName()); + adminSettingsService.deleteAdminSettingsByKey(tenantId, settingsKey); + attributesService.removeAll(tenantId, tenantId, DataConstants.SERVER_SCOPE, Collections.singletonList(OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); + clientsParams.remove(tenantId); } @Override @@ -351,9 +385,17 @@ public class OAuth2ServiceImpl implements OAuth2Service { }, MoreExecutors.directExecutor()); } - private OAuth2ClientsParams getMergedOAuth2ClientsParams(String domainName) { + private OAuth2ClientsDomainParams getMergedOAuth2ClientsParams(String domainName) { AdminSettings oauth2ClientsSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, constructAdminSettingsDomainKey(domainName)); - OAuth2ClientsParams result; + OAuth2ClientsDomainParams result; + + OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); + OAuth2ClientsDomainParams systemOAuth2ClientsDomainParams = systemOAuth2ClientsParams != null ? + systemOAuth2ClientsParams.getClientsDomainsParams().stream() + .filter(oAuth2ClientsDomainParams -> domainName.equals(oAuth2ClientsDomainParams.getDomainName())) + .findFirst() + .orElse(null) + : null; if (oauth2ClientsSettings != null) { String strEntityType = oauth2ClientsSettings.getJsonValue().get("entityType").asText(); String strEntityId = oauth2ClientsSettings.getJsonValue().get("entityId").asText(); @@ -363,17 +405,16 @@ public class OAuth2ServiceImpl implements OAuth2Service { throw new IllegalStateException("Only tenant can configure OAuth2 for certain domain!"); } TenantId tenantId = (TenantId) entityId; - result = getTenantOAuth2ClientsParams(tenantId); - OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); - if (systemOAuth2ClientsParams != null) { + result = getTenantOAuth2ClientsParams(tenantId).getClientsDomainsParams().get(0); + if (systemOAuth2ClientsDomainParams != null) { ArrayList tenantClientRegistrations = new ArrayList<>(result.getClientRegistrations()); - tenantClientRegistrations.addAll(systemOAuth2ClientsParams.getClientRegistrations()); + tenantClientRegistrations.addAll(systemOAuth2ClientsDomainParams.getClientRegistrations()); result = result.toBuilder() .clientRegistrations(tenantClientRegistrations) .build(); } } else { - result = getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); + result = systemOAuth2ClientsDomainParams; } return result; } @@ -423,6 +464,11 @@ public class OAuth2ServiceImpl implements OAuth2Service { return client; } + private Stream toClientRegistrationStream(OAuth2ClientsParams oAuth2ClientsParams) { + return oAuth2ClientsParams.getClientsDomainsParams().stream() + .flatMap(oAuth2ClientsDomainParams -> oAuth2ClientsDomainParams.getClientRegistrations().stream()); + } + private final Consumer validator = clientRegistration -> { if (StringUtils.isEmpty(clientRegistration.getRegistrationId())) { throw new DataValidationException("Registration ID should be specified!"); From 0149c4b6524e9e3c039cba8210bd03489ec718dd Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 25 Jun 2020 16:45:50 +0300 Subject: [PATCH 047/463] Get System OAuth2 config without tenantId --- .../server/controller/OAuth2Controller.java | 2 +- .../server/dao/oauth2/OAuth2Service.java | 2 +- .../server/dao/oauth2/OAuth2ServiceImpl.java | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index 18e7a4eacb..8d94cee08e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -65,7 +65,7 @@ public class OAuth2Controller extends BaseController { checkOAuth2ConfigPermissions(Operation.READ); OAuth2ClientsParams oAuth2ClientsParams = null; if (Authority.SYS_ADMIN.equals(authority)) { - oAuth2ClientsParams = oauth2Service.getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); + oAuth2ClientsParams = oauth2Service.getSystemOAuth2ClientsParams(); } else if (Authority.TENANT_ADMIN.equals(authority)) { oAuth2ClientsParams = oauth2Service.getTenantOAuth2ClientsParams(getCurrentUser().getTenantId()); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java index 17dc6d5f61..a575142499 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java @@ -36,7 +36,7 @@ public interface OAuth2Service { OAuth2ClientsParams saveTenantOAuth2ClientsParams(TenantId tenantId, OAuth2ClientsParams oAuth2ClientsParams); - OAuth2ClientsParams getSystemOAuth2ClientsParams(TenantId tenantId); + OAuth2ClientsParams getSystemOAuth2ClientsParams(); OAuth2ClientsParams getTenantOAuth2ClientsParams(TenantId tenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index a3bd1c90b8..7d28af9fc0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -145,7 +145,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { cacheWriteLock.unlock(); } - return getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); + return getSystemOAuth2ClientsParams(); } @Override @@ -301,12 +301,12 @@ public class OAuth2ServiceImpl implements OAuth2Service { } @Override - public OAuth2ClientsParams getSystemOAuth2ClientsParams(TenantId tenantId) { - return clientsParams.get(tenantId); + public OAuth2ClientsParams getSystemOAuth2ClientsParams() { + return clientsParams.get(TenantId.SYS_TENANT_ID); } - private OAuth2ClientsParams getSystemOAuth2ClientsParamsFromDb(TenantId tenantId) { - AdminSettings oauth2ClientsParamsSettings = adminSettingsService.findAdminSettingsByKey(tenantId, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); + private OAuth2ClientsParams getSystemOAuth2ClientsParamsFromDb() { + AdminSettings oauth2ClientsParamsSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); String json = null; if (oauth2ClientsParamsSettings != null) { json = oauth2ClientsParamsSettings.getJsonValue().get(SYSTEM_SETTINGS_OAUTH2_VALUE).asText(); @@ -320,7 +320,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { } private Map getAllOAuth2ClientsParams() { - OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParamsFromDb(TenantId.SYS_TENANT_ID); + OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParamsFromDb(); ListenableFuture> jsonFuture = getAllOAuth2ClientsParamsAttribute(); try { return Futures.transform(jsonFuture, @@ -389,7 +389,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { AdminSettings oauth2ClientsSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, constructAdminSettingsDomainKey(domainName)); OAuth2ClientsDomainParams result; - OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParams(TenantId.SYS_TENANT_ID); + OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParams(); OAuth2ClientsDomainParams systemOAuth2ClientsDomainParams = systemOAuth2ClientsParams != null ? systemOAuth2ClientsParams.getClientsDomainsParams().stream() .filter(oAuth2ClientsDomainParams -> domainName.equals(oAuth2ClientsDomainParams.getDomainName())) From 0c7cd1297c45e67f3f06ad861ea19e2fb2a13b18 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 25 Jun 2020 17:06:55 +0300 Subject: [PATCH 048/463] Moved static fields and methods to Util class --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 33 ++-------------- .../server/dao/oauth2/OAuth2Utils.java | 39 +++++++++++++++++++ 2 files changed, 42 insertions(+), 30 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 7d28af9fc0..0bcc67e85a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -49,18 +49,14 @@ import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.thingsboard.server.dao.oauth2.OAuth2Utils.*; + @Slf4j @Service public class OAuth2ServiceImpl implements OAuth2Service { private static final ObjectMapper mapper = new ObjectMapper(); - private static final String OAUTH2_CLIENT_REGISTRATIONS_PARAMS = "oauth2ClientRegistrationsParams"; - private static final String OAUTH2_CLIENT_REGISTRATIONS_DOMAIN_NAME_PREFIX = "oauth2ClientRegistrationsDomainNamePrefix"; - private static final String ALLOW_OAUTH2_CONFIGURATION = "allowOAuth2Configuration"; - private static final String SYSTEM_SETTINGS_OAUTH2_VALUE = "value"; - private static final String OAUTH2_AUTHORIZATION_PATH_TEMPLATE = "/oauth2/authorization/%s"; - private final ReentrantLock cacheWriteLock = new ReentrantLock(); private final Map clientsParams = new ConcurrentHashMap<>(); @@ -118,7 +114,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { OAuth2ClientsDomainParams oAuth2ClientsDomainParams = getMergedOAuth2ClientsParams(domainName); return oAuth2ClientsDomainParams != null && oAuth2ClientsDomainParams.getClientRegistrations() != null ? oAuth2ClientsDomainParams.getClientRegistrations().stream() - .map(this::toClientInfo) + .map(OAuth2Utils::toClientInfo) .collect(Collectors.toList()) : Collections.emptyList() ; @@ -419,16 +415,6 @@ public class OAuth2ServiceImpl implements OAuth2Service { return result; } - private String constructAdminSettingsDomainKey(String domainName) { - String clientRegistrationsKey; - if (StringUtils.isEmpty(domainName)) { - clientRegistrationsKey = OAUTH2_CLIENT_REGISTRATIONS_PARAMS; - } else { - clientRegistrationsKey = OAUTH2_CLIENT_REGISTRATIONS_DOMAIN_NAME_PREFIX + "_" + domainName; - } - return clientRegistrationsKey; - } - private OAuth2ClientsParams constructOAuth2ClientsParams(String json) { OAuth2ClientsParams result = null; if (!StringUtils.isEmpty(json)) { @@ -456,19 +442,6 @@ public class OAuth2ServiceImpl implements OAuth2Service { return json; } - private OAuth2ClientInfo toClientInfo(OAuth2ClientRegistration clientRegistration) { - OAuth2ClientInfo client = new OAuth2ClientInfo(); - client.setName(clientRegistration.getLoginButtonLabel()); - client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, clientRegistration.getRegistrationId())); - client.setIcon(clientRegistration.getLoginButtonIcon()); - return client; - } - - private Stream toClientRegistrationStream(OAuth2ClientsParams oAuth2ClientsParams) { - return oAuth2ClientsParams.getClientsDomainsParams().stream() - .flatMap(oAuth2ClientsDomainParams -> oAuth2ClientsDomainParams.getClientRegistrations().stream()); - } - private final Consumer validator = clientRegistration -> { if (StringUtils.isEmpty(clientRegistration.getRegistrationId())) { throw new DataValidationException("Registration ID should be specified!"); diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java new file mode 100644 index 0000000000..faa96e1d4f --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java @@ -0,0 +1,39 @@ +package org.thingsboard.server.dao.oauth2; + +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; + +import java.util.stream.Stream; + +public class OAuth2Utils { + public static final String OAUTH2_CLIENT_REGISTRATIONS_PARAMS = "oauth2ClientRegistrationsParams"; + public static final String OAUTH2_CLIENT_REGISTRATIONS_DOMAIN_NAME_PREFIX = "oauth2ClientRegistrationsDomainNamePrefix"; + public static final String ALLOW_OAUTH2_CONFIGURATION = "allowOAuth2Configuration"; + public static final String SYSTEM_SETTINGS_OAUTH2_VALUE = "value"; + public static final String OAUTH2_AUTHORIZATION_PATH_TEMPLATE = "/oauth2/authorization/%s"; + + public static String constructAdminSettingsDomainKey(String domainName) { + String clientRegistrationsKey; + if (StringUtils.isEmpty(domainName)) { + clientRegistrationsKey = OAUTH2_CLIENT_REGISTRATIONS_PARAMS; + } else { + clientRegistrationsKey = OAUTH2_CLIENT_REGISTRATIONS_DOMAIN_NAME_PREFIX + "_" + domainName; + } + return clientRegistrationsKey; + } + + public static OAuth2ClientInfo toClientInfo(OAuth2ClientRegistration clientRegistration) { + OAuth2ClientInfo client = new OAuth2ClientInfo(); + client.setName(clientRegistration.getLoginButtonLabel()); + client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, clientRegistration.getRegistrationId())); + client.setIcon(clientRegistration.getLoginButtonIcon()); + return client; + } + + public static Stream toClientRegistrationStream(OAuth2ClientsParams oAuth2ClientsParams) { + return oAuth2ClientsParams.getClientsDomainsParams().stream() + .flatMap(oAuth2ClientsDomainParams -> oAuth2ClientsDomainParams.getClientRegistrations().stream()); + } +} From c30e8b3a2d8cfdf3afdf40028422e48916d2c115 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 25 Jun 2020 18:04:39 +0300 Subject: [PATCH 049/463] Added method to delete system oauth2 config --- .../org/thingsboard/server/dao/oauth2/OAuth2Service.java | 2 ++ .../thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java index a575142499..320a2e650a 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java @@ -42,6 +42,8 @@ public interface OAuth2Service { void deleteTenantOAuth2ClientsParams(TenantId tenantId); + void deleteSystemOAuth2ClientsParams(); + boolean isOAuth2ClientRegistrationAllowed(TenantId tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 0bcc67e85a..6d344d9640 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -352,6 +352,12 @@ public class OAuth2ServiceImpl implements OAuth2Service { clientsParams.remove(tenantId); } + @Override + public void deleteSystemOAuth2ClientsParams() { + adminSettingsService.deleteAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAuth2Utils.OAUTH2_CLIENT_REGISTRATIONS_PARAMS); + clientsParams.remove(TenantId.SYS_TENANT_ID); + } + @Override public boolean isOAuth2ClientRegistrationAllowed(TenantId tenantId) { Tenant tenant = tenantService.findTenantById(tenantId); From 328c827744ac897729b30fc67ed085a2e6250c2b Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 25 Jun 2020 18:05:21 +0300 Subject: [PATCH 050/463] Made method 'getAllOAuth2Params' public --- .../java/org/thingsboard/server/dao/oauth2/OAuth2Service.java | 3 +++ .../org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java index 320a2e650a..380738583f 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; import java.util.List; +import java.util.Map; public interface OAuth2Service { Pair getClientRegistrationWithTenant(String registrationId); @@ -46,4 +47,6 @@ public interface OAuth2Service { boolean isOAuth2ClientRegistrationAllowed(TenantId tenantId); + Map getAllOAuth2ClientsParams(); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 6d344d9640..c91739c9e6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -315,7 +315,9 @@ public class OAuth2ServiceImpl implements OAuth2Service { return clientsParams.get(tenantId); } - private Map getAllOAuth2ClientsParams() { + // TODO this is just for test, maybe there's a better way to test it without exporting to interface + @Override + public Map getAllOAuth2ClientsParams() { OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParamsFromDb(); ListenableFuture> jsonFuture = getAllOAuth2ClientsParamsAttribute(); try { From a2bf3d687f70c87e4d0df7361b570d03070e960c Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 25 Jun 2020 18:22:28 +0300 Subject: [PATCH 051/463] Added tests for OAuth2Service --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 1 + .../dao/service/BaseOAuth2ServiceTest.java | 415 ++++++++++++++++++ .../dao/service/sql/OAuth2ServiceSqlTest.java | 23 + 3 files changed, 439 insertions(+) create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/sql/OAuth2ServiceSqlTest.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index c91739c9e6..d6210df266 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -371,6 +371,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { } } + // TODO maybe it's better to load all tenants and get attribute for each one private ListenableFuture> getAllOAuth2ClientsParamsAttribute() { ListenableFuture> entityAttributeKvEntriesFuture; try { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java new file mode 100644 index 0000000000..86fac00f1f --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -0,0 +1,415 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.service; + +import org.apache.commons.lang3.tuple.Pair; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.oauth2.*; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.oauth2.OAuth2Service; +import org.thingsboard.server.dao.oauth2.OAuth2Utils; + +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.thingsboard.server.dao.oauth2.OAuth2Utils.ALLOW_OAUTH2_CONFIGURATION; + +public class BaseOAuth2ServiceTest extends AbstractServiceTest { + + @Autowired + protected OAuth2Service oAuth2Service; + + @Autowired + protected AttributesService attributesService; + + private TenantId tenantId; + + @Before + public void beforeRun() throws Exception { + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = tenantService.saveTenant(tenant); + Assert.assertNotNull(savedTenant); + tenantId = savedTenant.getId(); + + Assert.assertNull(oAuth2Service.getSystemOAuth2ClientsParams()); + Assert.assertNull(oAuth2Service.getTenantOAuth2ClientsParams(tenantId)); + + Assert.assertTrue(attributesService.findAll(tenantId, tenantId, DataConstants.SERVER_SCOPE).get().isEmpty()); + Assert.assertNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAuth2Utils.OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); + } + + @After + public void after() throws Exception { + clearSysAdmin(); + + tenantService.deleteTenant(tenantId); + + Assert.assertNull(oAuth2Service.getSystemOAuth2ClientsParams()); + Assert.assertNull(oAuth2Service.getTenantOAuth2ClientsParams(tenantId)); + + Assert.assertTrue(attributesService.findAll(tenantId, tenantId, DataConstants.SERVER_SCOPE).get().isEmpty()); + Assert.assertNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAuth2Utils.OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); + } + + @Test + public void testIsOAuth2Allowed_null() throws IOException { + updateTenantAllowOAuth2Setting(null); + Assert.assertTrue(oAuth2Service.isOAuth2ClientRegistrationAllowed(tenantId)); + } + + @Test + public void testIsOAuth2Allowed_false() throws IOException { + updateTenantAllowOAuth2Setting(false); + Assert.assertFalse(oAuth2Service.isOAuth2ClientRegistrationAllowed(tenantId)); + } + + @Test + public void testIsOAuth2Allowed_true() throws IOException { + updateTenantAllowOAuth2Setting(true); + Assert.assertTrue(oAuth2Service.isOAuth2ClientRegistrationAllowed(tenantId)); + } + + @Test + public void testSaveSystemOAuth2() throws IOException { + updateTenantAllowOAuth2Setting(true); + Assert.assertTrue(oAuth2Service.isOAuth2ClientRegistrationAllowed(tenantId)); + } + + @Test(expected = DataValidationException.class) + public void testSaveSystemParamsWithDuplicateDomains() { + oAuth2Service.saveSystemOAuth2ClientsParams(clientsParamsWithDuplicateDomains()); + } + + @Test(expected = DataValidationException.class) + public void testSaveSystemParamsWithDuplicateRegistrationIds() { + oAuth2Service.saveSystemOAuth2ClientsParams(clientsParamsWithDuplicateRegistrationIds()); + } + + @Test(expected = DataValidationException.class) + public void testSaveTenantParamsWithDuplicateRegistrationIds() { + oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, clientsParamsWithDuplicateRegistrationIds()); + } + + @Test(expected = DataValidationException.class) + public void testSaveTenantParamsWithMultipleDomains() { + oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, validClientsParamsWithMultipleDomains()); + } + + @Test + public void testSaveSystemParams() { + OAuth2ClientsParams clientsParams = validClientsParams(); + OAuth2ClientsParams savedClientParams = oAuth2Service.saveSystemOAuth2ClientsParams(clientsParams); + + Assert.assertNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAuth2Utils.OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); + Assert.assertEquals(clientsParams, savedClientParams); + } + + @Test + public void testSaveSystemParamsWithMultipleDomains() { + OAuth2ClientsParams clientsParams = validClientsParamsWithMultipleDomains(); + OAuth2ClientsParams savedClientParams = oAuth2Service.saveSystemOAuth2ClientsParams(clientsParams); + + Assert.assertNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAuth2Utils.OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); + Assert.assertEquals(clientsParams, savedClientParams); + } + + @Test + public void testFindSystemParams() { + OAuth2ClientsParams clientsParams = validClientsParams(); + oAuth2Service.saveSystemOAuth2ClientsParams(clientsParams); + + Assert.assertNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAuth2Utils.OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); + + OAuth2ClientsParams foundClientParams = oAuth2Service.getSystemOAuth2ClientsParams(); + Assert.assertNotNull(foundClientParams); + Assert.assertEquals(clientsParams, foundClientParams); + } + + @Test + public void testSaveTenantParams() { + OAuth2ClientsParams clientsParams = validClientsParams(); + OAuth2ClientsDomainParams domainParams = clientsParams.getClientsDomainsParams().get(0); + String domainKey = OAuth2Utils.constructAdminSettingsDomainKey(domainParams.getDomainName()); + + Assert.assertNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); + + OAuth2ClientsParams savedClientParams = oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, clientsParams); + + Assert.assertNotNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); + Assert.assertNotNull(savedClientParams); + + OAuth2ClientsDomainParams savedDomainParams = savedClientParams.getClientsDomainsParams().get(0); + Assert.assertNotNull(savedDomainParams.getAdminSettingsId()); + Assert.assertEquals(domainParams.getDomainName(), savedDomainParams.getDomainName()); + Assert.assertEquals(domainParams.getClientRegistrations(), savedDomainParams.getClientRegistrations()); + } + + @Test + public void testFindTenantParams() { + OAuth2ClientsParams clientsParams = validClientsParams(); + OAuth2ClientsDomainParams domainParams = clientsParams.getClientsDomainsParams().get(0); + String domainKey = OAuth2Utils.constructAdminSettingsDomainKey(domainParams.getDomainName()); + + Assert.assertNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); + + OAuth2ClientsParams savedClientsParams = oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, clientsParams); + + Assert.assertNotNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); + + OAuth2ClientsParams foundClientsParams = oAuth2Service.getTenantOAuth2ClientsParams(tenantId); + Assert.assertEquals(savedClientsParams, foundClientsParams); + } + + @Test + public void testGetClientRegistrationWithTenant() { + OAuth2ClientsParams tenantClientsParams = validClientsParams(); + OAuth2ClientsParams sysAdminClientsParams = validClientsParams(); + + oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, tenantClientsParams); + oAuth2Service.saveSystemOAuth2ClientsParams(sysAdminClientsParams); + + OAuth2Utils.toClientRegistrationStream(tenantClientsParams) + .forEach(clientRegistration -> { + Pair pair = oAuth2Service.getClientRegistrationWithTenant(clientRegistration.getRegistrationId()); + Assert.assertEquals(tenantId, pair.getKey()); + Assert.assertEquals(clientRegistration.getRegistrationId(), pair.getValue().getRegistrationId()); + }); + OAuth2Utils.toClientRegistrationStream(sysAdminClientsParams) + .forEach(clientRegistration -> { + Pair pair = oAuth2Service.getClientRegistrationWithTenant(clientRegistration.getRegistrationId()); + Assert.assertNotNull(pair); + Assert.assertEquals(TenantId.SYS_TENANT_ID, pair.getKey()); + Assert.assertEquals(clientRegistration.getRegistrationId(), pair.getValue().getRegistrationId()); + }); + } + + @Test + public void testGetClientRegistration() { + OAuth2ClientsParams tenantClientsParams = validClientsParams(); + OAuth2ClientsParams sysAdminClientsParams = validClientsParams(); + + oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, tenantClientsParams); + oAuth2Service.saveSystemOAuth2ClientsParams(sysAdminClientsParams); + + Stream.concat( + OAuth2Utils.toClientRegistrationStream(tenantClientsParams), + OAuth2Utils.toClientRegistrationStream(sysAdminClientsParams) + ) + .forEach(clientRegistration -> { + OAuth2ClientRegistration foundClientRegistration = oAuth2Service.getClientRegistration(clientRegistration.getRegistrationId()); + Assert.assertNotNull(foundClientRegistration); + Assert.assertEquals(clientRegistration.getRegistrationId(), foundClientRegistration.getRegistrationId()); + }); + + } + + @Test + public void testGetOAuth2Clients() { + OAuth2ClientsParams tenantClientsParams = validClientsParams(); + OAuth2ClientsParams sysAdminClientsParams = validClientsParams(); + + OAuth2ClientsDomainParams tenantDomainParams = tenantClientsParams.getClientsDomainsParams().get(0); + OAuth2ClientsDomainParams systemDomainParams = sysAdminClientsParams.getClientsDomainsParams().get(0); + systemDomainParams.setDomainName(tenantDomainParams.getDomainName()); + + oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, tenantClientsParams); + oAuth2Service.saveSystemOAuth2ClientsParams(sysAdminClientsParams); + + List oAuth2Clients = oAuth2Service.getOAuth2Clients(tenantDomainParams.getDomainName()); + + Set actualLabels = Stream.concat( + tenantDomainParams.getClientRegistrations().stream() + .map(OAuth2ClientRegistration::getLoginButtonLabel), + systemDomainParams.getClientRegistrations().stream() + .map(OAuth2ClientRegistration::getLoginButtonLabel) + ).collect(Collectors.toSet()); + + + Set foundLabels = oAuth2Clients.stream().map(OAuth2ClientInfo::getName).collect(Collectors.toSet()); + Assert.assertEquals(actualLabels, foundLabels); + } + + @Test + public void testGetAllOAuth2ClientsParams() { + OAuth2ClientsParams tenantClientsParams = validClientsParams(); + OAuth2ClientsParams sysAdminClientsParams = validClientsParams(); + + Map emptyParams = oAuth2Service.getAllOAuth2ClientsParams(); + Assert.assertTrue(emptyParams.isEmpty()); + + OAuth2ClientsParams savedTenantParams = oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, tenantClientsParams); + OAuth2ClientsParams savedSystemParams = oAuth2Service.saveSystemOAuth2ClientsParams(sysAdminClientsParams); + + Map clientsParams = oAuth2Service.getAllOAuth2ClientsParams(); + + OAuth2ClientsParams foundTenantParams = clientsParams.get(tenantId); + Assert.assertEquals(savedTenantParams, foundTenantParams); + + OAuth2ClientsParams foundSystemParams = clientsParams.get(TenantId.SYS_TENANT_ID); + Assert.assertEquals(savedSystemParams, foundSystemParams); + } + + @Test + public void testDeleteSystemOAuth2ClientsParams() { + OAuth2ClientsParams sysAdminClientsParams = validClientsParams(); + + Assert.assertNull(oAuth2Service.getSystemOAuth2ClientsParams()); + + oAuth2Service.saveSystemOAuth2ClientsParams(sysAdminClientsParams); + + Assert.assertNotNull(oAuth2Service.getSystemOAuth2ClientsParams()); + } + + @Test + public void testDeleteTenantOAuth2ClientsParams() { + OAuth2ClientsParams tenantClientsParams = validClientsParams(); + + Assert.assertNull(oAuth2Service.getTenantOAuth2ClientsParams(tenantId)); + + oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, tenantClientsParams); + + Assert.assertNotNull(oAuth2Service.getTenantOAuth2ClientsParams(tenantId)); + } + + + private void clearSysAdmin() { + oAuth2Service.deleteSystemOAuth2ClientsParams(); + Assert.assertNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAuth2Utils.OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); + } + + private void updateTenantAllowOAuth2Setting(Boolean allowOAuth2) throws IOException { + Tenant tenant = tenantService.findTenantById(tenantId); + if (allowOAuth2 == null) { + tenant.setAdditionalInfo(mapper.readTree("{}")); + } else { + String additionalInfo = "{\"" + ALLOW_OAUTH2_CONFIGURATION + "\":" + allowOAuth2 + "}"; + tenant.setAdditionalInfo(mapper.readTree(additionalInfo)); + tenantService.saveTenant(tenant); + } + } + + private OAuth2ClientsParams validClientsParams() { + OAuth2ClientRegistration first = validClientRegistration(); + OAuth2ClientRegistration second = validClientRegistration(); + return OAuth2ClientsParams.builder() + .clientsDomainsParams(Collections.singletonList( + OAuth2ClientsDomainParams.builder() + .domainName(UUID.randomUUID().toString()) + .clientRegistrations(Arrays.asList(first, second)) + .build() + )) + .build(); + } + + private OAuth2ClientsParams validClientsParamsWithMultipleDomains() { + OAuth2ClientRegistration first = validClientRegistration(); + OAuth2ClientRegistration second = validClientRegistration(); + OAuth2ClientRegistration third = validClientRegistration(); + return OAuth2ClientsParams.builder() + .clientsDomainsParams(Arrays.asList( + OAuth2ClientsDomainParams.builder() + .domainName(UUID.randomUUID().toString()) + .clientRegistrations(Arrays.asList(first, second)) + .build(), + OAuth2ClientsDomainParams.builder() + .domainName(UUID.randomUUID().toString()) + .clientRegistrations(Arrays.asList(third)) + .build() + )) + .build(); + } + + + private OAuth2ClientsParams clientsParamsWithDuplicateDomains() { + OAuth2ClientRegistration first = validClientRegistration(); + OAuth2ClientRegistration second = validClientRegistration(); + OAuth2ClientRegistration third = validClientRegistration(); + return OAuth2ClientsParams.builder() + .clientsDomainsParams(Arrays.asList( + OAuth2ClientsDomainParams.builder() + .domainName("domain") + .clientRegistrations(Collections.singletonList(first)) + .build(), + OAuth2ClientsDomainParams.builder() + .domainName("domain") + .clientRegistrations(Collections.singletonList(second)) + .build(), + OAuth2ClientsDomainParams.builder() + .domainName(UUID.randomUUID().toString()) + .clientRegistrations(Collections.singletonList(third)) + .build() + )) + .build(); + } + + private OAuth2ClientsParams clientsParamsWithDuplicateRegistrationIds() { + OAuth2ClientRegistration first = validClientRegistration(); + first.setRegistrationId("registrationId"); + OAuth2ClientRegistration second = validClientRegistration(); + OAuth2ClientRegistration third = validClientRegistration(); + third.setRegistrationId("registrationId"); + return OAuth2ClientsParams.builder() + .clientsDomainsParams(Arrays.asList( + OAuth2ClientsDomainParams.builder() + .domainName(UUID.randomUUID().toString()) + .clientRegistrations(Arrays.asList(first, second, third)) + .build() + )) + .build(); + } + + private OAuth2ClientRegistration validClientRegistration() { + return OAuth2ClientRegistration.builder() + .registrationId(UUID.randomUUID().toString()) + .mapperConfig(OAuth2MapperConfig.builder() + .allowUserCreation(true) + .activateUser(true) + .type(MapperType.CUSTOM) + .customConfig( + OAuth2CustomMapperConfig.builder() + .url("localhost:8082") + .username("test") + .password("test") + .build() + ) + .build()) + .clientId("clientId") + .clientSecret("clientSecret") + .authorizationUri("authorizationUri") + .tokenUri("tokenUri") + .redirectUriTemplate("http://localhost:8080/login/oauth2/code/") + .scope("scope") + .authorizationGrantType("authorizationGrantType") + .userInfoUri("userInfoUri") + .userNameAttributeName("userNameAttributeName") + .jwkSetUri("jwkSetUri") + .clientAuthenticationMethod("clientAuthenticationMethod") + .clientName("clientName") + .loginButtonLabel("loginButtonLabel") + .build(); + } +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/sql/OAuth2ServiceSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/sql/OAuth2ServiceSqlTest.java new file mode 100644 index 0000000000..64eba8f89a --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/service/sql/OAuth2ServiceSqlTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.service.sql; + +import org.thingsboard.server.dao.service.BaseOAuth2ServiceTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class OAuth2ServiceSqlTest extends BaseOAuth2ServiceTest { +} From 909a989e14d238c571f034277db43a9fd468686d Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Fri, 26 Jun 2020 17:41:00 +0300 Subject: [PATCH 052/463] Removed authorizationGrantType from ClientRegistration --- .../server/common/data/oauth2/OAuth2ClientRegistration.java | 1 - .../server/dao/oauth2/HybridClientRegistrationRepository.java | 2 +- .../org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java | 3 --- .../thingsboard/server/dao/service/BaseOAuth2ServiceTest.java | 1 - 4 files changed, 1 insertion(+), 6 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java index 1fc2743e73..231a8167f0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java @@ -35,7 +35,6 @@ public class OAuth2ClientRegistration { private String tokenUri; private String redirectUriTemplate; private String scope; - private String authorizationGrantType; private String userInfoUri; private String userNameAttributeName; private String jwkSetUri; diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java index 3c9743e4d3..d581899fae 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java @@ -47,7 +47,7 @@ public class HybridClientRegistrationRepository implements ClientRegistrationRep .redirectUriTemplate(localClientRegistration.getRedirectUriTemplate()) .scope(localClientRegistration.getScope().split(",")) .clientName(localClientRegistration.getClientName()) - .authorizationGrantType(new AuthorizationGrantType(localClientRegistration.getAuthorizationGrantType())) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .userInfoUri(localClientRegistration.getUserInfoUri()) .userNameAttributeName(localClientRegistration.getUserNameAttributeName()) .jwkSetUri(localClientRegistration.getJwkSetUri()) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index d6210df266..e82eca3a71 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -473,9 +473,6 @@ public class OAuth2ServiceImpl implements OAuth2Service { if (StringUtils.isEmpty(clientRegistration.getScope())) { throw new DataValidationException("Scope should be specified!"); } - if (StringUtils.isEmpty(clientRegistration.getAuthorizationGrantType())) { - throw new DataValidationException("Authorization grant type should be specified!"); - } if (StringUtils.isEmpty(clientRegistration.getUserInfoUri())) { throw new DataValidationException("User info uri should be specified!"); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java index 86fac00f1f..30cf12c2a8 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -403,7 +403,6 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { .tokenUri("tokenUri") .redirectUriTemplate("http://localhost:8080/login/oauth2/code/") .scope("scope") - .authorizationGrantType("authorizationGrantType") .userInfoUri("userInfoUri") .userNameAttributeName("userNameAttributeName") .jwkSetUri("jwkSetUri") From 7d5ceaf3112176ed62a32aa3a1a2a9fb2e1b858c Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Tue, 30 Jun 2020 14:58:34 +0300 Subject: [PATCH 053/463] Removed condition on creating ClientRegistrationRepo --- .../server/dao/oauth2/HybridClientRegistrationRepository.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java index d581899fae..26a3312376 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java @@ -24,7 +24,6 @@ import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; -@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true") @Component public class HybridClientRegistrationRepository implements ClientRegistrationRepository { From 201ffebc5af2853885b9750cf5c14c1d0bbe1c75 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Tue, 30 Jun 2020 15:10:55 +0300 Subject: [PATCH 054/463] Renamed lock in OAuth2Service --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index e82eca3a71..659601deaa 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -47,7 +47,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; import java.util.stream.Collectors; -import java.util.stream.Stream; import static org.thingsboard.server.dao.oauth2.OAuth2Utils.*; @@ -57,7 +56,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { private static final ObjectMapper mapper = new ObjectMapper(); - private final ReentrantLock cacheWriteLock = new ReentrantLock(); + private final ReentrantLock clientRegistrationSaveLock = new ReentrantLock(); private final Map clientsParams = new ConcurrentHashMap<>(); @Autowired @@ -126,7 +125,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { validateRegistrationIdUniqueness(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); - cacheWriteLock.lock(); + clientRegistrationSaveLock.lock(); try { validateRegistrationIdUniqueness(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); AdminSettings oauth2SystemAdminSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); @@ -138,7 +137,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, oauth2SystemAdminSettings); clientsParams.put(TenantId.SYS_TENANT_ID, oAuth2ClientsParams); } finally { - cacheWriteLock.unlock(); + clientRegistrationSaveLock.unlock(); } return getSystemOAuth2ClientsParams(); @@ -152,7 +151,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { validate(oAuth2ClientsParams); validateRegistrationIdUniqueness(oAuth2ClientsParams, tenantId); - cacheWriteLock.lock(); + clientRegistrationSaveLock.lock(); try { validateRegistrationIdUniqueness(oAuth2ClientsParams, tenantId); @@ -170,7 +169,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { clientsParams.put(tenantId, oAuth2ClientsParams); } finally { - cacheWriteLock.unlock(); + clientRegistrationSaveLock.unlock(); } return getTenantOAuth2ClientsParams(tenantId); From b9b8b49aefe122f6cb6370d5505a3346ec165eff Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Tue, 30 Jun 2020 15:42:37 +0300 Subject: [PATCH 055/463] Get OAuth2Params from DB (not cache) --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 659601deaa..ea8cd21557 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -297,10 +297,6 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Override public OAuth2ClientsParams getSystemOAuth2ClientsParams() { - return clientsParams.get(TenantId.SYS_TENANT_ID); - } - - private OAuth2ClientsParams getSystemOAuth2ClientsParamsFromDb() { AdminSettings oauth2ClientsParamsSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); String json = null; if (oauth2ClientsParamsSettings != null) { @@ -311,13 +307,24 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Override public OAuth2ClientsParams getTenantOAuth2ClientsParams(TenantId tenantId) { - return clientsParams.get(tenantId); + ListenableFuture jsonFuture; + if (isOAuth2ClientRegistrationAllowed(tenantId)) { + jsonFuture = getOAuth2ClientsParamsAttribute(tenantId); + } else { + jsonFuture = Futures.immediateFuture(""); + } + try { + return Futures.transform(jsonFuture, this::constructOAuth2ClientsParams, MoreExecutors.directExecutor()).get(); + } catch (InterruptedException | ExecutionException e) { + log.error("Failed to read OAuth2 Clients Params from attributes!", e); + throw new RuntimeException("Failed to read OAuth2 Clients Params from attributes!", e); + } } // TODO this is just for test, maybe there's a better way to test it without exporting to interface @Override public Map getAllOAuth2ClientsParams() { - OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParamsFromDb(); + OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParams(); ListenableFuture> jsonFuture = getAllOAuth2ClientsParamsAttribute(); try { return Futures.transform(jsonFuture, @@ -370,6 +377,25 @@ public class OAuth2ServiceImpl implements OAuth2Service { } } + private ListenableFuture getOAuth2ClientsParamsAttribute(TenantId tenantId) { + ListenableFuture> attributeKvEntriesFuture; + try { + attributeKvEntriesFuture = attributesService.find(tenantId, tenantId, DataConstants.SERVER_SCOPE, + Collections.singletonList(OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); + } catch (Exception e) { + log.error("Unable to read OAuth2 Clients Params from attributes!", e); + throw new IncorrectParameterException("Unable to read OAuth2 Clients Params from attributes!"); + } + return Futures.transform(attributeKvEntriesFuture, attributeKvEntries -> { + if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) { + AttributeKvEntry kvEntry = attributeKvEntries.get(0); + return kvEntry.getValueAsString(); + } else { + return ""; + } + }, MoreExecutors.directExecutor()); + } + // TODO maybe it's better to load all tenants and get attribute for each one private ListenableFuture> getAllOAuth2ClientsParamsAttribute() { ListenableFuture> entityAttributeKvEntriesFuture; From b7321716117a411d63a71290502fc356b34aee38 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Tue, 30 Jun 2020 16:05:10 +0300 Subject: [PATCH 056/463] Removed cache from OAuth2Service --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 21 ++++--------------- .../dao/service/BaseOAuth2ServiceTest.java | 16 +++++++------- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index ea8cd21557..0e9c7f8f7c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -57,7 +57,6 @@ public class OAuth2ServiceImpl implements OAuth2Service { private static final ObjectMapper mapper = new ObjectMapper(); private final ReentrantLock clientRegistrationSaveLock = new ReentrantLock(); - private final Map clientsParams = new ConcurrentHashMap<>(); @Autowired private Environment environment; @@ -75,18 +74,9 @@ public class OAuth2ServiceImpl implements OAuth2Service { return environment.acceptsProfiles("install"); } - @PostConstruct - public void init() { - if (isInstall()) return; - - Map allOAuth2ClientsParams = getAllOAuth2ClientsParams(); - - allOAuth2ClientsParams.forEach(clientsParams::put); - } - @Override public Pair getClientRegistrationWithTenant(String registrationId) { - return clientsParams.entrySet().stream() + return getAllOAuth2ClientsParams().entrySet().stream() .map(entry -> { TenantId tenantId = entry.getKey(); OAuth2ClientRegistration clientRegistration = toClientRegistrationStream(entry.getValue()) @@ -135,7 +125,6 @@ public class OAuth2ServiceImpl implements OAuth2Service { String json = toJson(oAuth2ClientsParams); ((ObjectNode) oauth2SystemAdminSettings.getJsonValue()).put(SYSTEM_SETTINGS_OAUTH2_VALUE, json); adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, oauth2SystemAdminSettings); - clientsParams.put(TenantId.SYS_TENANT_ID, oAuth2ClientsParams); } finally { clientRegistrationSaveLock.unlock(); } @@ -167,7 +156,6 @@ public class OAuth2ServiceImpl implements OAuth2Service { throw new IncorrectParameterException("Unable to save OAuth2 Client Registration Params to attributes!"); } - clientsParams.put(tenantId, oAuth2ClientsParams); } finally { clientRegistrationSaveLock.unlock(); } @@ -258,7 +246,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { toClientRegistrationStream(inputOAuth2ClientsParams) .map(OAuth2ClientRegistration::getRegistrationId) .forEach(registrationId -> { - clientsParams.forEach((paramsTenantId, oAuth2ClientsParams) -> { + getAllOAuth2ClientsParams().forEach((paramsTenantId, oAuth2ClientsParams) -> { boolean registrationExists = toClientRegistrationStream(oAuth2ClientsParams) .map(OAuth2ClientRegistration::getRegistrationId) .anyMatch(registrationId::equals); @@ -352,23 +340,22 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Override public void deleteTenantOAuth2ClientsParams(TenantId tenantId) { OAuth2ClientsParams params = getTenantOAuth2ClientsParams(tenantId); - if (params == null) return; + if (params == null || params.getClientsDomainsParams() == null) return; OAuth2ClientsDomainParams domainParams = params.getClientsDomainsParams().get(0); String settingsKey = constructAdminSettingsDomainKey(domainParams.getDomainName()); adminSettingsService.deleteAdminSettingsByKey(tenantId, settingsKey); attributesService.removeAll(tenantId, tenantId, DataConstants.SERVER_SCOPE, Collections.singletonList(OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); - clientsParams.remove(tenantId); } @Override public void deleteSystemOAuth2ClientsParams() { adminSettingsService.deleteAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAuth2Utils.OAUTH2_CLIENT_REGISTRATIONS_PARAMS); - clientsParams.remove(TenantId.SYS_TENANT_ID); } @Override public boolean isOAuth2ClientRegistrationAllowed(TenantId tenantId) { Tenant tenant = tenantService.findTenantById(tenantId); + if (tenant == null) return false; JsonNode allowOAuth2ConfigurationJsonNode = tenant.getAdditionalInfo() != null ? tenant.getAdditionalInfo().get(ALLOW_OAUTH2_CONFIGURATION) : null; if (allowOAuth2ConfigurationJsonNode == null) { return true; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java index 30cf12c2a8..09fec5af4a 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -55,8 +55,8 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { Assert.assertNotNull(savedTenant); tenantId = savedTenant.getId(); - Assert.assertNull(oAuth2Service.getSystemOAuth2ClientsParams()); - Assert.assertNull(oAuth2Service.getTenantOAuth2ClientsParams(tenantId)); + Assert.assertNull(oAuth2Service.getSystemOAuth2ClientsParams().getClientsDomainsParams()); + Assert.assertNull(oAuth2Service.getTenantOAuth2ClientsParams(tenantId).getClientsDomainsParams()); Assert.assertTrue(attributesService.findAll(tenantId, tenantId, DataConstants.SERVER_SCOPE).get().isEmpty()); Assert.assertNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAuth2Utils.OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); @@ -68,8 +68,8 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { tenantService.deleteTenant(tenantId); - Assert.assertNull(oAuth2Service.getSystemOAuth2ClientsParams()); - Assert.assertNull(oAuth2Service.getTenantOAuth2ClientsParams(tenantId)); + Assert.assertNull(oAuth2Service.getSystemOAuth2ClientsParams().getClientsDomainsParams()); + Assert.assertNull(oAuth2Service.getTenantOAuth2ClientsParams(tenantId).getClientsDomainsParams()); Assert.assertTrue(attributesService.findAll(tenantId, tenantId, DataConstants.SERVER_SCOPE).get().isEmpty()); Assert.assertNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAuth2Utils.OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); @@ -277,22 +277,22 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { public void testDeleteSystemOAuth2ClientsParams() { OAuth2ClientsParams sysAdminClientsParams = validClientsParams(); - Assert.assertNull(oAuth2Service.getSystemOAuth2ClientsParams()); + Assert.assertNull(oAuth2Service.getSystemOAuth2ClientsParams().getClientsDomainsParams()); oAuth2Service.saveSystemOAuth2ClientsParams(sysAdminClientsParams); - Assert.assertNotNull(oAuth2Service.getSystemOAuth2ClientsParams()); + Assert.assertNotNull(oAuth2Service.getSystemOAuth2ClientsParams().getClientsDomainsParams()); } @Test public void testDeleteTenantOAuth2ClientsParams() { OAuth2ClientsParams tenantClientsParams = validClientsParams(); - Assert.assertNull(oAuth2Service.getTenantOAuth2ClientsParams(tenantId)); + Assert.assertNull(oAuth2Service.getTenantOAuth2ClientsParams(tenantId).getClientsDomainsParams()); oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, tenantClientsParams); - Assert.assertNotNull(oAuth2Service.getTenantOAuth2ClientsParams(tenantId)); + Assert.assertNotNull(oAuth2Service.getTenantOAuth2ClientsParams(tenantId).getClientsDomainsParams()); } From 64a7b160fc7fd4d051ff6efed099eeceea30fe1e Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 6 Jul 2020 11:28:43 +0300 Subject: [PATCH 057/463] Add license header --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 8 ++++---- .../server/dao/oauth2/OAuth2Utils.java | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 0e9c7f8f7c..7e681e4f6e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -1,12 +1,12 @@ /** * Copyright © 2016-2020 The Thingsboard Authors - *

+ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

+ * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java index faa96e1d4f..003f87cc73 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.dao.oauth2; import org.springframework.util.StringUtils; From e4777d9267ea5f7fe752176d54bf28880a7b2a08 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Mon, 6 Jul 2020 12:26:38 +0300 Subject: [PATCH 058/463] Fixed nullpointers, refactored, added test --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 45 +++++++++++++------ .../dao/service/BaseOAuth2ServiceTest.java | 6 +++ 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 0e9c7f8f7c..f7485461f7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -403,16 +403,22 @@ public class OAuth2ServiceImpl implements OAuth2Service { } private OAuth2ClientsDomainParams getMergedOAuth2ClientsParams(String domainName) { - AdminSettings oauth2ClientsSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, constructAdminSettingsDomainKey(domainName)); - OAuth2ClientsDomainParams result; + OAuth2ClientsDomainParams result = OAuth2ClientsDomainParams.builder() + .domainName(domainName) + .clientRegistrations(new ArrayList<>()) + .build(); OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParams(); - OAuth2ClientsDomainParams systemOAuth2ClientsDomainParams = systemOAuth2ClientsParams != null ? + OAuth2ClientsDomainParams systemOAuth2ClientsDomainParams = systemOAuth2ClientsParams != null && systemOAuth2ClientsParams.getClientsDomainsParams() != null ? systemOAuth2ClientsParams.getClientsDomainsParams().stream() .filter(oAuth2ClientsDomainParams -> domainName.equals(oAuth2ClientsDomainParams.getDomainName())) .findFirst() .orElse(null) : null; + + result = mergeDomainParams(result, systemOAuth2ClientsDomainParams); + + AdminSettings oauth2ClientsSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, constructAdminSettingsDomainKey(domainName)); if (oauth2ClientsSettings != null) { String strEntityType = oauth2ClientsSettings.getJsonValue().get("entityType").asText(); String strEntityId = oauth2ClientsSettings.getJsonValue().get("entityId").asText(); @@ -422,20 +428,33 @@ public class OAuth2ServiceImpl implements OAuth2Service { throw new IllegalStateException("Only tenant can configure OAuth2 for certain domain!"); } TenantId tenantId = (TenantId) entityId; - result = getTenantOAuth2ClientsParams(tenantId).getClientsDomainsParams().get(0); - if (systemOAuth2ClientsDomainParams != null) { - ArrayList tenantClientRegistrations = new ArrayList<>(result.getClientRegistrations()); - tenantClientRegistrations.addAll(systemOAuth2ClientsDomainParams.getClientRegistrations()); - result = result.toBuilder() - .clientRegistrations(tenantClientRegistrations) - .build(); - } - } else { - result = systemOAuth2ClientsDomainParams; + OAuth2ClientsParams tenantOAuth2ClientsParams = getTenantOAuth2ClientsParams(tenantId); + OAuth2ClientsDomainParams tenantDomainsParams = tenantOAuth2ClientsParams != null && tenantOAuth2ClientsParams.getClientsDomainsParams() != null ? + tenantOAuth2ClientsParams.getClientsDomainsParams().stream().findFirst().orElse(null) : null; + result = mergeDomainParams(result, tenantDomainsParams); } return result; } + private OAuth2ClientsDomainParams mergeDomainParams(OAuth2ClientsDomainParams sourceParams, OAuth2ClientsDomainParams newParams){ + if (newParams == null) return sourceParams; + + OAuth2ClientsDomainParams.OAuth2ClientsDomainParamsBuilder mergedParamsBuilder = sourceParams.toBuilder(); + + if (newParams.getClientRegistrations() != null){ + List mergedClientRegistrations = sourceParams.getClientRegistrations() != null ? + sourceParams.getClientRegistrations() : new ArrayList<>(); + mergedClientRegistrations.addAll(newParams.getClientRegistrations()); + mergedParamsBuilder.clientRegistrations(mergedClientRegistrations); + } + + if (newParams.getAdminSettingsId() != null){ + mergedParamsBuilder.adminSettingsId(newParams.getAdminSettingsId()); + } + + return mergedParamsBuilder.build(); + } + private OAuth2ClientsParams constructOAuth2ClientsParams(String json) { OAuth2ClientsParams result = null; if (!StringUtils.isEmpty(json)) { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java index 09fec5af4a..b5e8280eee 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -253,6 +253,12 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { Assert.assertEquals(actualLabels, foundLabels); } + @Test + public void testGetEmptyOAuth2Clients() { + List oAuth2Clients = oAuth2Service.getOAuth2Clients("random-domain"); + Assert.assertTrue(oAuth2Clients.isEmpty()); + } + @Test public void testGetAllOAuth2ClientsParams() { OAuth2ClientsParams tenantClientsParams = validClientsParams(); From 60857dd7336a8d9485a80c0119b0610b3013f1fb Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Tue, 7 Jul 2020 10:59:44 +0300 Subject: [PATCH 059/463] Refactored OAuth2Controller --- .../server/controller/OAuth2Controller.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index 8d94cee08e..b3b2b1eb83 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -40,8 +40,6 @@ import java.util.List; @RequestMapping("/api") @Slf4j public class OAuth2Controller extends BaseController { - private static final String REGISTRATION_ID = "registrationId"; - @Autowired private OAuth2Service oauth2Service; @@ -57,9 +55,9 @@ public class OAuth2Controller extends BaseController { } @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - @RequestMapping(value = "/oauth2/currentOAuth2Configuration", method = RequestMethod.GET, produces = "application/json") + @RequestMapping(value = "/oauth2/config", method = RequestMethod.GET, produces = "application/json") @ResponseBody - public OAuth2ClientsParams getCurrentOAuth2ClientsParams() throws ThingsboardException { + public OAuth2ClientsParams getCurrentClientsParams() throws ThingsboardException { try { Authority authority = getCurrentUser().getAuthority(); checkOAuth2ConfigPermissions(Operation.READ); @@ -76,9 +74,9 @@ public class OAuth2Controller extends BaseController { } @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - @RequestMapping(value = "/oauth2/oAuth2Configuration", method = RequestMethod.POST) + @RequestMapping(value = "/oauth2/config", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) - public OAuth2ClientsParams saveLoginWhiteLabelParams(@RequestBody OAuth2ClientsParams oAuth2ClientsParams) throws ThingsboardException { + public OAuth2ClientsParams saveClientParams(@RequestBody OAuth2ClientsParams oAuth2ClientsParams) throws ThingsboardException { try { Authority authority = getCurrentUser().getAuthority(); checkOAuth2ConfigPermissions(Operation.WRITE); @@ -95,7 +93,7 @@ public class OAuth2Controller extends BaseController { } @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/oauth2/isOAuth2ConfigurationAllowed", method = RequestMethod.GET) + @RequestMapping(value = "/oauth2/config/isAllowed", method = RequestMethod.GET) @ResponseBody public Boolean isOAuth2ConfigurationAllowed() throws ThingsboardException { try { @@ -105,7 +103,6 @@ public class OAuth2Controller extends BaseController { } } - private void checkOAuth2ConfigPermissions(Operation operation) throws ThingsboardException { accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION, operation); } From 8590bcb7093da60d8696e92afdedb3d3587ae107 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Tue, 7 Jul 2020 12:10:35 +0300 Subject: [PATCH 060/463] Refactored OAuth2Service --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 64 +++++++++---------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 0d8af953df..e915dc8bb8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -114,7 +114,6 @@ public class OAuth2ServiceImpl implements OAuth2Service { validate(oAuth2ClientsParams); validateRegistrationIdUniqueness(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); - clientRegistrationSaveLock.lock(); try { validateRegistrationIdUniqueness(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); @@ -190,8 +189,8 @@ public class OAuth2ServiceImpl implements OAuth2Service { ); if (existentOAuth2ClientsSettingsById == null) { - log.error("Admin setting ID is already set in login white labeling object, but doesn't exist in the database"); - throw new IllegalStateException("Admin setting ID is already set in login white labeling object, but doesn't exist in the database"); + log.error("Admin setting ID is already set in OAuth2 Client Params object, but doesn't exist in the database"); + throw new IllegalStateException("Admin setting ID is already set in OAuth2 Client Params object, but doesn't exist in the database"); } if (!existentOAuth2ClientsSettingsById.getKey().equals(selectedDomainSettingsKey)) { @@ -234,28 +233,27 @@ public class OAuth2ServiceImpl implements OAuth2Service { } private void validateRegistrationIdUniqueness(OAuth2ClientsParams inputOAuth2ClientsParams, TenantId tenantId) { - long distinctRegistrationIds = toClientRegistrationStream(inputOAuth2ClientsParams) + List registrationIds = toClientRegistrationStream(inputOAuth2ClientsParams) .map(OAuth2ClientRegistration::getRegistrationId) - .distinct() - .count(); - long actualRegistrationIds = toClientRegistrationStream(inputOAuth2ClientsParams).count(); - if (distinctRegistrationIds != actualRegistrationIds) { + .collect(Collectors.toList()); + + boolean regIdDuplicates = registrationIds.stream() + .anyMatch(registrationId -> Collections.frequency(registrationIds, registrationId) > 1); + if (regIdDuplicates) { throw new DataValidationException("All registration IDs should be unique!"); } - toClientRegistrationStream(inputOAuth2ClientsParams) - .map(OAuth2ClientRegistration::getRegistrationId) - .forEach(registrationId -> { - getAllOAuth2ClientsParams().forEach((paramsTenantId, oAuth2ClientsParams) -> { - boolean registrationExists = toClientRegistrationStream(oAuth2ClientsParams) - .map(OAuth2ClientRegistration::getRegistrationId) - .anyMatch(registrationId::equals); - if (registrationExists && !tenantId.equals(paramsTenantId)) { - log.error("Current registrationId [{}] already registered in the system!", registrationId); - throw new IncorrectParameterException("Current registrationId [" + registrationId + "] already registered in the system!"); - } - }); - }); + getAllOAuth2ClientsParams().forEach((paramsTenantId, oAuth2ClientsParams) -> { + if (tenantId.equals(paramsTenantId)) return; + Set duplicatedRegistrationIds = toClientRegistrationStream(oAuth2ClientsParams) + .map(OAuth2ClientRegistration::getRegistrationId) + .filter(registrationIds::contains) + .collect(Collectors.toSet()); + if (!duplicatedRegistrationIds.isEmpty()) { + log.error("RegistrationIds [{}] are already registered in the system!", duplicatedRegistrationIds); + throw new IncorrectParameterException("RegistrationIds [" + duplicatedRegistrationIds + "] are already registered in the system!"); + } + }); } private void validate(OAuth2ClientsParams oAuth2ClientsParams) { @@ -266,19 +264,19 @@ public class OAuth2ServiceImpl implements OAuth2Service { } private void validateDomainNames(OAuth2ClientsParams oAuth2ClientsParams) { - oAuth2ClientsParams.getClientsDomainsParams() - .forEach(oAuth2ClientsDomainParams -> { - if (StringUtils.isEmpty(oAuth2ClientsDomainParams.getDomainName())) { - throw new DataValidationException("Domain name should be specified!"); - } - }); - - long distinctDomainNames = oAuth2ClientsParams.getClientsDomainsParams().stream() + List domainNames = oAuth2ClientsParams.getClientsDomainsParams().stream() .map(OAuth2ClientsDomainParams::getDomainName) - .distinct() - .count(); - long actualDomainNames = oAuth2ClientsParams.getClientsDomainsParams().size(); - if (distinctDomainNames != actualDomainNames) { + .collect(Collectors.toList()); + + domainNames.forEach(domainName -> { + if (StringUtils.isEmpty(domainName)) { + throw new DataValidationException("Domain name should be specified!"); + } + }); + + boolean duplicateDomainNames = domainNames.stream() + .anyMatch(domainName -> Collections.frequency(domainNames, domainName) > 1); + if (duplicateDomainNames) { throw new DataValidationException("All domain names should be unique!"); } } From 7468f4b713bc1563d86d515cbf09746649c582df Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Tue, 7 Jul 2020 13:03:11 +0300 Subject: [PATCH 061/463] Added code for validating AdminSettingsId --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index e915dc8bb8..5ce6256816 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -258,6 +258,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { private void validate(OAuth2ClientsParams oAuth2ClientsParams) { validateDomainNames(oAuth2ClientsParams); + validateAdminSettingsIds(oAuth2ClientsParams); toClientRegistrationStream(oAuth2ClientsParams) .forEach(validator); @@ -281,6 +282,19 @@ public class OAuth2ServiceImpl implements OAuth2Service { } } + private void validateAdminSettingsIds(OAuth2ClientsParams oAuth2ClientsParams) { + List adminSettingsIds = oAuth2ClientsParams.getClientsDomainsParams().stream() + .map(OAuth2ClientsDomainParams::getAdminSettingsId) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + boolean duplicateAdminSettingsIds = adminSettingsIds.stream() + .anyMatch(adminSettingsId -> Collections.frequency(adminSettingsIds, adminSettingsId) > 1); + if (duplicateAdminSettingsIds) { + throw new DataValidationException("All admin settings ids should be unique!"); + } + } + @Override public OAuth2ClientsParams getSystemOAuth2ClientsParams() { AdminSettings oauth2ClientsParamsSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); From 65d075035f19ec65f700e5b27530dec362281f85 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Tue, 7 Jul 2020 15:07:20 +0300 Subject: [PATCH 062/463] Removed AdminSettingsId from ClientParams --- .../oauth2/OAuth2ClientsDomainParams.java | 2 - .../server/dao/oauth2/OAuth2ServiceImpl.java | 103 +++++++----------- .../dao/service/BaseOAuth2ServiceTest.java | 92 ++++++++++++++-- 3 files changed, 123 insertions(+), 74 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java index d9dc07d8ea..3ebfa783c2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java @@ -27,7 +27,5 @@ import java.util.List; @AllArgsConstructor public class OAuth2ClientsDomainParams { private String domainName; - private String adminSettingsId; - private List clientRegistrations; } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 5ce6256816..8857e6ceb9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -133,9 +133,6 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Override public OAuth2ClientsParams saveTenantOAuth2ClientsParams(TenantId tenantId, OAuth2ClientsParams oAuth2ClientsParams) { - if (oAuth2ClientsParams.getClientsDomainsParams().size() != 1) { - throw new DataValidationException("Tenant can configure OAuth2 only for one domain!"); - } validate(oAuth2ClientsParams); validateRegistrationIdUniqueness(oAuth2ClientsParams, tenantId); @@ -143,9 +140,10 @@ public class OAuth2ServiceImpl implements OAuth2Service { try { validateRegistrationIdUniqueness(oAuth2ClientsParams, tenantId); - OAuth2ClientsDomainParams oAuth2ClientsDomainParams = oAuth2ClientsParams.getClientsDomainsParams().get(0); - String adminSettingsId = processTenantAdminSettings(tenantId, oAuth2ClientsDomainParams.getDomainName(), oAuth2ClientsDomainParams.getAdminSettingsId()); - oAuth2ClientsDomainParams.setAdminSettingsId(adminSettingsId); + Set domainNames = oAuth2ClientsParams.getClientsDomainsParams().stream() + .map(OAuth2ClientsDomainParams::getDomainName) + .collect(Collectors.toSet()); + processTenantAdminSettings(tenantId, domainNames); List attributes = createOAuth2ClientsParamsAttributes(oAuth2ClientsParams); try { @@ -170,46 +168,40 @@ public class OAuth2ServiceImpl implements OAuth2Service { return attributes; } - private String processTenantAdminSettings(TenantId tenantId, String domainName, String prevAdminSettingsId) { - String selectedDomainSettingsKey = constructAdminSettingsDomainKey(domainName); - AdminSettings existentAdminSettingsByKey = adminSettingsService.findAdminSettingsByKey(tenantId, selectedDomainSettingsKey); - if (StringUtils.isEmpty(prevAdminSettingsId)) { - if (existentAdminSettingsByKey == null) { - AdminSettings tenantAdminSettings = createTenantAdminSettings(tenantId, selectedDomainSettingsKey); - existentAdminSettingsByKey = adminSettingsService.saveAdminSettings(tenantId, tenantAdminSettings); - return existentAdminSettingsByKey.getId().getId().toString(); - } else { + private void processTenantAdminSettings(TenantId tenantId, Set domainNames) { + OAuth2ClientsParams existentClientsParams = getTenantOAuth2ClientsParams(tenantId); + + Set existentDomainNames = existentClientsParams != null && existentClientsParams.getClientsDomainsParams() != null ? + existentClientsParams.getClientsDomainsParams().stream() + .map(OAuth2ClientsDomainParams::getDomainName) + .collect(Collectors.toSet()) + : Collections.emptySet(); + + Set domainNamesToAdd = domainNames.stream() + .filter(domainName -> !existentDomainNames.contains(domainName)) + .collect(Collectors.toSet()); + Set domainNamesToDelete = existentDomainNames.stream() + .filter(domainName -> !domainNames.contains(domainName)) + .collect(Collectors.toSet()); + + domainNamesToAdd.forEach(domainName -> { + String domainSettingsKey = constructAdminSettingsDomainKey(domainName); + if (adminSettingsService.findAdminSettingsByKey(tenantId, domainSettingsKey) != null) { log.error("Current domain name [{}] already registered in the system!", domainName); throw new IncorrectParameterException("Current domain name [" + domainName + "] already registered in the system!"); } - } else { - AdminSettings existentOAuth2ClientsSettingsById = adminSettingsService.findAdminSettingsById( - tenantId, - new AdminSettingsId(UUID.fromString(prevAdminSettingsId)) - ); - - if (existentOAuth2ClientsSettingsById == null) { - log.error("Admin setting ID is already set in OAuth2 Client Params object, but doesn't exist in the database"); - throw new IllegalStateException("Admin setting ID is already set in OAuth2 Client Params object, but doesn't exist in the database"); - } + }); - if (!existentOAuth2ClientsSettingsById.getKey().equals(selectedDomainSettingsKey)) { - if (existentAdminSettingsByKey == null) { - AdminSettings newOAuth2ClientsSettings = replaceExistentAdminSettings(tenantId, selectedDomainSettingsKey, existentOAuth2ClientsSettingsById.getKey()); - return newOAuth2ClientsSettings.getId().getId().toString(); - } else { - log.error("Current domain name [{}] already registered in the system!", domainName); - throw new IncorrectParameterException("Current domain name [" + domainName + "] already registered in the system!"); - } - } - return prevAdminSettingsId; - } - } + domainNamesToAdd.forEach(domainName -> { + String domainSettingsKey = constructAdminSettingsDomainKey(domainName); + AdminSettings tenantAdminSettings = createTenantAdminSettings(tenantId, domainSettingsKey); + adminSettingsService.saveAdminSettings(tenantId, tenantAdminSettings); + }); - private AdminSettings replaceExistentAdminSettings(TenantId tenantId, String newKey, String oldKey) { - adminSettingsService.deleteAdminSettingsByKey(tenantId, oldKey); - AdminSettings tenantAdminSettings = createTenantAdminSettings(tenantId, newKey); - return adminSettingsService.saveAdminSettings(tenantId, tenantAdminSettings); + domainNamesToDelete.forEach(domainName -> { + String domainSettingsKey = constructAdminSettingsDomainKey(domainName); + adminSettingsService.deleteAdminSettingsByKey(tenantId, domainSettingsKey); + }); } private AdminSettings createTenantAdminSettings(TenantId tenantId, String clientRegistrationsKey) { @@ -258,7 +250,6 @@ public class OAuth2ServiceImpl implements OAuth2Service { private void validate(OAuth2ClientsParams oAuth2ClientsParams) { validateDomainNames(oAuth2ClientsParams); - validateAdminSettingsIds(oAuth2ClientsParams); toClientRegistrationStream(oAuth2ClientsParams) .forEach(validator); @@ -282,19 +273,6 @@ public class OAuth2ServiceImpl implements OAuth2Service { } } - private void validateAdminSettingsIds(OAuth2ClientsParams oAuth2ClientsParams) { - List adminSettingsIds = oAuth2ClientsParams.getClientsDomainsParams().stream() - .map(OAuth2ClientsDomainParams::getAdminSettingsId) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - - boolean duplicateAdminSettingsIds = adminSettingsIds.stream() - .anyMatch(adminSettingsId -> Collections.frequency(adminSettingsIds, adminSettingsId) > 1); - if (duplicateAdminSettingsIds) { - throw new DataValidationException("All admin settings ids should be unique!"); - } - } - @Override public OAuth2ClientsParams getSystemOAuth2ClientsParams() { AdminSettings oauth2ClientsParamsSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); @@ -353,9 +331,10 @@ public class OAuth2ServiceImpl implements OAuth2Service { public void deleteTenantOAuth2ClientsParams(TenantId tenantId) { OAuth2ClientsParams params = getTenantOAuth2ClientsParams(tenantId); if (params == null || params.getClientsDomainsParams() == null) return; - OAuth2ClientsDomainParams domainParams = params.getClientsDomainsParams().get(0); - String settingsKey = constructAdminSettingsDomainKey(domainParams.getDomainName()); - adminSettingsService.deleteAdminSettingsByKey(tenantId, settingsKey); + params.getClientsDomainsParams().forEach(domainParams -> { + String settingsKey = constructAdminSettingsDomainKey(domainParams.getDomainName()); + adminSettingsService.deleteAdminSettingsByKey(tenantId, settingsKey); + }); attributesService.removeAll(tenantId, tenantId, DataConstants.SERVER_SCOPE, Collections.singletonList(OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); } @@ -448,22 +427,18 @@ public class OAuth2ServiceImpl implements OAuth2Service { return result; } - private OAuth2ClientsDomainParams mergeDomainParams(OAuth2ClientsDomainParams sourceParams, OAuth2ClientsDomainParams newParams){ + private OAuth2ClientsDomainParams mergeDomainParams(OAuth2ClientsDomainParams sourceParams, OAuth2ClientsDomainParams newParams) { if (newParams == null) return sourceParams; OAuth2ClientsDomainParams.OAuth2ClientsDomainParamsBuilder mergedParamsBuilder = sourceParams.toBuilder(); - if (newParams.getClientRegistrations() != null){ + if (newParams.getClientRegistrations() != null) { List mergedClientRegistrations = sourceParams.getClientRegistrations() != null ? sourceParams.getClientRegistrations() : new ArrayList<>(); mergedClientRegistrations.addAll(newParams.getClientRegistrations()); mergedParamsBuilder.clientRegistrations(mergedClientRegistrations); } - if (newParams.getAdminSettingsId() != null){ - mergedParamsBuilder.adminSettingsId(newParams.getAdminSettingsId()); - } - return mergedParamsBuilder.build(); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java index b5e8280eee..94b58fb5ce 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -114,11 +114,6 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, clientsParamsWithDuplicateRegistrationIds()); } - @Test(expected = DataValidationException.class) - public void testSaveTenantParamsWithMultipleDomains() { - oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, validClientsParamsWithMultipleDomains()); - } - @Test public void testSaveSystemParams() { OAuth2ClientsParams clientsParams = validClientsParams(); @@ -130,7 +125,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testSaveSystemParamsWithMultipleDomains() { - OAuth2ClientsParams clientsParams = validClientsParamsWithMultipleDomains(); + OAuth2ClientsParams clientsParams = validClientsParamsWithThreeDomains(); OAuth2ClientsParams savedClientParams = oAuth2Service.saveSystemOAuth2ClientsParams(clientsParams); Assert.assertNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAuth2Utils.OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); @@ -163,11 +158,81 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { Assert.assertNotNull(savedClientParams); OAuth2ClientsDomainParams savedDomainParams = savedClientParams.getClientsDomainsParams().get(0); - Assert.assertNotNull(savedDomainParams.getAdminSettingsId()); Assert.assertEquals(domainParams.getDomainName(), savedDomainParams.getDomainName()); Assert.assertEquals(domainParams.getClientRegistrations(), savedDomainParams.getClientRegistrations()); } + @Test + public void testSaveTenantMultipleParams() { + OAuth2ClientsParams clientsParams = validClientsParamsWithThreeDomains(); + + clientsParams.getClientsDomainsParams().forEach(oAuth2ClientsDomainParams -> { + String domainName = oAuth2ClientsDomainParams.getDomainName(); + String domainKey = OAuth2Utils.constructAdminSettingsDomainKey(domainName); + Assert.assertNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); + }); + + OAuth2ClientsParams savedClientParams = oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, clientsParams); + Assert.assertNotNull(savedClientParams); + + clientsParams.getClientsDomainsParams().forEach(oAuth2ClientsDomainParams -> { + String domainName = oAuth2ClientsDomainParams.getDomainName(); + String domainKey = OAuth2Utils.constructAdminSettingsDomainKey(domainName); + Assert.assertNotNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); + }); + + Assert.assertEquals(clientsParams, savedClientParams); + } + + @Test + public void testRewriteSameDomainTenantParams() { + OAuth2ClientsParams clientsParams = validClientsParamsWithThreeDomains(); + oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, clientsParams); + + List clientsDomainsParams = clientsParams.getClientsDomainsParams(); + OAuth2ClientsParams updatedClientsParams = validClientsParamsWithThreeDomains(); + String sameDomainName = clientsDomainsParams.get(0).getDomainName(); + updatedClientsParams.getClientsDomainsParams().get(0).setDomainName(sameDomainName); + OAuth2ClientsParams rewrittenClientParams = oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, updatedClientsParams); + Assert.assertEquals(updatedClientsParams, rewrittenClientParams); + + clientsParams.getClientsDomainsParams().forEach(oAuth2ClientsDomainParams -> { + String domainName = oAuth2ClientsDomainParams.getDomainName(); + String domainKey = OAuth2Utils.constructAdminSettingsDomainKey(domainName); + if (domainName.equals(sameDomainName)) { + Assert.assertNotNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); + } else { + Assert.assertNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); + } + }); + updatedClientsParams.getClientsDomainsParams().forEach(oAuth2ClientsDomainParams -> { + String domainName = oAuth2ClientsDomainParams.getDomainName(); + String domainKey = OAuth2Utils.constructAdminSettingsDomainKey(domainName); + Assert.assertNotNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); + }); + } + + @Test + public void testAddDeleteTenantDomainParams() { + OAuth2ClientsParams clientsParams = validClientsParamsWithThreeDomains(); + oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, clientsParams); + + List clientsDomainsParams = clientsParams.getClientsDomainsParams(); + OAuth2ClientsParams updatedClientsParams = validClientsParamsWithThreeDomains(); + for (int i = 0; i < updatedClientsParams.getClientsDomainsParams().size(); i++) { + String domainName = clientsDomainsParams.get(i).getDomainName(); + updatedClientsParams.getClientsDomainsParams().get(i).setDomainName(domainName); + } + OAuth2ClientsParams rewrittenClientParams = oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, updatedClientsParams); + Assert.assertEquals(updatedClientsParams, rewrittenClientParams); + + clientsParams.getClientsDomainsParams().forEach(oAuth2ClientsDomainParams -> { + String domainName = oAuth2ClientsDomainParams.getDomainName(); + String domainKey = OAuth2Utils.constructAdminSettingsDomainKey(domainName); + Assert.assertNotNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); + }); + } + @Test public void testFindTenantParams() { OAuth2ClientsParams clientsParams = validClientsParams(); @@ -288,6 +353,9 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { oAuth2Service.saveSystemOAuth2ClientsParams(sysAdminClientsParams); Assert.assertNotNull(oAuth2Service.getSystemOAuth2ClientsParams().getClientsDomainsParams()); + + oAuth2Service.deleteSystemOAuth2ClientsParams(); + Assert.assertNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAuth2Utils.OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); } @Test @@ -299,6 +367,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, tenantClientsParams); Assert.assertNotNull(oAuth2Service.getTenantOAuth2ClientsParams(tenantId).getClientsDomainsParams()); + + oAuth2Service.deleteTenantOAuth2ClientsParams(tenantId); + Assert.assertNull(oAuth2Service.getTenantOAuth2ClientsParams(tenantId).getClientsDomainsParams()); + tenantClientsParams.getClientsDomainsParams().forEach(oAuth2ClientsDomainParams -> { + String domainName = oAuth2ClientsDomainParams.getDomainName(); + String domainKey = OAuth2Utils.constructAdminSettingsDomainKey(domainName); + Assert.assertNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); + }); } @@ -331,7 +407,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { .build(); } - private OAuth2ClientsParams validClientsParamsWithMultipleDomains() { + private OAuth2ClientsParams validClientsParamsWithThreeDomains() { OAuth2ClientRegistration first = validClientRegistration(); OAuth2ClientRegistration second = validClientRegistration(); OAuth2ClientRegistration third = validClientRegistration(); From 5aba253928a216a2c8952c7f26989801d46378e3 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 9 Jul 2020 12:11:49 +0300 Subject: [PATCH 063/463] Changed OAuth2ClientsDomainParams protocol --- .../auth/oauth2/BasicOAuth2ClientMapper.java | 30 ++++---- .../auth/oauth2/CustomOAuth2ClientMapper.java | 2 +- .../server/dao/oauth2/OAuth2Service.java | 3 +- .../ExtendedOAuth2ClientRegistration.java | 28 ++++++++ .../data/oauth2/OAuth2ClientRegistration.java | 7 +- .../oauth2/OAuth2ClientsDomainParams.java | 1 + .../data/oauth2/OAuth2MapperConfig.java | 4 +- .../HybridClientRegistrationRepository.java | 16 ++--- .../server/dao/oauth2/OAuth2ServiceImpl.java | 68 +++++++++++-------- .../server/dao/SqlDaoServiceTestSuite.java | 2 +- .../dao/service/BaseOAuth2ServiceTest.java | 24 ++++--- 11 files changed, 116 insertions(+), 69 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistration.java diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java index 19ed540543..8c7c4d9bb4 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java @@ -38,42 +38,42 @@ public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implemen public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, TenantId parentTenantId, OAuth2MapperConfig config) { OAuth2User oauth2User = new OAuth2User(); Map attributes = token.getPrincipal().getAttributes(); - String email = getStringAttributeByKey(attributes, config.getBasicConfig().getEmailAttributeKey()); + String email = getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); oauth2User.setEmail(email); oauth2User.setTenantName(getTenantName(attributes, config)); - if (!StringUtils.isEmpty(config.getBasicConfig().getLastNameAttributeKey())) { - String lastName = getStringAttributeByKey(attributes, config.getBasicConfig().getLastNameAttributeKey()); + if (!StringUtils.isEmpty(config.getBasic().getLastNameAttributeKey())) { + String lastName = getStringAttributeByKey(attributes, config.getBasic().getLastNameAttributeKey()); oauth2User.setLastName(lastName); } - if (!StringUtils.isEmpty(config.getBasicConfig().getFirstNameAttributeKey())) { - String firstName = getStringAttributeByKey(attributes, config.getBasicConfig().getFirstNameAttributeKey()); + if (!StringUtils.isEmpty(config.getBasic().getFirstNameAttributeKey())) { + String firstName = getStringAttributeByKey(attributes, config.getBasic().getFirstNameAttributeKey()); oauth2User.setFirstName(firstName); } - if (!StringUtils.isEmpty(config.getBasicConfig().getCustomerNamePattern())) { + if (!StringUtils.isEmpty(config.getBasic().getCustomerNamePattern())) { StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX); - String customerName = sub.replace(config.getBasicConfig().getCustomerNamePattern()); + String customerName = sub.replace(config.getBasic().getCustomerNamePattern()); oauth2User.setCustomerName(customerName); } - oauth2User.setAlwaysFullScreen(config.getBasicConfig().isAlwaysFullScreen()); - if (!StringUtils.isEmpty(config.getBasicConfig().getDefaultDashboardName())) { - oauth2User.setDefaultDashboardName(config.getBasicConfig().getDefaultDashboardName()); + oauth2User.setAlwaysFullScreen(config.getBasic().isAlwaysFullScreen()); + if (!StringUtils.isEmpty(config.getBasic().getDefaultDashboardName())) { + oauth2User.setDefaultDashboardName(config.getBasic().getDefaultDashboardName()); } return getOrCreateSecurityUserFromOAuth2User(parentTenantId, oauth2User, config.isAllowUserCreation(), config.isActivateUser()); } private String getTenantName(Map attributes, OAuth2MapperConfig config) { - switch (config.getBasicConfig().getTenantNameStrategy()) { + switch (config.getBasic().getTenantNameStrategy()) { case EMAIL: - return getStringAttributeByKey(attributes, config.getBasicConfig().getEmailAttributeKey()); + return getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); case DOMAIN: - String email = getStringAttributeByKey(attributes, config.getBasicConfig().getEmailAttributeKey()); + String email = getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); return email.substring(email .indexOf("@") + 1); case CUSTOM: StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX); - return sub.replace(config.getBasicConfig().getTenantNamePattern()); + return sub.replace(config.getBasic().getTenantNamePattern()); default: - throw new RuntimeException("Tenant Name Strategy with type " + config.getBasicConfig().getTenantNameStrategy() + " is not supported!"); + throw new RuntimeException("Tenant Name Strategy with type " + config.getBasic().getTenantNameStrategy() + " is not supported!"); } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java index 27286e454a..0dce6f1e98 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java @@ -39,7 +39,7 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme @Override public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, TenantId parentTenantId, OAuth2MapperConfig config) { - OAuth2User oauth2User = getOAuth2User(token, config.getCustomConfig()); + OAuth2User oauth2User = getOAuth2User(token, config.getCustom()); return getOrCreateSecurityUserFromOAuth2User(parentTenantId, oauth2User, config.isAllowUserCreation(), config.isActivateUser()); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java index 380738583f..bfeb8f52a1 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java @@ -19,6 +19,7 @@ import org.apache.commons.lang3.tuple.Pair; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.oauth2.ExtendedOAuth2ClientRegistration; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; @@ -29,7 +30,7 @@ import java.util.Map; public interface OAuth2Service { Pair getClientRegistrationWithTenant(String registrationId); - OAuth2ClientRegistration getClientRegistration(String registrationId); + ExtendedOAuth2ClientRegistration getExtendedClientRegistration(String registrationId); List getOAuth2Clients(String domainName); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistration.java new file mode 100644 index 0000000000..18d5337132 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistration.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.oauth2; + +import lombok.*; + +@Data +@ToString +@Builder(toBuilder = true) +@NoArgsConstructor +@AllArgsConstructor +public class ExtendedOAuth2ClientRegistration { + private String redirectUriTemplate; + private OAuth2ClientRegistration clientRegistration; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java index 231a8167f0..2051e4b850 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java @@ -19,6 +19,8 @@ import lombok.*; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.id.OAuth2IntegrationId; +import java.util.List; + @EqualsAndHashCode @Data @ToString(exclude = {"clientSecret"}) @@ -32,9 +34,8 @@ public class OAuth2ClientRegistration { private String clientId; private String clientSecret; private String authorizationUri; - private String tokenUri; - private String redirectUriTemplate; - private String scope; + private String accessTokenUri; + private List scope; private String userInfoUri; private String userNameAttributeName; private String jwkSetUri; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java index 3ebfa783c2..699bf90c37 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java @@ -27,5 +27,6 @@ import java.util.List; @AllArgsConstructor public class OAuth2ClientsDomainParams { private String domainName; + private String redirectUriTemplate; private List clientRegistrations; } \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java index e0aaf72ee3..15b3067cda 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java @@ -28,6 +28,6 @@ public class OAuth2MapperConfig { private boolean allowUserCreation; private boolean activateUser; private MapperType type; - private OAuth2BasicMapperConfig basicConfig; - private OAuth2CustomMapperConfig customConfig; + private OAuth2BasicMapperConfig basic; + private OAuth2CustomMapperConfig custom; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java index 26a3312376..ab8a14e078 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java @@ -16,12 +16,12 @@ package org.thingsboard.server.dao.oauth2; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.oauth2.ExtendedOAuth2ClientRegistration; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; @Component @@ -32,19 +32,19 @@ public class HybridClientRegistrationRepository implements ClientRegistrationRep @Override public ClientRegistration findByRegistrationId(String registrationId) { - OAuth2ClientRegistration localClientRegistration = oAuth2Service.getClientRegistration(registrationId); - return localClientRegistration == null ? - null : toSpringClientRegistration(localClientRegistration); + ExtendedOAuth2ClientRegistration localExtendedClientRegistration = oAuth2Service.getExtendedClientRegistration(registrationId); + return localExtendedClientRegistration == null ? + null : toSpringClientRegistration(localExtendedClientRegistration.getRedirectUriTemplate(), localExtendedClientRegistration.getClientRegistration()); } - private ClientRegistration toSpringClientRegistration(OAuth2ClientRegistration localClientRegistration){ + private ClientRegistration toSpringClientRegistration(String redirectUriTemplate, OAuth2ClientRegistration localClientRegistration){ return ClientRegistration.withRegistrationId(localClientRegistration.getRegistrationId()) .clientId(localClientRegistration.getClientId()) .authorizationUri(localClientRegistration.getAuthorizationUri()) .clientSecret(localClientRegistration.getClientSecret()) - .tokenUri(localClientRegistration.getTokenUri()) - .redirectUriTemplate(localClientRegistration.getRedirectUriTemplate()) - .scope(localClientRegistration.getScope().split(",")) + .tokenUri(localClientRegistration.getAccessTokenUri()) + .redirectUriTemplate(redirectUriTemplate) + .scope(localClientRegistration.getScope()) .clientName(localClientRegistration.getClientName()) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .userInfoUri(localClientRegistration.getUserInfoUri()) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 8857e6ceb9..25dec8e696 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -1,12 +1,12 @@ /** * Copyright © 2016-2020 The Thingsboard Authors - * + *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -39,10 +39,8 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.tenant.TenantService; -import javax.annotation.PostConstruct; import java.io.IOException; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; @@ -76,27 +74,40 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Override public Pair getClientRegistrationWithTenant(String registrationId) { + return getExtendedOAuth2ClientRegistrationWithTenant(registrationId) + .map(pair -> ImmutablePair.of(pair.getLeft(), pair.getRight().getClientRegistration())) + .orElse(null); + } + + @Override + public ExtendedOAuth2ClientRegistration getExtendedClientRegistration(String registrationId) { + return getExtendedOAuth2ClientRegistrationWithTenant(registrationId) + .map(Pair::getValue) + .orElse(null); + + } + + private Optional> getExtendedOAuth2ClientRegistrationWithTenant(String registrationId) { return getAllOAuth2ClientsParams().entrySet().stream() .map(entry -> { TenantId tenantId = entry.getKey(); - OAuth2ClientRegistration clientRegistration = toClientRegistrationStream(entry.getValue()) - .filter(registration -> registrationId.equals(registration.getRegistrationId())) + return entry.getValue().getClientsDomainsParams().stream() + .flatMap(domainParams -> + domainParams.getClientRegistrations().stream() + .map(clientRegistration -> new ExtendedOAuth2ClientRegistration(domainParams.getRedirectUriTemplate(), clientRegistration)) + ) + .filter(registration -> registrationId.equals(registration.getClientRegistration().getRegistrationId())) .findFirst() + .map(extendedClientRegistration -> ImmutablePair.of(tenantId, extendedClientRegistration)) .orElse(null); - return clientRegistration != null ? - ImmutablePair.of(tenantId, clientRegistration) : null; + }) .filter(Objects::nonNull) .findFirst() - .orElse(null) + .map(entry -> ImmutablePair.of(entry.getKey(), entry.getValue())) ; } - @Override - public OAuth2ClientRegistration getClientRegistration(String registrationId) { - Pair clientRegistrationPair = getClientRegistrationWithTenant(registrationId); - return clientRegistrationPair != null ? clientRegistrationPair.getRight() : null; - } @Override public List getOAuth2Clients(String domainName) { @@ -249,6 +260,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { } private void validate(OAuth2ClientsParams oAuth2ClientsParams) { + validateRedirectUris(oAuth2ClientsParams); validateDomainNames(oAuth2ClientsParams); toClientRegistrationStream(oAuth2ClientsParams) @@ -273,6 +285,15 @@ public class OAuth2ServiceImpl implements OAuth2Service { } } + private void validateRedirectUris(OAuth2ClientsParams oAuth2ClientsParams) { + oAuth2ClientsParams.getClientsDomainsParams().stream() + .forEach(oAuth2ClientsDomainParams -> { + if (StringUtils.isEmpty(oAuth2ClientsDomainParams.getRedirectUriTemplate())) { + throw new DataValidationException("Redirect uri template should be specified!"); + } + }); + } + @Override public OAuth2ClientsParams getSystemOAuth2ClientsParams() { AdminSettings oauth2ClientsParamsSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); @@ -482,12 +503,9 @@ public class OAuth2ServiceImpl implements OAuth2Service { if (StringUtils.isEmpty(clientRegistration.getAuthorizationUri())) { throw new DataValidationException("Authorization uri should be specified!"); } - if (StringUtils.isEmpty(clientRegistration.getTokenUri())) { + if (StringUtils.isEmpty(clientRegistration.getAccessTokenUri())) { throw new DataValidationException("Token uri should be specified!"); } - if (StringUtils.isEmpty(clientRegistration.getRedirectUriTemplate())) { - throw new DataValidationException("Redirect uri template should be specified!"); - } if (StringUtils.isEmpty(clientRegistration.getScope())) { throw new DataValidationException("Scope should be specified!"); } @@ -517,7 +535,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { throw new DataValidationException("Mapper config type should be specified!"); } if (mapperConfig.getType() == MapperType.BASIC) { - OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasicConfig(); + OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic(); if (basicConfig == null) { throw new DataValidationException("Basic config should be specified!"); } @@ -533,19 +551,13 @@ public class OAuth2ServiceImpl implements OAuth2Service { } } if (mapperConfig.getType() == MapperType.CUSTOM) { - OAuth2CustomMapperConfig customConfig = mapperConfig.getCustomConfig(); + OAuth2CustomMapperConfig customConfig = mapperConfig.getCustom(); if (customConfig == null) { throw new DataValidationException("Custom config should be specified!"); } if (StringUtils.isEmpty(customConfig.getUrl())) { throw new DataValidationException("Custom mapper URL should be specified!"); } - if (StringUtils.isEmpty(customConfig.getUsername())) { - throw new DataValidationException("Custom mapper username should be specified!"); - } - if (StringUtils.isEmpty(customConfig.getPassword())) { - throw new DataValidationException("Custom mapper password should be specified!"); - } } }; } diff --git a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java index 32fd45c188..3f182339d0 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java @@ -24,7 +24,7 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClassnameFilters({ - "org.thingsboard.server.dao.service.*ServiceSqlTest" + "org.thingsboard.server.dao.service.*2ServiceSqlTest" }) public class SqlDaoServiceTestSuite { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java index 94b58fb5ce..e502b660ec 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -273,7 +273,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { } @Test - public void testGetClientRegistration() { + public void testGetExtendedClientRegistration() { OAuth2ClientsParams tenantClientsParams = validClientsParams(); OAuth2ClientsParams sysAdminClientsParams = validClientsParams(); @@ -285,9 +285,9 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { OAuth2Utils.toClientRegistrationStream(sysAdminClientsParams) ) .forEach(clientRegistration -> { - OAuth2ClientRegistration foundClientRegistration = oAuth2Service.getClientRegistration(clientRegistration.getRegistrationId()); - Assert.assertNotNull(foundClientRegistration); - Assert.assertEquals(clientRegistration.getRegistrationId(), foundClientRegistration.getRegistrationId()); + ExtendedOAuth2ClientRegistration foundExtendedClientRegistration = oAuth2Service.getExtendedClientRegistration(clientRegistration.getRegistrationId()); + Assert.assertNotNull(foundExtendedClientRegistration); + Assert.assertEquals(clientRegistration, foundExtendedClientRegistration.getClientRegistration()); }); } @@ -401,6 +401,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { .clientsDomainsParams(Collections.singletonList( OAuth2ClientsDomainParams.builder() .domainName(UUID.randomUUID().toString()) + .redirectUriTemplate("http://localhost:8080/login/oauth2/code/") .clientRegistrations(Arrays.asList(first, second)) .build() )) @@ -415,10 +416,12 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { .clientsDomainsParams(Arrays.asList( OAuth2ClientsDomainParams.builder() .domainName(UUID.randomUUID().toString()) + .redirectUriTemplate("http://localhost:8080/login/oauth2/code/") .clientRegistrations(Arrays.asList(first, second)) .build(), OAuth2ClientsDomainParams.builder() .domainName(UUID.randomUUID().toString()) + .redirectUriTemplate("http://localhost:8080/login/oauth2/code/") .clientRegistrations(Arrays.asList(third)) .build() )) @@ -434,14 +437,17 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { .clientsDomainsParams(Arrays.asList( OAuth2ClientsDomainParams.builder() .domainName("domain") + .redirectUriTemplate("http://localhost:8080/login/oauth2/code/") .clientRegistrations(Collections.singletonList(first)) .build(), OAuth2ClientsDomainParams.builder() .domainName("domain") + .redirectUriTemplate("http://localhost:8080/login/oauth2/code/") .clientRegistrations(Collections.singletonList(second)) .build(), OAuth2ClientsDomainParams.builder() .domainName(UUID.randomUUID().toString()) + .redirectUriTemplate("http://localhost:8080/login/oauth2/code/") .clientRegistrations(Collections.singletonList(third)) .build() )) @@ -458,6 +464,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { .clientsDomainsParams(Arrays.asList( OAuth2ClientsDomainParams.builder() .domainName(UUID.randomUUID().toString()) + .redirectUriTemplate("http://localhost:8080/login/oauth2/code/") .clientRegistrations(Arrays.asList(first, second, third)) .build() )) @@ -471,20 +478,17 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { .allowUserCreation(true) .activateUser(true) .type(MapperType.CUSTOM) - .customConfig( + .custom( OAuth2CustomMapperConfig.builder() .url("localhost:8082") - .username("test") - .password("test") .build() ) .build()) .clientId("clientId") .clientSecret("clientSecret") .authorizationUri("authorizationUri") - .tokenUri("tokenUri") - .redirectUriTemplate("http://localhost:8080/login/oauth2/code/") - .scope("scope") + .accessTokenUri("tokenUri") + .scope(Arrays.asList("scope1", "scope2")) .userInfoUri("userInfoUri") .userNameAttributeName("userNameAttributeName") .jwkSetUri("jwkSetUri") From 981ff1fda7049c80e34767ccf1b0d869457b652e Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 9 Jul 2020 12:19:23 +0300 Subject: [PATCH 064/463] Fixed test suite --- .../java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java index 3f182339d0..32fd45c188 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java @@ -24,7 +24,7 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClassnameFilters({ - "org.thingsboard.server.dao.service.*2ServiceSqlTest" + "org.thingsboard.server.dao.service.*ServiceSqlTest" }) public class SqlDaoServiceTestSuite { From 7af3fe0050a674b46888cd72fc28c88622e398c7 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 9 Jul 2020 13:47:35 +0300 Subject: [PATCH 065/463] Add system admin OAuth2 settings --- ui-ngx/src/app/core/http/admin.service.ts | 18 +- ui-ngx/src/app/core/services/menu.service.ts | 8 +- .../home/pages/admin/admin-routing.module.ts | 14 + .../modules/home/pages/admin/admin.module.ts | 4 +- .../admin/oauth2-settings.component.html | 364 ++++++++++++++++++ .../admin/oauth2-settings.component.scss | 28 ++ .../pages/admin/oauth2-settings.component.ts | 284 ++++++++++++++ .../home/pages/tenant/tenant.component.html | 3 + .../home/pages/tenant/tenant.component.ts | 11 +- ui-ngx/src/app/shared/models/constants.ts | 3 +- .../src/app/shared/models/settings.models.ts | 56 +++ .../assets/locale/locale.constant-en_US.json | 61 ++- 12 files changed, 845 insertions(+), 9 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts diff --git a/ui-ngx/src/app/core/http/admin.service.ts b/ui-ngx/src/app/core/http/admin.service.ts index f575f56906..ddc2069d4a 100644 --- a/ui-ngx/src/app/core/http/admin.service.ts +++ b/ui-ngx/src/app/core/http/admin.service.ts @@ -18,7 +18,13 @@ import { Injectable } from '@angular/core'; import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; -import { AdminSettings, MailServerSettings, SecuritySettings, UpdateMessage } from '@shared/models/settings.models'; +import { + AdminSettings, + MailServerSettings, + OAuth2Settings, + SecuritySettings, + UpdateMessage +} from '@shared/models/settings.models'; @Injectable({ providedIn: 'root' @@ -53,6 +59,16 @@ export class AdminService { defaultHttpOptionsFromConfig(config)); } + public getOAuth2Settings(config?: RequestConfig): Observable { + return this.http.get(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config)); + } + + public saveOAuth2Settings(OAuth2Setting: OAuth2Settings, + config?: RequestConfig): Observable { + return this.http.post('/api/oauth2/config', OAuth2Setting, + defaultHttpOptionsFromConfig(config)); + } + public checkUpdates(config?: RequestConfig): Observable { return this.http.get(`/api/admin/updates`, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index 6032bf24f3..ea04968f71 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -95,7 +95,7 @@ export class MenuService { name: 'admin.system-settings', type: 'toggle', path: '/settings', - height: '120px', + height: '160px', icon: 'settings', pages: [ { @@ -115,6 +115,12 @@ export class MenuService { type: 'link', path: '/settings/security-settings', icon: 'security' + }, + { + name: 'admin.oauth2.settings', + type: 'link', + path: '/settings/oauth2-settings', + icon: 'security' } ] } diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts index 47f1a5261b..32c25c6e7d 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts @@ -22,6 +22,7 @@ import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; import { Authority } from '@shared/models/authority.enum'; import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component'; import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component'; +import { OAuth2SettingsComponent } from '@home/pages/admin/oauth2-settings.component'; const routes: Routes = [ { @@ -77,6 +78,19 @@ const routes: Routes = [ icon: 'security' } } + }, + { + path: 'oauth2-settings', + component: OAuth2SettingsComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + auth: [Authority.SYS_ADMIN], + title: 'admin.oauth2.settings', + breadcrumb: { + label: 'admin.oauth2.settings', + icon: 'security' + } + } } ] } diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts index 48bcf56fd5..73fef6a32f 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts @@ -23,13 +23,15 @@ import { MailServerComponent } from '@modules/home/pages/admin/mail-server.compo import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component'; import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component'; import { HomeComponentsModule } from '@modules/home/components/home-components.module'; +import { OAuth2SettingsComponent } from '@modules/home/pages/admin/oauth2-settings.component'; @NgModule({ declarations: [ GeneralSettingsComponent, MailServerComponent, - SecuritySettingsComponent + SecuritySettingsComponent, + OAuth2SettingsComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html new file mode 100644 index 0000000000..87f1871ec2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -0,0 +1,364 @@ + +

+ + +
+ admin.oauth2.settings + +
+
+
+ + +
+ +
+
+ +
+ + + + + + {{ domain.get('domainName').value ? domain.get('domainName').value : ("admin.new-domain" | translate) }} + + + + + + + + admin.domain-name + + + {{ 'admin.error-verification-url' | translate }} + + + + + admin.oauth2.redirect-uri-template + + + {{ 'admin.oauth2.redirect-uri-template-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + + + + +
+ +
+
+ +
+
+ + admin.oauth2.registration-id + + + {{ 'admin.oauth2.registration-id-required' | translate }} + + + + + admin.oauth2.client-name + + + {{ 'admin.oauth2.client-name-required' | translate }} + + +
+ +
+ + admin.oauth2.client-id + + + {{ 'admin.oauth2.client-id-required' | translate }} + + + + + admin.oauth2.client-secret + + + {{ 'admin.oauth2.client-secret-required' | translate }} + + +
+ +
+ + admin.oauth2.access-token-uri + + + {{ 'admin.oauth2.access-token-uri-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + + + + + admin.oauth2.authorization-uri + + + {{ 'admin.oauth2.authorization-uri-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + + +
+ + + admin.oauth2.scope + + + {{scope}} + cancel + + + + + +
+ + admin.oauth2.jwk-set-uri + + + {{ 'admin.oauth2.jwk-set-uri-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + + + + + admin.oauth2.user-info-uri + + + {{ 'admin.oauth2.user-info-uri-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + + +
+ + + admin.oauth2.client-authentication-method + + + {{ clientAuthenticationMethod | uppercase }} + + + + + + admin.oauth2.user-name-attribute-name + + + {{ 'admin.oauth2.user-name-attribute-name-required' | translate }} + + + +
+
+ + {{ 'admin.oauth2.allow-user-creation' | translate }} + + + {{ 'admin.oauth2.activate-user' | translate }} + +
+ + + admin.oauth2.type + + + {{ converterTypeExternalUser }} + + + + +
+ + admin.oauth2.email-attribute-key + + + {{ 'admin.oauth2.email-attribute-key-required' | translate }} + + + +
+ + admin.oauth2.first-name-attribute-key + + + + + admin.oauth2.last-name-attribute-key + + +
+ +
+ + admin.oauth2.tenant-name-strategy + + + {{ tenantNameStrategy }} + + + + + + admin.oauth2.tenant-name-pattern + + + {{ 'admin.oauth2.tenant-name-pattern-required' | translate }} + + +
+ + + admin.oauth2.customer-name-pattern + + + +
+ + admin.oauth2.default-dashboard-name + + + + + {{ 'admin.oauth2.always-fullscreen' | translate}} + +
+ +
+ +
+ + admin.oauth2.url + + + {{ 'admin.oauth2.url-required' | translate }} + + + {{ 'admin.oauth2.url-pattern' | translate }} + + + +
+ + common.username + + + + + common.password + + +
+
+
+ +
+ + admin.oauth2.login-button-label + + + {{ 'admin.oauth2.login-button-label-required' | translate }} + + + + + admin.oauth2.login-button-icon + + +
+
+
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ + +
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss new file mode 100644 index 0000000000..95f47b7023 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host{ + .checkbox-row { + margin-top: 16px; + } + + .registration-card{ + margin-bottom: 8px; + } + + .container{ + margin-bottom: 16px; + } +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts new file mode 100644 index 0000000000..d128ede633 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -0,0 +1,284 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; +import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { ClientRegistration, DomainParams, OAuth2Settings } from '@shared/models/settings.models'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { AdminService } from '@core/http/admin.service'; +import { PageComponent } from '@shared/components/page.component'; +import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; +import { COMMA, ENTER } from '@angular/cdk/keycodes'; +import { MatChipInputEvent } from '@angular/material/chips'; +import { WINDOW } from '@core/services/window.service'; +import { Subscription } from 'rxjs'; +import { DialogService } from '@core/services/dialog.service'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'tb-oauth2-settings', + templateUrl: './oauth2-settings.component.html', + styleUrls: ['./oauth2-settings.component.scss', './settings-card.scss'] +}) +export class OAuth2SettingsComponent extends PageComponent implements OnInit, HasConfirmForm, OnDestroy { + + private URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.?+=&%@\-/]*)?$/; + private subscriptions: Subscription[] = []; + + readonly separatorKeysCodes: number[] = [ENTER, COMMA]; + + oauth2SettingsForm: FormGroup; + oauth2Settings: OAuth2Settings; + + clientAuthenticationMethods = ['basic', 'post']; + converterTypesExternalUser = ['BASIC', 'CUSTOM']; + tenantNameStrategies = ['DOMAIN', 'EMAIL', 'CUSTOM']; + + constructor(protected store: Store, + private adminService: AdminService, + private fb: FormBuilder, + private dialogService: DialogService, + private translate: TranslateService, + @Inject(WINDOW) private window: Window) { + super(store); + } + + ngOnInit(): void { + this.buildOAuth2SettingsForm(); + this.adminService.getOAuth2Settings().subscribe( + (oauth2Settings) => { + this.oauth2Settings = oauth2Settings; + this.initOAuth2Settings(this.oauth2Settings); + this.oauth2SettingsForm.reset(this.oauth2Settings); + } + ); + } + + ngOnDestroy() { + super.ngOnDestroy(); + this.subscriptions.forEach((subscription) => { + subscription.unsubscribe(); + }) + } + + get clientsDomainsParams(): FormArray { + return this.oauth2SettingsForm.get('clientsDomainsParams') as FormArray; + } + + private get formBasicGroup(): FormGroup { + const basicGroup = this.fb.group({ + emailAttributeKey: ['email', [Validators.required]], + firstNameAttributeKey: [''], + lastNameAttributeKey: [''], + tenantNameStrategy: ['DOMAIN'], + tenantNamePattern: [null], + customerNamePattern: [null], + defaultDashboardName: [null], + alwaysFullScreen: [false], + }); + + this.subscriptions.push(basicGroup.get('tenantNameStrategy').valueChanges.subscribe((domain) => { + if (domain === 'CUSTOM') { + basicGroup.get('tenantNamePattern').setValidators(Validators.required); + } else { + basicGroup.get('tenantNamePattern').clearValidators(); + } + })); + + return basicGroup; + } + + get formCustomGroup(): FormGroup { + return this.fb.group({ + url: [null, [Validators.required, Validators.pattern(this.URL_REGEXP)]], + username: [null], + password: [null] + }) + } + + private buildOAuth2SettingsForm(): void { + this.oauth2SettingsForm = this.fb.group({ + clientsDomainsParams: this.fb.array([]) + }); + } + + private initOAuth2Settings(oauth2Settings: OAuth2Settings): void { + oauth2Settings.clientsDomainsParams.forEach((domaindomain) => { + this.clientsDomainsParams.push(this.buildSettingsDomain(domaindomain)); + }); + } + + private buildSettingsDomain(domainParams?: DomainParams): FormGroup { + let url = this.window.location.protocol + '//' + this.window.location.hostname; + const port = this.window.location.port; + if (port !== '80' && port !== '443') { + url += ':' + port; + } + url += '/login/oauth2/code/'; + const formDomain = this.fb.group({ + domainName: ['', [Validators.required, Validators.pattern('((?![:/]).)*$')]], + redirectUriTemplate: [url, [Validators.required, Validators.pattern(this.URL_REGEXP)]], + clientRegistrations: this.fb.array([]) + }); + + this.subscriptions.push(formDomain.get('domainName').valueChanges.subscribe((domain) => { + if (!domain) { + domain = this.window.location.hostname + } + const uri = this.window.location.protocol + `//${domain}/login/oauth2/code/`; + formDomain.get('redirectUriTemplate').patchValue(uri); + })); + + if(domainParams){ + domainParams.clientRegistrations.forEach((registration) => { + this.clientDomainRegistrations(formDomain).push(this.buildSettingsRegistration(registration)); + }) + } else { + this.clientDomainRegistrations(formDomain).push(this.buildSettingsRegistration()); + } + + return formDomain; + } + + private buildSettingsRegistration(registrationData?: ClientRegistration): FormGroup { + const clientRegistration = this.fb.group({ + registrationId: [null, [Validators.required]], + clientName: [null, [Validators.required]], + loginButtonLabel: [null, [Validators.required]], + loginButtonIcon: [null], + clientId: ['', [Validators.required]], + clientSecret: ['', [Validators.required]], + accessTokenUri: ['', [Validators.required, Validators.pattern(this.URL_REGEXP)]], + authorizationUri: ['', [Validators.required, Validators.pattern(this.URL_REGEXP)]], + scope: this.fb.array([], [Validators.required]), + jwkSetUri: ['', [Validators.required, Validators.pattern(this.URL_REGEXP)]], + userInfoUri: ['', [Validators.required, Validators.pattern(this.URL_REGEXP)]], + clientAuthenticationMethod: ['post', [Validators.required]], + userNameAttributeName: ['email', [Validators.required]], + mapperConfig: this.fb.group({ + allowUserCreation: [true], + activateUser: [false], + type: ['BASIC', [Validators.required]], + basic: this.formBasicGroup + } + ) + }); + + this.subscriptions.push(clientRegistration.get('mapperConfig.type').valueChanges.subscribe((value) => { + const mapperConfig = clientRegistration.get('mapperConfig') as FormGroup; + if (value === 'BASIC') { + mapperConfig.removeControl('custom'); + mapperConfig.addControl('basic', this.formBasicGroup); + } else { + mapperConfig.removeControl('basic'); + mapperConfig.addControl('custom', this.formCustomGroup); + } + })); + + if(registrationData){ + registrationData.scope.forEach(() => { + (clientRegistration.get('scope') as FormArray).push(this.fb.control('')) + }) + if(registrationData.mapperConfig.type !== 'BASIC'){ + clientRegistration.get('mapperConfig.type').patchValue('CUSTOM'); + } + } + + return clientRegistration; + } + + save(): void { + console.log(this.oauth2SettingsForm.value); + this.adminService.saveOAuth2Settings(this.oauth2SettingsForm.value).subscribe( + (oauth2Settings) => { + this.oauth2Settings = oauth2Settings; + this.oauth2SettingsForm.markAsPristine(); + this.oauth2SettingsForm.markAsUntouched(); + } + ); + } + + confirmForm(): FormGroup { + return this.oauth2SettingsForm; + } + + addScope(event: MatChipInputEvent, control: AbstractControl): void { + const input = event.input; + const value = event.value; + const controller = control.get('scope') as FormArray; + if ((value.trim() !== '')) { + controller.push(this.fb.control(value.trim())); + } + + if (input) { + input.value = ''; + } + } + + removeScope(i: number, control: AbstractControl): void { + const controller = control.get('scope') as FormArray; + controller.removeAt(i); + } + + addDomain(): void { + this.clientsDomainsParams.push(this.buildSettingsDomain()); + } + + deleteDomain($event: Event, index: number): void { + if ($event) { + $event.stopPropagation(); + $event.preventDefault(); + } + + const domainName = this.clientsDomainsParams.at(index).get('domainName').value; + this.dialogService.confirm( + this.translate.instant('admin.oauth2.delete-domain-title', {domainName}), + this.translate.instant('admin.oauth2.delete-domain-text'), null, + this.translate.instant('action.delete') + ).subscribe((data) => { + if (data) { + this.clientsDomainsParams.removeAt(index); + } + }) + } + + clientDomainRegistrations(control: AbstractControl): FormArray { + return control.get('clientRegistrations') as FormArray; + } + + addRegistration(control: AbstractControl): void { + this.clientDomainRegistrations(control).push(this.buildSettingsRegistration()); + } + + deleteRegistration($event: Event, controler: AbstractControl, index: number): void { + if ($event) { + $event.stopPropagation(); + $event.preventDefault(); + } + + const registrationId = this.clientDomainRegistrations(controler).at(index).get('registrationId').value; + this.dialogService.confirm( + this.translate.instant('admin.oauth2.delete-registration-title', {name: registrationId}), + this.translate.instant('admin.oauth2.delete-registration-text'), null, + this.translate.instant('action.delete') + ).subscribe((data) => { + if (data) { + this.clientDomainRegistrations(controler).removeAt(index); + } + }) + } +} diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html index 335a7d3c9a..f13809ffa6 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html @@ -54,6 +54,9 @@ tenant.description + + {{ 'tenant.allow-oauth2-configuration' | translate }} +
diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts index 8a76830093..0d55936039 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts @@ -23,6 +23,7 @@ import { ActionNotificationShow } from '@app/core/notification/notification.acti import { TranslateService } from '@ngx-translate/core'; import { ContactBasedComponent } from '../../components/entity/contact-based.component'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; +import { isDefined } from '@core/utils'; @Component({ selector: 'tb-tenant', @@ -55,7 +56,9 @@ export class TenantComponent extends ContactBasedComponent { isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []], additionalInfo: this.fb.group( { - description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''] + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], + allowOAuth2Configuration: [isDefined(entity?.additionalInfo?.allowOAuth2Configuration) ? + entity.additionalInfo.allowOAuth2Configuration : true] } ) } @@ -66,7 +69,11 @@ export class TenantComponent extends ContactBasedComponent { this.entityForm.patchValue({title: entity.title}); this.entityForm.patchValue({isolatedTbCore: entity.isolatedTbCore}); this.entityForm.patchValue({isolatedTbRuleEngine: entity.isolatedTbRuleEngine}); - this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); + this.entityForm.patchValue({additionalInfo: { + description: entity.additionalInfo ? entity.additionalInfo.description : '', + allowOAuth2Configuration: isDefined(entity?.additionalInfo?.allowOAuth2Configuration) ? + entity.additionalInfo.allowOAuth2Configuration : true + }}); } updateFormState() { diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index 4c9612489a..1b4570d848 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -58,6 +58,7 @@ export const HelpLinks = { linksMap: { outgoingMailSettings: helpBaseUrl + '/docs/user-guide/ui/mail-settings', securitySettings: helpBaseUrl + '/docs/user-guide/ui/security-settings', + oauth2Settings: helpBaseUrl + '/docs/user-guide/oauth-2-support/', ruleEngine: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/overview/', ruleNodeCheckRelation: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#check-relation-filter-node', ruleNodeCheckExistenceFields: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#check-existence-fields-node', @@ -108,7 +109,7 @@ export const HelpLinks = { widgetsConfigLatest: helpBaseUrl + '/docs/user-guide/ui/dashboards#latest', widgetsConfigRpc: helpBaseUrl + '/docs/user-guide/ui/dashboards#rpc', widgetsConfigAlarm: helpBaseUrl + '/docs/user-guide/ui/dashboards#alarm', - widgetsConfigStatic: helpBaseUrl + '/docs/user-guide/ui/dashboards#static' + widgetsConfigStatic: helpBaseUrl + '/docs/user-guide/ui/dashboards#static', } }; diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index f71ac1bb2e..c52c759719 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -23,6 +23,10 @@ export interface AdminSettings { export declare type SmtpProtocol = 'smtp' | 'smtps'; +export declare type ClientAuthenticationMethod = 'basic' | 'post'; +export declare type MapperConfigType = 'BASIC' | 'CUSTOM'; +export declare type TenantNameStrategy = 'DOMAIN' | 'EMAIL' | 'CUSTOM'; + export interface MailServerSettings { mailFrom: string; smtpProtocol: SmtpProtocol; @@ -60,3 +64,55 @@ export interface UpdateMessage { message: string; updateAvailable: boolean; } + +export interface OAuth2Settings { + clientsDomainsParams: DomainParams[]; +} + +export interface DomainParams { + domainName: string; + redirectUriTemplate: string; + clientRegistrations: ClientRegistration[]; +} + +export interface ClientRegistration { + registrationId: string; + clientName: string; + loginButtonLabel: string; + loginButtonIcon: string; + clientId: string; + clientSecret: string; + accessTokenUri: string; + authorizationUri: string; + scope: string[]; + jwkSetUri: string; + userInfoUri: string; + clientAuthenticationMethod: ClientAuthenticationMethod + userNameAttributeName: string; + mapperConfig: MapperConfig +} + +export interface MapperConfig { + allowUserCreation: boolean; + activateUser: boolean; + type: MapperConfigType; + basic?: MapperConfigBasic; + custom?: MapperConfigCustom; +} + +export interface MapperConfigBasic { + emailAttributeKey: string; + firstNameAttributeKey?: string; + lastNameAttributeKey?: string; + tenantNameStrategy: TenantNameStrategy; + tenantNamePattern?: string; + customerNamePattern?: string; + defaultDashboardName?: string; + alwaysFullScreen?: boolean; +} + +export interface MapperConfigCustom { + url: string; + username?: string; + password?: string; +} diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 0d4925d89f..d1817e89ea 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -119,8 +119,62 @@ "general-policy": "General policy", "max-failed-login-attempts": "Maximum number of failed login attempts, before account is locked", "minimum-max-failed-login-attempts-range": "Maximum number of failed login attempts can't be negative", - "user-lockout-notification-email": "In case user account lockout, send notification to email" - }, + "user-lockout-notification-email": "In case user account lockout, send notification to email", + "domain-name": "Domain name", + "error-verification-url": "A domain name shouldn't contain symbols '/' and ':'. Example: thingsboard.io", + "add-domain": "Add domain", + "new-domain": "New domain", + "add-registration": "Add registration", + "oauth2": { + "settings": "OAuth2 settings", + "registration-id": "Registration ID", + "registration-id-required": "Registration ID is required.", + "client-name": "Client name", + "client-name-required": "Client name is required.", + "client-id": "Client ID", + "client-id-required": "Client ID is required.", + "client-secret": "Client secret", + "client-secret-required": "Client secret is required.", + "access-token-uri": "Access token URI", + "access-token-uri-required": "Access token URI is required.", + "authorization-uri": "Authorization URI", + "authorization-uri-required": "Authorization URI is required.", + "uri-pattern-error": "Invalid URI format.", + "scope": "Scope", + "redirect-uri-template": "Redirect URI template", + "redirect-uri-template-required": "Redirect URI template is required.", + "jwk-set-uri": "JSON Web Key URI", + "jwk-set-uri-required": "JSON Web Key URI is required.", + "user-info-uri": "User info URI", + "user-info-uri-required": "User info URI is required.", + "client-authentication-method": "Client authentication method", + "user-name-attribute-name": "User name attribute key", + "user-name-attribute-name-required": "User name attribute key is required", + "allow-user-creation": "Allow user creation", + "activate-user": "Activate user", + "type": "Mapper type", + "email-attribute-key": "Email attribute key", + "email-attribute-key-required": "Email attribute key is required.", + "first-name-attribute-key": "First name attribute key", + "last-name-attribute-key": "Last name attribute key", + "tenant-name-strategy": "Tenant name strategy", + "tenant-name-pattern": "Tenant name pattern", + "tenant-name-pattern-required": "Tenant name pattern is required.", + "customer-name-pattern": "Customer name pattern", + "default-dashboard-name": "Default dashboard name", + "always-fullscreen": "Always fullscreen", + "url": "URL", + "url-required": "URL is required.", + "url-pattern": "Invalid URL format.", + "login-button-label": "Login button label", + "login-button-label-required": "Login button label is required.", + "login-button-icon": "Login button icon", + "delete-domain-title": "Are you sure you want to delete the domain '{{domainName}}'?", + "delete-domain-text": "Be careful, after the confirmation a domain and all registration data will be unavailable.", + "delete-registration-title": "Are you sure you want to delete the registration '{{name}}'?", + "delete-registration-text": "Be careful, after the confirmation a registration data will be unavailable." + } + }, "alarm": { "alarm": "Alarm", "alarms": "Alarms", @@ -1561,7 +1615,8 @@ "isolated-tb-core": "Processing in isolated ThingsBoard Core container", "isolated-tb-rule-engine": "Processing in isolated ThingsBoard Rule Engine container", "isolated-tb-core-details": "Requires separate microservice(s) per isolated Tenant", - "isolated-tb-rule-engine-details": "Requires separate microservice(s) per isolated Tenant" + "isolated-tb-rule-engine-details": "Requires separate microservice(s) per isolated Tenant", + "allow-oauth2-configuration": "Allow OAuth2 configuration" }, "timeinterval": { "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", From 483a3b847b8266d71e39477094add11836076219 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 10 Jul 2020 11:33:47 +0300 Subject: [PATCH 066/463] Add tenant menu settingOAuth2 Add validation unique parameters to settings OAuth2 --- ui-ngx/src/app/core/auth/auth.models.ts | 2 + ui-ngx/src/app/core/auth/auth.reducer.ts | 1 + ui-ngx/src/app/core/auth/auth.service.ts | 12 ++++- ui-ngx/src/app/core/services/menu.service.ts | 24 ++++++++- .../home/pages/admin/admin-routing.module.ts | 6 +-- .../admin/oauth2-settings.component.html | 15 ++++-- .../pages/admin/oauth2-settings.component.ts | 49 +++++++++++++++---- .../home/pages/tenant/tenant.component.ts | 4 +- .../assets/locale/locale.constant-en_US.json | 2 + 9 files changed, 94 insertions(+), 21 deletions(-) diff --git a/ui-ngx/src/app/core/auth/auth.models.ts b/ui-ngx/src/app/core/auth/auth.models.ts index c7210da6ed..573b692a29 100644 --- a/ui-ngx/src/app/core/auth/auth.models.ts +++ b/ui-ngx/src/app/core/auth/auth.models.ts @@ -22,6 +22,7 @@ export interface AuthPayload { userTokenAccessEnabled: boolean; allowedDashboardIds: string[]; forceFullscreen: boolean; + allowOAuth2Configuration: boolean; } export interface AuthState { @@ -33,4 +34,5 @@ export interface AuthState { allowedDashboardIds: string[]; forceFullscreen: boolean; lastPublicDashboardId: string; + allowOAuth2Configuration: boolean; } diff --git a/ui-ngx/src/app/core/auth/auth.reducer.ts b/ui-ngx/src/app/core/auth/auth.reducer.ts index 6e02d03191..0e7ca095af 100644 --- a/ui-ngx/src/app/core/auth/auth.reducer.ts +++ b/ui-ngx/src/app/core/auth/auth.reducer.ts @@ -22,6 +22,7 @@ const emptyUserAuthState: AuthPayload = { userDetails: null, userTokenAccessEnabled: false, forceFullscreen: false, + allowOAuth2Configuration: false, allowedDashboardIds: [] }; diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts index fad2a5e145..4089eb8979 100644 --- a/ui-ngx/src/app/core/auth/auth.service.ts +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -425,15 +425,25 @@ export class AuthService { } } + private loadIsOAuth2ConfigurationAllow(authUser: AuthUser): Observable { + if (authUser.authority === Authority.TENANT_ADMIN) { + return this.http.get('/api/oauth2/config/isAllowed', defaultHttpOptions()); + } else { + return of(false); + } + } + private loadSystemParams(authPayload: AuthPayload): Observable { const sources: Array> = [this.loadIsUserTokenAccessEnabled(authPayload.authUser), this.fetchAllowedDashboardIds(authPayload), + this.loadIsOAuth2ConfigurationAllow(authPayload.authUser), this.timeService.loadMaxDatapointsLimit()]; return forkJoin(sources) .pipe(map((data) => { const userTokenAccessEnabled: boolean = data[0]; const allowedDashboardIds: string[] = data[1]; - return {userTokenAccessEnabled, allowedDashboardIds}; + const allowOAuth2Configuration: boolean = data[2]; + return {userTokenAccessEnabled, allowedDashboardIds, allowOAuth2Configuration}; })); } diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index ea04968f71..429f65b736 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -18,12 +18,13 @@ import { Injectable } from '@angular/core'; import { AuthService } from '../auth/auth.service'; import { select, Store } from '@ngrx/store'; import { AppState } from '../core.state'; -import { selectAuthUser, selectIsAuthenticated } from '../auth/auth.selectors'; +import { getCurrentAuthState, selectAuthUser, selectIsAuthenticated } from '../auth/auth.selectors'; import { take } from 'rxjs/operators'; import { HomeSection, MenuSection } from '@core/services/menu.models'; import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { Authority } from '@shared/models/authority.enum'; import { AuthUser } from '@shared/models/user.model'; +import { AuthState } from '@core/auth/auth.models'; @Injectable({ providedIn: 'root' @@ -43,6 +44,8 @@ export class MenuService { ); } + authState: AuthState = getCurrentAuthState(this.store); + private buildMenu() { this.store.pipe(select(selectAuthUser), take(1)).subscribe( (authUser: AuthUser) => { @@ -233,6 +236,25 @@ export class MenuService { icon: 'track_changes' } ); + + if (this.authState.allowOAuth2Configuration) { + sections.push({ + name: 'admin.system-settings', + type: 'toggle', + path: '/settings', + height: '40px', + icon: 'settings', + pages: [ + { + name: 'admin.oauth2.settings', + type: 'link', + path: '/settings/oauth2-settings', + icon: 'security' + } + ] + }) + } + return sections; } diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts index 32c25c6e7d..329e313110 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts @@ -28,7 +28,7 @@ const routes: Routes = [ { path: 'settings', data: { - auth: [Authority.SYS_ADMIN], + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], breadcrumb: { label: 'admin.system-settings', icon: 'settings' @@ -37,7 +37,7 @@ const routes: Routes = [ children: [ { path: '', - redirectTo: 'general', + redirectTo: Authority.TENANT_ADMIN ? 'oauth2-settings' : 'general', pathMatch: 'full' }, { @@ -84,7 +84,7 @@ const routes: Routes = [ component: OAuth2SettingsComponent, canDeactivate: [ConfirmOnExitGuard], data: { - auth: [Authority.SYS_ADMIN], + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], title: 'admin.oauth2.settings', breadcrumb: { label: 'admin.oauth2.settings', diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html index 87f1871ec2..5c8c92658e 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -52,6 +52,9 @@ {{ 'admin.error-verification-url' | translate }} + + {{ 'admin.domain-name-unique' | translate }} + @@ -84,6 +87,9 @@ {{ 'admin.oauth2.registration-id-required' | translate }} + + {{ 'admin.oauth2.registration-id-unique' | translate }} + @@ -140,10 +146,8 @@ admin.oauth2.scope - + {{scope}} cancel @@ -152,6 +156,9 @@ matChipInputAddOnBlur (matChipInputTokenEnd)="addScope($event, registration)"> + + {{ 'admin.oauth2.jwk-set-uri-required' | translate }} +
diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index d128ede633..47333a99b1 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -112,14 +112,44 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha private buildOAuth2SettingsForm(): void { this.oauth2SettingsForm = this.fb.group({ - clientsDomainsParams: this.fb.array([]) + clientsDomainsParams: this.fb.array([], Validators.required) }); } private initOAuth2Settings(oauth2Settings: OAuth2Settings): void { - oauth2Settings.clientsDomainsParams.forEach((domaindomain) => { - this.clientsDomainsParams.push(this.buildSettingsDomain(domaindomain)); - }); + if(oauth2Settings.clientsDomainsParams) { + oauth2Settings.clientsDomainsParams.forEach((domaindomain) => { + this.clientsDomainsParams.push(this.buildSettingsDomain(domaindomain)); + }); + } + } + + private uniqueDomainValidator(control: AbstractControl): { [key: string]: boolean } | null { + if (control.value !== null && control?.root) { + const listDomainName = []; + control.root.value.clientsDomainsParams.forEach((domain) => { + listDomainName.push(domain.domainName); + }) + if (listDomainName.indexOf(control.value) > -1) { + return {unique: true}; + } + } + return null; + } + + private uniqueRegistrationIdValidator(control: AbstractControl): { [key: string]: boolean } | null { + if (control.value !== null && control?.root) { + const listRegistration = []; + control.root.value.clientsDomainsParams.forEach((domain) => { + domain.clientRegistrations.forEach((client) => { + listRegistration.push(client.registrationId); + }) + }) + if (listRegistration.indexOf(control.value) > -1) { + return {unique: true}; + } + } + return null; } private buildSettingsDomain(domainParams?: DomainParams): FormGroup { @@ -130,9 +160,9 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } url += '/login/oauth2/code/'; const formDomain = this.fb.group({ - domainName: ['', [Validators.required, Validators.pattern('((?![:/]).)*$')]], + domainName: [null, [Validators.required, Validators.pattern('((?![:/]).)*$'), this.uniqueDomainValidator]], redirectUriTemplate: [url, [Validators.required, Validators.pattern(this.URL_REGEXP)]], - clientRegistrations: this.fb.array([]) + clientRegistrations: this.fb.array([], Validators.required) }); this.subscriptions.push(formDomain.get('domainName').valueChanges.subscribe((domain) => { @@ -156,7 +186,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha private buildSettingsRegistration(registrationData?: ClientRegistration): FormGroup { const clientRegistration = this.fb.group({ - registrationId: [null, [Validators.required]], + registrationId: [null, [Validators.required, this.uniqueRegistrationIdValidator]], clientName: [null, [Validators.required]], loginButtonLabel: [null, [Validators.required]], loginButtonIcon: [null], @@ -202,7 +232,6 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } save(): void { - console.log(this.oauth2SettingsForm.value); this.adminService.saveOAuth2Settings(this.oauth2SettingsForm.value).subscribe( (oauth2Settings) => { this.oauth2Settings = oauth2Settings; @@ -246,7 +275,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha const domainName = this.clientsDomainsParams.at(index).get('domainName').value; this.dialogService.confirm( - this.translate.instant('admin.oauth2.delete-domain-title', {domainName}), + this.translate.instant('admin.oauth2.delete-domain-title', {domainName: domainName || ''}), this.translate.instant('admin.oauth2.delete-domain-text'), null, this.translate.instant('action.delete') ).subscribe((data) => { @@ -272,7 +301,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha const registrationId = this.clientDomainRegistrations(controler).at(index).get('registrationId').value; this.dialogService.confirm( - this.translate.instant('admin.oauth2.delete-registration-title', {name: registrationId}), + this.translate.instant('admin.oauth2.delete-registration-title', {name: registrationId || ''}), this.translate.instant('admin.oauth2.delete-registration-text'), null, this.translate.instant('action.delete') ).subscribe((data) => { diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts index 0d55936039..aa40d792b0 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts @@ -58,7 +58,7 @@ export class TenantComponent extends ContactBasedComponent { { description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], allowOAuth2Configuration: [isDefined(entity?.additionalInfo?.allowOAuth2Configuration) ? - entity.additionalInfo.allowOAuth2Configuration : true] + entity.additionalInfo.allowOAuth2Configuration : false] } ) } @@ -72,7 +72,7 @@ export class TenantComponent extends ContactBasedComponent { this.entityForm.patchValue({additionalInfo: { description: entity.additionalInfo ? entity.additionalInfo.description : '', allowOAuth2Configuration: isDefined(entity?.additionalInfo?.allowOAuth2Configuration) ? - entity.additionalInfo.allowOAuth2Configuration : true + entity.additionalInfo.allowOAuth2Configuration : false }}); } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index d1817e89ea..0866117933 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -121,6 +121,7 @@ "minimum-max-failed-login-attempts-range": "Maximum number of failed login attempts can't be negative", "user-lockout-notification-email": "In case user account lockout, send notification to email", "domain-name": "Domain name", + "domain-name-unique": "Domain name need to unique for the system.", "error-verification-url": "A domain name shouldn't contain symbols '/' and ':'. Example: thingsboard.io", "add-domain": "Add domain", "new-domain": "New domain", @@ -129,6 +130,7 @@ "settings": "OAuth2 settings", "registration-id": "Registration ID", "registration-id-required": "Registration ID is required.", + "registration-id-unique": "Registration ID need to unique for the system.", "client-name": "Client name", "client-name-required": "Client name is required.", "client-id": "Client ID", From d8fecd18d9cd3bb97a90827772393ca055b5dca0 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Fri, 10 Jul 2020 13:16:34 +0300 Subject: [PATCH 067/463] Get 'clientName' from 'registrationId' --- .../server/common/data/oauth2/OAuth2ClientRegistration.java | 1 - .../server/dao/oauth2/HybridClientRegistrationRepository.java | 2 +- .../org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java | 3 --- .../thingsboard/server/dao/service/BaseOAuth2ServiceTest.java | 1 - 4 files changed, 1 insertion(+), 6 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java index 2051e4b850..0a4156bcc5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java @@ -40,7 +40,6 @@ public class OAuth2ClientRegistration { private String userNameAttributeName; private String jwkSetUri; private String clientAuthenticationMethod; - private String clientName; private String loginButtonLabel; private String loginButtonIcon; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java index ab8a14e078..7309cc7edb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java @@ -39,13 +39,13 @@ public class HybridClientRegistrationRepository implements ClientRegistrationRep private ClientRegistration toSpringClientRegistration(String redirectUriTemplate, OAuth2ClientRegistration localClientRegistration){ return ClientRegistration.withRegistrationId(localClientRegistration.getRegistrationId()) + .clientName(localClientRegistration.getRegistrationId()) .clientId(localClientRegistration.getClientId()) .authorizationUri(localClientRegistration.getAuthorizationUri()) .clientSecret(localClientRegistration.getClientSecret()) .tokenUri(localClientRegistration.getAccessTokenUri()) .redirectUriTemplate(redirectUriTemplate) .scope(localClientRegistration.getScope()) - .clientName(localClientRegistration.getClientName()) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .userInfoUri(localClientRegistration.getUserInfoUri()) .userNameAttributeName(localClientRegistration.getUserNameAttributeName()) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 25dec8e696..92790b3d98 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -521,9 +521,6 @@ public class OAuth2ServiceImpl implements OAuth2Service { if (StringUtils.isEmpty(clientRegistration.getClientAuthenticationMethod())) { throw new DataValidationException("Client authentication method should be specified!"); } - if (StringUtils.isEmpty(clientRegistration.getClientName())) { - throw new DataValidationException("Client name should be specified!"); - } if (StringUtils.isEmpty(clientRegistration.getLoginButtonLabel())) { throw new DataValidationException("Login button label should be specified!"); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java index e502b660ec..ab572e7e53 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -493,7 +493,6 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { .userNameAttributeName("userNameAttributeName") .jwkSetUri("jwkSetUri") .clientAuthenticationMethod("clientAuthenticationMethod") - .clientName("clientName") .loginButtonLabel("loginButtonLabel") .build(); } From 9eb9302ee3070e5af69f9d410d39bc2a312c7609 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Fri, 10 Jul 2020 15:54:34 +0300 Subject: [PATCH 068/463] Used DB lock instead of Java lock --- .../server/controller/OAuth2Controller.java | 1 - .../thingsboard/server/dao/lock/LockKey.java | 30 ++++++ .../server/dao/lock/LockService.java | 25 +++++ .../server/dao/lock/LockServiceImpl.java | 43 +++++++++ .../server/dao/oauth2/OAuth2ServiceImpl.java | 91 ++++++++++--------- .../server/dao/sql/lock/LockRepository.java | 20 ++++ .../dao/sql/lock/PsqlLockRepository.java | 42 +++++++++ 7 files changed, 210 insertions(+), 42 deletions(-) create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockKey.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/lock/LockServiceImpl.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/lock/LockRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/lock/PsqlLockRepository.java diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index b3b2b1eb83..ba300eec71 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -43,7 +43,6 @@ public class OAuth2Controller extends BaseController { @Autowired private OAuth2Service oauth2Service; - // TODO ask why POST @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) @ResponseBody public List getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockKey.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockKey.java new file mode 100644 index 0000000000..5de01b3e2c --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockKey.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.lock; + +public enum LockKey { + OAUTH2_CONFIG(999); + + private int id; + + LockKey(int id) { + this.id = id; + } + + public int getId() { + return id; + } +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockService.java new file mode 100644 index 0000000000..4ffd05233d --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockService.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.lock; + +public interface LockService { + /** + * Lock with SQL Database till the end of transaction + * + * @param key identifier + */ + void transactionLock(LockKey key); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/lock/LockServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/lock/LockServiceImpl.java new file mode 100644 index 0000000000..703a6686a8 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/lock/LockServiceImpl.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.lock; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.thingsboard.server.dao.sql.lock.LockRepository; + +import javax.annotation.PostConstruct; + +@Service +@Slf4j +public class LockServiceImpl implements LockService { + + @Autowired(required = false) + private LockRepository lockRepository; + + @PostConstruct + public void init(){ + log.warn("Locking with DB is not enabled."); + } + + @Override + public void transactionLock(LockKey key) { + if (lockRepository == null) return; + log.trace("Locking transaction key [{}]", key); + lockRepository.transactionLock(key.getId()); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 92790b3d98..90d19a19f3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -1,12 +1,12 @@ /** * Copyright © 2016-2020 The Thingsboard Authors - *

+ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

+ * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -28,6 +28,7 @@ import org.apache.commons.lang3.tuple.Pair; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.*; import org.thingsboard.server.common.data.id.*; @@ -36,13 +37,14 @@ import org.thingsboard.server.common.data.oauth2.*; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; +import org.thingsboard.server.dao.lock.LockKey; +import org.thingsboard.server.dao.lock.LockService; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.tenant.TenantService; import java.io.IOException; import java.util.*; import java.util.concurrent.ExecutionException; -import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -51,11 +53,8 @@ import static org.thingsboard.server.dao.oauth2.OAuth2Utils.*; @Slf4j @Service public class OAuth2ServiceImpl implements OAuth2Service { - private static final ObjectMapper mapper = new ObjectMapper(); - private final ReentrantLock clientRegistrationSaveLock = new ReentrantLock(); - @Autowired private Environment environment; @@ -68,6 +67,9 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Autowired private TenantService tenantService; + @Autowired + private LockService lockService; + private boolean isInstall() { return environment.acceptsProfiles("install"); } @@ -123,52 +125,59 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Override public OAuth2ClientsParams saveSystemOAuth2ClientsParams(OAuth2ClientsParams oAuth2ClientsParams) { validate(oAuth2ClientsParams); - validateRegistrationIdUniqueness(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); - clientRegistrationSaveLock.lock(); - try { - validateRegistrationIdUniqueness(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); - AdminSettings oauth2SystemAdminSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); - if (oauth2SystemAdminSettings == null) { - oauth2SystemAdminSettings = createSystemAdminSettings(); - } - String json = toJson(oAuth2ClientsParams); - ((ObjectNode) oauth2SystemAdminSettings.getJsonValue()).put(SYSTEM_SETTINGS_OAUTH2_VALUE, json); - adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, oauth2SystemAdminSettings); - } finally { - clientRegistrationSaveLock.unlock(); - } + + transactionalSaveSystemOAuth2ClientsParams(oAuth2ClientsParams); return getSystemOAuth2ClientsParams(); } + @Transactional + private void transactionalSaveSystemOAuth2ClientsParams(OAuth2ClientsParams oAuth2ClientsParams) { + long acquireStart = System.currentTimeMillis(); + lockService.transactionLock(LockKey.OAUTH2_CONFIG); + log.trace("[{}] Waited for lock {} ms.", TenantId.SYS_TENANT_ID, System.currentTimeMillis() - acquireStart); + + validateRegistrationIdUniqueness(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); + AdminSettings oauth2SystemAdminSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); + if (oauth2SystemAdminSettings == null) { + oauth2SystemAdminSettings = createSystemAdminSettings(); + } + String json = toJson(oAuth2ClientsParams); + ((ObjectNode) oauth2SystemAdminSettings.getJsonValue()).put(SYSTEM_SETTINGS_OAUTH2_VALUE, json); + adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, oauth2SystemAdminSettings); + } + @Override public OAuth2ClientsParams saveTenantOAuth2ClientsParams(TenantId tenantId, OAuth2ClientsParams oAuth2ClientsParams) { validate(oAuth2ClientsParams); - validateRegistrationIdUniqueness(oAuth2ClientsParams, tenantId); - clientRegistrationSaveLock.lock(); - try { - validateRegistrationIdUniqueness(oAuth2ClientsParams, tenantId); - Set domainNames = oAuth2ClientsParams.getClientsDomainsParams().stream() - .map(OAuth2ClientsDomainParams::getDomainName) - .collect(Collectors.toSet()); - processTenantAdminSettings(tenantId, domainNames); + transactionalSaveTenantOAuth2ClientsParams(tenantId, oAuth2ClientsParams); - List attributes = createOAuth2ClientsParamsAttributes(oAuth2ClientsParams); - try { - attributesService.save(tenantId, tenantId, DataConstants.SERVER_SCOPE, attributes).get(); - } catch (Exception e) { - log.error("Unable to save OAuth2 Client Registration Params to attributes!", e); - throw new IncorrectParameterException("Unable to save OAuth2 Client Registration Params to attributes!"); - } + return getTenantOAuth2ClientsParams(tenantId); + } - } finally { - clientRegistrationSaveLock.unlock(); - } + @Transactional + private void transactionalSaveTenantOAuth2ClientsParams(TenantId tenantId, OAuth2ClientsParams oAuth2ClientsParams) { + long acquireStart = System.currentTimeMillis(); + lockService.transactionLock(LockKey.OAUTH2_CONFIG); + log.trace("[{}] Waited for lock {} ms.", tenantId, System.currentTimeMillis() - acquireStart); - return getTenantOAuth2ClientsParams(tenantId); + validateRegistrationIdUniqueness(oAuth2ClientsParams, tenantId); + + Set domainNames = oAuth2ClientsParams.getClientsDomainsParams().stream() + .map(OAuth2ClientsDomainParams::getDomainName) + .collect(Collectors.toSet()); + processTenantAdminSettings(tenantId, domainNames); + + List attributes = createOAuth2ClientsParamsAttributes(oAuth2ClientsParams); + try { + attributesService.save(tenantId, tenantId, DataConstants.SERVER_SCOPE, attributes).get(); + } catch (Exception e) { + log.error("Unable to save OAuth2 Client Registration Params to attributes!", e); + throw new IncorrectParameterException("Unable to save OAuth2 Client Registration Params to attributes!"); + } } private List createOAuth2ClientsParamsAttributes(OAuth2ClientsParams oAuth2ClientsParams) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/lock/LockRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/lock/LockRepository.java new file mode 100644 index 0000000000..eced945acd --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/lock/LockRepository.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.lock; + +public interface LockRepository { + void transactionLock(Integer key); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/lock/PsqlLockRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/lock/PsqlLockRepository.java new file mode 100644 index 0000000000..0ae44edffa --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/lock/PsqlLockRepository.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.lock; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; +import org.thingsboard.server.dao.util.PsqlDao; +import org.thingsboard.server.dao.util.SqlDao; + +@Slf4j +@SqlDao +@PsqlDao +@Repository +public class PsqlLockRepository implements LockRepository { + private static final String TRANSACTION_LOCK_QUERY = "SELECT pg_advisory_xact_lock(?)"; + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Override + public void transactionLock(Integer key) { + jdbcTemplate.query(TRANSACTION_LOCK_QUERY, + preparedStatement -> preparedStatement.setInt(1, key), + resultSet -> {} + ); + } +} From 3d533b7c2384f0aac122eb7e13c0bd39aeaa1bd3 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 13 Jul 2020 12:08:32 +0300 Subject: [PATCH 069/463] Add type and clear code --- ui-ngx/src/app/core/guards/redirect.guard.ts | 35 +++++++++++++++++++ .../home/pages/admin/admin-routing.module.ts | 3 +- .../admin/oauth2-settings.component.html | 32 ++++++----------- .../pages/admin/oauth2-settings.component.ts | 26 ++++++++------ .../src/app/shared/models/settings.models.ts | 3 +- .../assets/locale/locale.constant-en_US.json | 2 -- 6 files changed, 65 insertions(+), 36 deletions(-) create mode 100644 ui-ngx/src/app/core/guards/redirect.guard.ts diff --git a/ui-ngx/src/app/core/guards/redirect.guard.ts b/ui-ngx/src/app/core/guards/redirect.guard.ts new file mode 100644 index 0000000000..106cc0840b --- /dev/null +++ b/ui-ngx/src/app/core/guards/redirect.guard.ts @@ -0,0 +1,35 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; +import { AuthState } from '@core/auth/auth.models'; +import { select, Store } from '@ngrx/store'; +import { selectAuth } from '@core/auth/auth.selectors'; +import { take } from 'rxjs/operators'; +import { AppState } from '@core/core.state'; +import { Authority } from '@shared/models/authority.enum'; + +@Injectable({ + providedIn: 'root' +}) +export class RedirectGuard implements CanActivate { + constructor(private store: Store, + private router: Router) { } + + canActivate( + next: ActivatedRouteSnapshot, + state: RouterStateSnapshot) { + let auth: AuthState = null; + this.store.pipe(select(selectAuth), take(1)).subscribe( + (authState: AuthState) => { + auth = authState; + } + ); + + if (auth?.userDetails?.authority === Authority.TENANT_ADMIN) { + this.router.navigateByUrl('/settings/oauth2-settings'); + return false; + } + this.router.navigateByUrl('/settings/general'); + return false; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts index 329e313110..a6b18cfd29 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts @@ -23,6 +23,7 @@ import { Authority } from '@shared/models/authority.enum'; import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component'; import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component'; import { OAuth2SettingsComponent } from '@home/pages/admin/oauth2-settings.component'; +import { RedirectGuard } from '@core/guards/redirect.guard'; const routes: Routes = [ { @@ -37,7 +38,7 @@ const routes: Routes = [ children: [ { path: '', - redirectTo: Authority.TENANT_ADMIN ? 'oauth2-settings' : 'general', + canActivate: [RedirectGuard], pathMatch: 'full' }, { diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html index 5c8c92658e..e5ea872fbc 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -80,26 +80,16 @@ delete

-
- - admin.oauth2.registration-id - - - {{ 'admin.oauth2.registration-id-required' | translate }} - - - {{ 'admin.oauth2.registration-id-unique' | translate }} - - - - - admin.oauth2.client-name - - - {{ 'admin.oauth2.client-name-required' | translate }} - - -
+ + admin.oauth2.registration-id + + + {{ 'admin.oauth2.registration-id-required' | translate }} + + + {{ 'admin.oauth2.registration-id-unique' | translate }} + +
@@ -310,7 +300,7 @@ common.password - +
diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index 47333a99b1..ab472643a6 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -16,7 +16,14 @@ import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { ClientRegistration, DomainParams, OAuth2Settings } from '@shared/models/settings.models'; +import { + ClientAuthenticationMethod, + ClientRegistration, + DomainParams, + MapperConfigType, + OAuth2Settings, + TenantNameStrategy +} from '@shared/models/settings.models'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { AdminService } from '@core/http/admin.service'; @@ -44,9 +51,9 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha oauth2SettingsForm: FormGroup; oauth2Settings: OAuth2Settings; - clientAuthenticationMethods = ['basic', 'post']; - converterTypesExternalUser = ['BASIC', 'CUSTOM']; - tenantNameStrategies = ['DOMAIN', 'EMAIL', 'CUSTOM']; + clientAuthenticationMethods: ClientAuthenticationMethod[] = ['BASIC', 'POST']; + converterTypesExternalUser: MapperConfigType[] = ['BASIC', 'CUSTOM']; + tenantNameStrategies: TenantNameStrategy[] = ['DOMAIN', 'EMAIL', 'CUSTOM']; constructor(protected store: Store, private adminService: AdminService, @@ -117,7 +124,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } private initOAuth2Settings(oauth2Settings: OAuth2Settings): void { - if(oauth2Settings.clientsDomainsParams) { + if (oauth2Settings.clientsDomainsParams) { oauth2Settings.clientsDomainsParams.forEach((domaindomain) => { this.clientsDomainsParams.push(this.buildSettingsDomain(domaindomain)); }); @@ -173,7 +180,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha formDomain.get('redirectUriTemplate').patchValue(uri); })); - if(domainParams){ + if (domainParams) { domainParams.clientRegistrations.forEach((registration) => { this.clientDomainRegistrations(formDomain).push(this.buildSettingsRegistration(registration)); }) @@ -187,7 +194,6 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha private buildSettingsRegistration(registrationData?: ClientRegistration): FormGroup { const clientRegistration = this.fb.group({ registrationId: [null, [Validators.required, this.uniqueRegistrationIdValidator]], - clientName: [null, [Validators.required]], loginButtonLabel: [null, [Validators.required]], loginButtonIcon: [null], clientId: ['', [Validators.required]], @@ -197,7 +203,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha scope: this.fb.array([], [Validators.required]), jwkSetUri: ['', [Validators.required, Validators.pattern(this.URL_REGEXP)]], userInfoUri: ['', [Validators.required, Validators.pattern(this.URL_REGEXP)]], - clientAuthenticationMethod: ['post', [Validators.required]], + clientAuthenticationMethod: ['POST', [Validators.required]], userNameAttributeName: ['email', [Validators.required]], mapperConfig: this.fb.group({ allowUserCreation: [true], @@ -219,11 +225,11 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } })); - if(registrationData){ + if (registrationData) { registrationData.scope.forEach(() => { (clientRegistration.get('scope') as FormArray).push(this.fb.control('')) }) - if(registrationData.mapperConfig.type !== 'BASIC'){ + if (registrationData.mapperConfig.type !== 'BASIC') { clientRegistration.get('mapperConfig.type').patchValue('CUSTOM'); } } diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index c52c759719..c4a1504f0f 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -23,7 +23,7 @@ export interface AdminSettings { export declare type SmtpProtocol = 'smtp' | 'smtps'; -export declare type ClientAuthenticationMethod = 'basic' | 'post'; +export declare type ClientAuthenticationMethod = 'BASIC' | 'POST'; export declare type MapperConfigType = 'BASIC' | 'CUSTOM'; export declare type TenantNameStrategy = 'DOMAIN' | 'EMAIL' | 'CUSTOM'; @@ -77,7 +77,6 @@ export interface DomainParams { export interface ClientRegistration { registrationId: string; - clientName: string; loginButtonLabel: string; loginButtonIcon: string; clientId: string; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 0866117933..239a580ef7 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -131,8 +131,6 @@ "registration-id": "Registration ID", "registration-id-required": "Registration ID is required.", "registration-id-unique": "Registration ID need to unique for the system.", - "client-name": "Client name", - "client-name-required": "Client name is required.", "client-id": "Client ID", "client-id-required": "Client ID is required.", "client-secret": "Client secret", From 294664b74607e261e9924fc3208effe78512f14a Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 23 Jul 2020 12:54:24 +0300 Subject: [PATCH 070/463] Minnor fix --- ui-ngx/src/app/core/guards/redirect.guard.ts | 16 ++++++++++++++++ .../pages/admin/oauth2-settings.component.html | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/core/guards/redirect.guard.ts b/ui-ngx/src/app/core/guards/redirect.guard.ts index 106cc0840b..652f1ff026 100644 --- a/ui-ngx/src/app/core/guards/redirect.guard.ts +++ b/ui-ngx/src/app/core/guards/redirect.guard.ts @@ -1,3 +1,19 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; import { AuthState } from '@core/auth/auth.models'; diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html index e5ea872fbc..f1c3bfaf62 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -295,7 +295,7 @@
common.username - + From 21275c789fc6ea136eaacbe92843b0b45beec030 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 23 Jul 2020 13:02:07 +0300 Subject: [PATCH 071/463] Improvement Translated --- ui-ngx/src/app/core/services/menu.service.ts | 2 +- ui-ngx/src/assets/locale/locale.constant-en_US.json | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index 429f65b736..bb39cfa415 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -239,7 +239,7 @@ export class MenuService { if (this.authState.allowOAuth2Configuration) { sections.push({ - name: 'admin.system-settings', + name: 'admin.settings', type: 'toggle', path: '/settings', height: '40px', diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 239a580ef7..d12d3a0e36 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -99,7 +99,7 @@ "proxy-user": "Proxy user", "proxy-password": "Proxy password", "send-test-mail": "Send test mail", - "security-settings": "Security settings", + "security-settings": "Security Settings", "password-policy": "Password policy", "minimum-password-length": "Minimum password length", "minimum-password-length-required": "Minimum password length is required", @@ -126,8 +126,9 @@ "add-domain": "Add domain", "new-domain": "New domain", "add-registration": "Add registration", + "settings": "Settings", "oauth2": { - "settings": "OAuth2 settings", + "settings": "OAuth2 Settings", "registration-id": "Registration ID", "registration-id-required": "Registration ID is required.", "registration-id-unique": "Registration ID need to unique for the system.", From 58464c54dc9f529c43eeb190aecb9ee8148a3d05 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Mon, 3 Aug 2020 17:31:02 +0300 Subject: [PATCH 072/463] Fix merge --- .../data/kv/BaseEntityAttributeKvEntry.java | 15 +++++---------- .../common/data/kv/EntityAttributeKvEntry.java | 5 ++--- .../server/dao/lock/LockServiceImpl.java | 4 +++- .../server/dao/model/sql/AttributeKvEntity.java | 2 +- .../server/dao/oauth2/OAuth2ServiceImpl.java | 6 +++--- .../server/dao/sql/lock/PsqlLockRepository.java | 2 -- 6 files changed, 14 insertions(+), 20 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseEntityAttributeKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseEntityAttributeKvEntry.java index aac91f5613..5c97acd90e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseEntityAttributeKvEntry.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseEntityAttributeKvEntry.java @@ -19,26 +19,21 @@ import lombok.EqualsAndHashCode; import lombok.ToString; import org.thingsboard.server.common.data.EntityType; +import java.util.UUID; + @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) public class BaseEntityAttributeKvEntry extends BaseAttributeKvEntry implements EntityAttributeKvEntry { - private final EntityType entityType; - private final String entityId; + private final UUID entityId; - public BaseEntityAttributeKvEntry(EntityType entityType, String entityId, long lastUpdateTs, KvEntry kv) { + public BaseEntityAttributeKvEntry(UUID entityId, long lastUpdateTs, KvEntry kv) { super(kv, lastUpdateTs); - this.entityType = entityType; this.entityId = entityId; } @Override - public EntityType getEntityType() { - return entityType; - } - - @Override - public String getEntityId() { + public UUID getEntityId() { return entityId; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/EntityAttributeKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/EntityAttributeKvEntry.java index b62576b8e0..ceb6c62a3c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/EntityAttributeKvEntry.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/EntityAttributeKvEntry.java @@ -15,10 +15,9 @@ */ package org.thingsboard.server.common.data.kv; -import org.thingsboard.server.common.data.EntityType; +import java.util.UUID; public interface EntityAttributeKvEntry extends AttributeKvEntry { - EntityType getEntityType(); - String getEntityId(); + UUID getEntityId(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/lock/LockServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/lock/LockServiceImpl.java index 703a6686a8..ff1e052f97 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/lock/LockServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/lock/LockServiceImpl.java @@ -31,7 +31,9 @@ public class LockServiceImpl implements LockService { @PostConstruct public void init(){ - log.warn("Locking with DB is not enabled."); + if (lockRepository == null) { + log.warn("Locking with DB is not enabled."); + } } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java index 7787f21830..c652a67469 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java @@ -89,6 +89,6 @@ public class AttributeKvEntity implements ToData, Serializable kvEntry = new JsonDataEntry(id.getAttributeKey(), jsonValue); } - return new BaseEntityAttributeKvEntry(id.getEntityType(), id.getEntityId(), lastUpdateTs, kvEntry); + return new BaseEntityAttributeKvEntry(id.getEntityId(), lastUpdateTs, kvEntry); }; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 90d19a19f3..df1cd5a049 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -333,14 +333,14 @@ public class OAuth2ServiceImpl implements OAuth2Service { @Override public Map getAllOAuth2ClientsParams() { OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParams(); - ListenableFuture> jsonFuture = getAllOAuth2ClientsParamsAttribute(); + ListenableFuture> jsonFuture = getAllOAuth2ClientsParamsAttribute(); try { return Futures.transform(jsonFuture, clientsParamsByKvEntryKey -> { Map tenantClientParams = clientsParamsByKvEntryKey != null ? clientsParamsByKvEntryKey.entrySet().stream() .collect(Collectors.toMap( - entry -> new TenantId(UUIDConverter.fromString(entry.getKey())), + entry -> new TenantId(entry.getKey()), entry -> constructOAuth2ClientsParams(entry.getValue()) )) : new HashMap<>(); @@ -405,7 +405,7 @@ public class OAuth2ServiceImpl implements OAuth2Service { } // TODO maybe it's better to load all tenants and get attribute for each one - private ListenableFuture> getAllOAuth2ClientsParamsAttribute() { + private ListenableFuture> getAllOAuth2ClientsParamsAttribute() { ListenableFuture> entityAttributeKvEntriesFuture; try { entityAttributeKvEntriesFuture = attributesService.findAllByAttributeKey(OAUTH2_CLIENT_REGISTRATIONS_PARAMS); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/lock/PsqlLockRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/lock/PsqlLockRepository.java index 0ae44edffa..76c4b6ebc8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/lock/PsqlLockRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/lock/PsqlLockRepository.java @@ -20,10 +20,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import org.thingsboard.server.dao.util.PsqlDao; -import org.thingsboard.server.dao.util.SqlDao; @Slf4j -@SqlDao @PsqlDao @Repository public class PsqlLockRepository implements LockRepository { From 61a6fdbc03694ac4656a2bafc1e8be4018dfc49d Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Wed, 5 Aug 2020 17:35:36 +0300 Subject: [PATCH 073/463] Moved OAuth2 config to separate table in DB --- .../main/data/upgrade/3.1.0/schema_update.sql | 53 ++ .../server/controller/BaseController.java | 35 +- .../server/controller/OAuth2Controller.java | 67 +- .../install/ThingsboardInstallService.java | 3 + .../install/SqlDatabaseUpgradeService.java | 9 + .../Oauth2AuthenticationSuccessHandler.java | 6 +- .../service/security/permission/Resource.java | 2 +- .../permission/TenantAdminPermissions.java | 8 + .../server/dao/oauth2/OAuth2Service.java | 29 +- .../server/common/data/EntityType.java | 2 +- .../common/data/id/EntityIdFactory.java | 2 + ...d.java => OAuth2ClientRegistrationId.java} | 16 +- .../common/data/oauth2/OAuth2ClientInfo.java | 19 +- .../data/oauth2/OAuth2ClientRegistration.java | 41 +- .../data/oauth2/OAuth2ClientsParams.java | 2 +- .../server/dao/model/ModelConstants.java | 6 +- .../sql/OAuth2ClientRegistrationEntity.java | 204 ++++++ .../HybridClientRegistrationRepository.java | 10 +- .../oauth2/OAuth2ClientRegistrationDao.java | 36 + .../server/dao/oauth2/OAuth2ServiceImpl.java | 644 +++++------------- .../server/dao/oauth2/OAuth2Utils.java | 18 - .../JpaOAuth2ClientRegistrationDao.java | 86 +++ .../OAuth2ClientRegistrationRepository.java | 37 + .../server/dao/tenant/TenantServiceImpl.java | 2 +- .../resources/sql/schema-entities-hsql.sql | 37 + .../main/resources/sql/schema-entities.sql | 36 + .../dao/service/BaseOAuth2ServiceTest.java | 471 ++++--------- .../resources/sql/hsql/drop-all-tables.sql | 1 + .../resources/sql/psql/drop-all-tables.sql | 1 + .../sql/timescale/drop-all-tables.sql | 1 + 30 files changed, 939 insertions(+), 945 deletions(-) create mode 100644 application/src/main/data/upgrade/3.1.0/schema_update.sql rename common/data/src/main/java/org/thingsboard/server/common/data/id/{OAuth2IntegrationId.java => OAuth2ClientRegistrationId.java} (63%) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java diff --git a/application/src/main/data/upgrade/3.1.0/schema_update.sql b/application/src/main/data/upgrade/3.1.0/schema_update.sql new file mode 100644 index 0000000000..06daa9541c --- /dev/null +++ b/application/src/main/data/upgrade/3.1.0/schema_update.sql @@ -0,0 +1,53 @@ +-- +-- Copyright © 2016-2020 The Thingsboard Authors +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +DROP TABLE IF EXISTS oauth2_client_registration; + +CREATE TABLE IF NOT EXISTS oauth2_client_registration ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + created_time bigint NOT NULL, + additional_info varchar, + tenant_id uuid, + registration_id varchar(255), + domain_name varchar(255), + client_id varchar(255), + client_secret varchar(255), + authorization_uri varchar(255), + token_uri varchar(255), + redirect_uri_template varchar(255), + scope varchar(255), + user_info_uri varchar(255), + user_name_attribute_name varchar(255), + jwk_set_uri varchar(255), + client_authentication_method varchar(255), + login_button_label varchar(255), + login_button_icon varchar(255), + allow_user_creation boolean, + activate_user boolean, + type varchar(31), + basic_email_attribute_key varchar(31), + basic_first_name_attribute_key varchar(31), + basic_last_name_attribute_key varchar(31), + basic_tenant_name_strategy varchar(31), + basic_tenant_name_pattern varchar(255), + basic_customer_name_pattern varchar(255), + basic_default_dashboard_name varchar(255), + basic_always_full_screen boolean, + custom_url varchar(255), + custom_username varchar(255), + custom_password varchar(255), + CONSTRAINT oauth2_registration_id_unq_key UNIQUE (registration_id) +); \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 2c202e71b2..3757223fda 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -47,22 +47,10 @@ import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.data.id.AlarmId; -import org.thingsboard.server.common.data.id.AssetId; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DashboardId; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.EntityIdFactory; -import org.thingsboard.server.common.data.id.EntityViewId; -import org.thingsboard.server.common.data.id.RuleChainId; -import org.thingsboard.server.common.data.id.RuleNodeId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.id.WidgetTypeId; -import org.thingsboard.server.common.data.id.WidgetsBundleId; +import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.DataType; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; @@ -87,6 +75,7 @@ import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; @@ -161,6 +150,9 @@ public abstract class BaseController { @Autowired protected DashboardService dashboardService; + @Autowired + protected OAuth2Service oAuth2Service; + @Autowired protected ComponentDiscoveryService componentDescriptorService; @@ -390,6 +382,9 @@ public abstract class BaseController { case WIDGET_TYPE: checkWidgetTypeId(new WidgetTypeId(entityId.getId()), operation); return; + case OAUTH2_CLIENT_REGISTRATION: + checkOAuth2ClientRegistrationId(new OAuth2ClientRegistrationId(entityId.getId()), operation); + return; default: throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType()); } @@ -542,6 +537,18 @@ public abstract class BaseController { } } + OAuth2ClientRegistration checkOAuth2ClientRegistrationId(OAuth2ClientRegistrationId clientRegistrationId, Operation operation) throws ThingsboardException { + try { + validateId(clientRegistrationId, "Incorrect oAuth2ClientRegistrationId " + clientRegistrationId); + OAuth2ClientRegistration clientRegistration = oAuth2Service.findClientRegistrationById(getCurrentUser().getTenantId(), clientRegistrationId); + checkNotNull(clientRegistration); + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION, operation, clientRegistrationId, clientRegistration); + return clientRegistration; + } catch (Exception e) { + throw handleException(e, false); + } + } + ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException { try { log.debug("[{}] Lookup component descriptor", clazz); diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index ba300eec71..9033b17c47 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -20,8 +20,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; +import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; @@ -40,14 +44,14 @@ import java.util.List; @RequestMapping("/api") @Slf4j public class OAuth2Controller extends BaseController { - @Autowired - private OAuth2Service oauth2Service; + public static final String CLIENT_REGISTRATION_ID = "clientRegistrationId"; + private static final String REGISTRATION_ID = "registrationId"; @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) @ResponseBody public List getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { try { - return oauth2Service.getOAuth2Clients(request.getServerName()); + return oAuth2Service.getOAuth2Clients(request.getServerName()); } catch (Exception e) { throw handleException(e); } @@ -60,13 +64,15 @@ public class OAuth2Controller extends BaseController { try { Authority authority = getCurrentUser().getAuthority(); checkOAuth2ConfigPermissions(Operation.READ); - OAuth2ClientsParams oAuth2ClientsParams = null; + List clientRegistrations = null; if (Authority.SYS_ADMIN.equals(authority)) { - oAuth2ClientsParams = oauth2Service.getSystemOAuth2ClientsParams(); + clientRegistrations = oAuth2Service.findClientRegistrationsByTenantId(TenantId.SYS_TENANT_ID); } else if (Authority.TENANT_ADMIN.equals(authority)) { - oAuth2ClientsParams = oauth2Service.getTenantOAuth2ClientsParams(getCurrentUser().getTenantId()); + clientRegistrations = oAuth2Service.findClientRegistrationsByTenantId(getCurrentUser().getTenantId()); + } else { + throw new IllegalStateException("Authority " + authority + " cannot get client registrations."); } - return oAuth2ClientsParams; + return new OAuth2ClientsParams(clientRegistrations); } catch (Exception e) { throw handleException(e); } @@ -75,18 +81,37 @@ public class OAuth2Controller extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/oauth2/config", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) - public OAuth2ClientsParams saveClientParams(@RequestBody OAuth2ClientsParams oAuth2ClientsParams) throws ThingsboardException { + public OAuth2ClientRegistration saveClientRegistration(@RequestBody OAuth2ClientRegistration clientRegistration) throws ThingsboardException { try { - Authority authority = getCurrentUser().getAuthority(); - checkOAuth2ConfigPermissions(Operation.WRITE); - OAuth2ClientsParams savedOAuth2ClientsParams = null; - if (Authority.SYS_ADMIN.equals(authority)) { - savedOAuth2ClientsParams = oauth2Service.saveSystemOAuth2ClientsParams(oAuth2ClientsParams); - } else if (Authority.TENANT_ADMIN.equals(authority)) { - savedOAuth2ClientsParams = oauth2Service.saveTenantOAuth2ClientsParams(getCurrentUser().getTenantId(), oAuth2ClientsParams); - } - return savedOAuth2ClientsParams; + clientRegistration.setTenantId(getCurrentUser().getTenantId()); + checkEntity(clientRegistration.getId(), clientRegistration, Resource.OAUTH2_CONFIGURATION); + return oAuth2Service.saveClientRegistration(clientRegistration); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(value = "/oauth2/config/{clientRegistrationId}", method = RequestMethod.DELETE) + @ResponseStatus(value = HttpStatus.OK) + public void deleteClientRegistration(@PathVariable(CLIENT_REGISTRATION_ID) String strClientRegistrationId) throws ThingsboardException { + checkParameter(CLIENT_REGISTRATION_ID, strClientRegistrationId); + try { + OAuth2ClientRegistrationId clientRegistrationId = new OAuth2ClientRegistrationId(toUUID(strClientRegistrationId)); + OAuth2ClientRegistration clientRegistration = checkOAuth2ClientRegistrationId(clientRegistrationId, Operation.DELETE); + oAuth2Service.deleteClientRegistrationById(getCurrentUser().getTenantId(), clientRegistrationId); + + logEntityAction(clientRegistrationId, clientRegistration, + null, + ActionType.DELETED, null, strClientRegistrationId); + } catch (Exception e) { + + logEntityAction(emptyId(EntityType.OAUTH2_CLIENT_REGISTRATION), + null, + null, + ActionType.DELETED, e, strClientRegistrationId); + throw handleException(e); } } @@ -96,7 +121,7 @@ public class OAuth2Controller extends BaseController { @ResponseBody public Boolean isOAuth2ConfigurationAllowed() throws ThingsboardException { try { - return oauth2Service.isOAuth2ClientRegistrationAllowed(getTenantId()); + return oAuth2Service.isOAuth2ClientRegistrationAllowed(getTenantId()); } catch (Exception e) { throw handleException(e); } @@ -105,4 +130,8 @@ public class OAuth2Controller extends BaseController { private void checkOAuth2ConfigPermissions(Operation operation) throws ThingsboardException { accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION, operation); } + + private void checkOAuth2ConfigPermissions(Operation operation, OAuth2ClientRegistration clientRegistration) throws ThingsboardException { + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION, operation, clientRegistration.getId(), clientRegistration); + } } diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 7d80b115d5..1b1355571d 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -173,6 +173,9 @@ public class ThingsboardInstallService { databaseEntitiesUpgradeService.upgradeDatabase("3.0.1"); log.info("Updating system data..."); systemDataLoaderService.updateSystemWidgets(); + case "3.1.0": + log.info("Upgrading ThingsBoard from version 3.1.0 to 3.2.0 ..."); + databaseEntitiesUpgradeService.upgradeDatabase("3.1.0"); break; default: throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion); diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index e21472ee12..6f60cc345b 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -294,6 +294,15 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService } catch (Exception e) { log.error("Failed updating schema!!!", e); } + case "3.1.0": + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + log.info("Updating schema ..."); + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.0", "schema_update.sql"); + loadSql(schemaUpdateFile, conn); + log.info("Schema updated."); + } catch (Exception e) { + log.error("Failed updating schema!!!", e); + } break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index 0cff27ae2e..e3d21b448d 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -65,11 +65,9 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS try { OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; - Pair clientRegistrationPair = oAuth2Service.getClientRegistrationWithTenant(token.getAuthorizedClientRegistrationId()); - TenantId tenantId = clientRegistrationPair.getKey(); - OAuth2ClientRegistration clientRegistration = clientRegistrationPair.getValue(); + OAuth2ClientRegistration clientRegistration = oAuth2Service.findClientRegistrationByRegistrationId(token.getAuthorizedClientRegistrationId()); OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(clientRegistration.getMapperConfig().getType()); - SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, tenantId, clientRegistration.getMapperConfig()); + SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, clientRegistration.getTenantId(), clientRegistration.getMapperConfig()); JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java index dfdea59e05..84819dda48 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java @@ -32,7 +32,7 @@ public enum Resource { USER(EntityType.USER), WIDGETS_BUNDLE(EntityType.WIDGETS_BUNDLE), WIDGET_TYPE(EntityType.WIDGET_TYPE), - OAUTH2_CONFIGURATION(), + OAUTH2_CONFIGURATION(EntityType.OAUTH2_CLIENT_REGISTRATION), ; private final EntityType entityType; diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java index 703c238c64..8476994a4e 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java @@ -113,5 +113,13 @@ public class TenantAdminPermissions extends AbstractPermissions { public boolean hasPermission(SecurityUser user, Operation operation) { return oAuth2Service.isOAuth2ClientRegistrationAllowed(user.getTenantId()); } + + @Override + public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { + if (!user.getTenantId().equals(entity.getTenantId())) { + return false; + } + return hasPermission(user, operation); + } }; } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java index bfeb8f52a1..6d4be61dc7 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java @@ -15,39 +15,30 @@ */ package org.thingsboard.server.dao.oauth2; -import org.apache.commons.lang3.tuple.Pair; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.oauth2.ExtendedOAuth2ClientRegistration; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; import java.util.List; -import java.util.Map; +import java.util.UUID; public interface OAuth2Service { - Pair getClientRegistrationWithTenant(String registrationId); - - ExtendedOAuth2ClientRegistration getExtendedClientRegistration(String registrationId); - List getOAuth2Clients(String domainName); - OAuth2ClientsParams saveSystemOAuth2ClientsParams(OAuth2ClientsParams oAuth2ClientsParams); + OAuth2ClientRegistration saveClientRegistration(OAuth2ClientRegistration clientRegistration); - OAuth2ClientsParams saveTenantOAuth2ClientsParams(TenantId tenantId, OAuth2ClientsParams oAuth2ClientsParams); + List findClientRegistrationsByTenantId(TenantId tenantId); - OAuth2ClientsParams getSystemOAuth2ClientsParams(); + OAuth2ClientRegistration findClientRegistrationByRegistrationId(String registrationId); - OAuth2ClientsParams getTenantOAuth2ClientsParams(TenantId tenantId); + OAuth2ClientRegistration findClientRegistrationById(TenantId tenantId, OAuth2ClientRegistrationId id); - void deleteTenantOAuth2ClientsParams(TenantId tenantId); + List findAllClientRegistrations(); - void deleteSystemOAuth2ClientsParams(); + void deleteClientRegistrationsByTenantId(TenantId tenantId); - boolean isOAuth2ClientRegistrationAllowed(TenantId tenantId); - - Map getAllOAuth2ClientsParams(); + void deleteClientRegistrationById(TenantId tenantId, OAuth2ClientRegistrationId id); + boolean isOAuth2ClientRegistrationAllowed(TenantId tenantId); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index 7e43c59797..d3ea208aa5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -19,5 +19,5 @@ package org.thingsboard.server.common.data; * @author Andrew Shvayka */ public enum EntityType { - TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE + TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, OAUTH2_CLIENT_REGISTRATION } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java index 11db1da1a0..03f05c54a4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java @@ -62,6 +62,8 @@ public class EntityIdFactory { return new WidgetsBundleId(uuid); case WIDGET_TYPE: return new WidgetTypeId(uuid); + case OAUTH2_CLIENT_REGISTRATION: + return new OAuth2ClientRegistrationId(uuid); } throw new IllegalArgumentException("EntityType " + type + " is not supported!"); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2IntegrationId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationId.java similarity index 63% rename from common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2IntegrationId.java rename to common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationId.java index 30fd55d204..3e4ad56f19 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2IntegrationId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationId.java @@ -17,19 +17,23 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -public class OAuth2IntegrationId extends UUIDBased { - - private static final long serialVersionUID = 1L; +public class OAuth2ClientRegistrationId extends UUIDBased implements EntityId { @JsonCreator - public OAuth2IntegrationId(@JsonProperty("id") UUID id) { + public OAuth2ClientRegistrationId(@JsonProperty("id") UUID id) { super(id); } - public static OAuth2IntegrationId fromString(String oauth2IntegrationId) { - return new OAuth2IntegrationId(UUID.fromString(oauth2IntegrationId)); + public static OAuth2ClientRegistrationId fromString(String clientRegistrationId) { + return new OAuth2ClientRegistrationId(UUID.fromString(clientRegistrationId)); + } + + @Override + public EntityType getEntityType() { + return EntityType.OAUTH2_CLIENT_REGISTRATION; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientInfo.java index 0ee5832e63..f15706a55e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientInfo.java @@ -17,27 +17,20 @@ package org.thingsboard.server.common.data.oauth2; import lombok.Data; import lombok.EqualsAndHashCode; -import org.thingsboard.server.common.data.BaseData; -import org.thingsboard.server.common.data.id.OAuth2IntegrationId; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; -@EqualsAndHashCode(callSuper = true) +@EqualsAndHashCode @Data -public class OAuth2ClientInfo extends BaseData { +@NoArgsConstructor +@AllArgsConstructor +public class OAuth2ClientInfo { private String name; private String icon; private String url; - public OAuth2ClientInfo() { - super(); - } - - public OAuth2ClientInfo(OAuth2IntegrationId id) { - super(id); - } - public OAuth2ClientInfo(OAuth2ClientInfo oauth2ClientInfo) { - super(oauth2ClientInfo); this.name = oauth2ClientInfo.getName(); this.icon = oauth2ClientInfo.getIcon(); this.url = oauth2ClientInfo.getUrl(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java index 0a4156bcc5..a0a7dcc48b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java @@ -15,21 +15,26 @@ */ package org.thingsboard.server.common.data.oauth2; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.*; import org.thingsboard.server.common.data.BaseData; -import org.thingsboard.server.common.data.id.OAuth2IntegrationId; +import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; +import org.thingsboard.server.common.data.id.TenantId; import java.util.List; -@EqualsAndHashCode +@EqualsAndHashCode(callSuper = true) @Data @ToString(exclude = {"clientSecret"}) -@Builder(toBuilder = true) @NoArgsConstructor -@AllArgsConstructor -public class OAuth2ClientRegistration { +public class OAuth2ClientRegistration extends BaseData implements HasTenantId, HasName { + private TenantId tenantId; private String registrationId; + private String domainName; + private String redirectUriTemplate; private OAuth2MapperConfig mapperConfig; private String clientId; private String clientSecret; @@ -42,4 +47,30 @@ public class OAuth2ClientRegistration { private String clientAuthenticationMethod; private String loginButtonLabel; private String loginButtonIcon; + + public OAuth2ClientRegistration(OAuth2ClientRegistration clientRegistration) { + super(clientRegistration); + this.tenantId = clientRegistration.getTenantId(); + this.registrationId = clientRegistration.getRegistrationId(); + this.domainName = clientRegistration.getDomainName(); + this.redirectUriTemplate = clientRegistration.getRedirectUriTemplate(); + this.mapperConfig = clientRegistration.mapperConfig; + this.clientId = clientRegistration.clientId; + this.clientSecret = clientRegistration.clientSecret; + this.authorizationUri = clientRegistration.authorizationUri; + this.accessTokenUri = clientRegistration.accessTokenUri; + this.scope = clientRegistration.scope; + this.userInfoUri = clientRegistration.userInfoUri; + this.userNameAttributeName = clientRegistration.userNameAttributeName; + this.jwkSetUri = clientRegistration.jwkSetUri; + this.clientAuthenticationMethod = clientRegistration.clientAuthenticationMethod; + this.loginButtonLabel = clientRegistration.loginButtonLabel; + this.loginButtonIcon = clientRegistration.loginButtonIcon; + } + + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getName() { + return loginButtonLabel; + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java index ef08a892a1..f442669b68 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java @@ -26,5 +26,5 @@ import java.util.List; @NoArgsConstructor @AllArgsConstructor public class OAuth2ClientsParams { - private List clientsDomainsParams; + private List clientRegistrations; } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index b7748d46da..0c31675dc2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -355,22 +355,22 @@ public class ModelConstants { public static final String RULE_NODE_CONFIGURATION_PROPERTY = "configuration"; /** - * Cassandra OAuth2 client registration constants. + * OAuth2 client registration constants. */ + public static final String OAUTH2_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; public static final String OAUTH2_CLIENT_REGISTRATION_COLUMN_FAMILY_NAME = "oauth2_client_registration"; public static final String OAUTH2_CLIENT_REGISTRATION_ID_PROPERTY = "registration_id"; + public static final String OAUTH2_DOMAIN_NAME_PROPERTY = "domain_name"; public static final String OAUTH2_CLIENT_ID_PROPERTY = "client_id"; public static final String OAUTH2_CLIENT_SECRET_PROPERTY = "client_secret"; public static final String OAUTH2_AUTHORIZATION_URI_PROPERTY = "authorization_uri"; public static final String OAUTH2_TOKEN_URI_PROPERTY = "token_uri"; public static final String OAUTH2_REDIRECT_URI_TEMPLATE_PROPERTY = "redirect_uri_template"; public static final String OAUTH2_SCOPE_PROPERTY = "scope"; - public static final String OAUTH2_AUTHORIZATION_GRANT_TYPE_PROPERTY = "authorization_grant_type"; public static final String OAUTH2_USER_INFO_URI_PROPERTY = "user_info_uri"; public static final String OAUTH2_USER_NAME_ATTRIBUTE_NAME_PROPERTY = "user_name_attribute_name"; public static final String OAUTH2_JWK_SET_URI_PROPERTY = "jwk_set_uri"; public static final String OAUTH2_CLIENT_AUTHENTICATION_METHOD_PROPERTY = "client_authentication_method"; - public static final String OAUTH2_CLIENT_NAME_PROPERTY = "client_name"; public static final String OAUTH2_LOGIN_BUTTON_LABEL_PROPERTY = "login_button_label"; public static final String OAUTH2_LOGIN_BUTTON_ICON_PROPERTY = "login_button_icon"; public static final String OAUTH2_ALLOW_USER_CREATION_PROPERTY = "allow_user_creation"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java new file mode 100644 index 0000000000..0c62de3bb2 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java @@ -0,0 +1,204 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.oauth2.*; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.util.mapping.JsonStringType; + +import javax.persistence.*; +import java.util.Arrays; +import java.util.UUID; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@TypeDef(name = "json", typeClass = JsonStringType.class) +@Table(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_COLUMN_FAMILY_NAME) +public class OAuth2ClientRegistrationEntity extends BaseSqlEntity { + + @Column(name = ModelConstants.OAUTH2_TENANT_ID_PROPERTY, columnDefinition = "uuid") + private UUID tenantId; + + @Column(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_ID_PROPERTY) + private String registrationId; + @Column(name = ModelConstants.OAUTH2_DOMAIN_NAME_PROPERTY) + private String domainName; + @Column(name = ModelConstants.OAUTH2_CLIENT_ID_PROPERTY) + private String clientId; + @Column(name = ModelConstants.OAUTH2_CLIENT_SECRET_PROPERTY) + private String clientSecret; + @Column(name = ModelConstants.OAUTH2_AUTHORIZATION_URI_PROPERTY) + private String authorizationUri; + @Column(name = ModelConstants.OAUTH2_TOKEN_URI_PROPERTY) + private String tokenUri; + @Column(name = ModelConstants.OAUTH2_REDIRECT_URI_TEMPLATE_PROPERTY) + private String redirectUriTemplate; + @Column(name = ModelConstants.OAUTH2_SCOPE_PROPERTY) + private String scope; + @Column(name = ModelConstants.OAUTH2_USER_INFO_URI_PROPERTY) + private String userInfoUri; + @Column(name = ModelConstants.OAUTH2_USER_NAME_ATTRIBUTE_NAME_PROPERTY) + private String userNameAttributeName; + @Column(name = ModelConstants.OAUTH2_JWK_SET_URI_PROPERTY) + private String jwkSetUri; + @Column(name = ModelConstants.OAUTH2_CLIENT_AUTHENTICATION_METHOD_PROPERTY) + private String clientAuthenticationMethod; + @Column(name = ModelConstants.OAUTH2_LOGIN_BUTTON_LABEL_PROPERTY) + private String loginButtonLabel; + @Column(name = ModelConstants.OAUTH2_LOGIN_BUTTON_ICON_PROPERTY) + private String loginButtonIcon; + @Column(name = ModelConstants.OAUTH2_ALLOW_USER_CREATION_PROPERTY) + private Boolean allowUserCreation; + @Column(name = ModelConstants.OAUTH2_ACTIVATE_USER_PROPERTY) + private Boolean activateUser; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.OAUTH2_MAPPER_TYPE_PROPERTY) + private MapperType type; + @Column(name = ModelConstants.OAUTH2_EMAIL_ATTRIBUTE_KEY_PROPERTY) + private String emailAttributeKey; + @Column(name = ModelConstants.OAUTH2_FIRST_NAME_ATTRIBUTE_KEY_PROPERTY) + private String firstNameAttributeKey; + @Column(name = ModelConstants.OAUTH2_LAST_NAME_ATTRIBUTE_KEY_PROPERTY) + private String lastNameAttributeKey; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.OAUTH2_TENANT_NAME_STRATEGY_PROPERTY) + private TenantNameStrategyType tenantNameStrategy; + @Column(name = ModelConstants.OAUTH2_TENANT_NAME_PATTERN_PROPERTY) + private String tenantNamePattern; + @Column(name = ModelConstants.OAUTH2_CUSTOMER_NAME_PATTERN_PROPERTY) + private String customerNamePattern; + @Column(name = ModelConstants.OAUTH2_DEFAULT_DASHBOARD_NAME_PROPERTY) + private String defaultDashboardName; + @Column(name = ModelConstants.OAUTH2_ALWAYS_FULL_SCREEN_PROPERTY) + private Boolean alwaysFullScreen; + @Column(name = ModelConstants.OAUTH2_MAPPER_URL_PROPERTY) + private String url; + @Column(name = ModelConstants.OAUTH2_MAPPER_USERNAME_PROPERTY) + private String username; + @Column(name = ModelConstants.OAUTH2_MAPPER_PASSWORD_PROPERTY) + private String password; + + public OAuth2ClientRegistrationEntity() { + super(); + } + + public OAuth2ClientRegistrationEntity(OAuth2ClientRegistration clientRegistration) { + if (clientRegistration.getId() != null) { + this.setUuid(clientRegistration.getId().getId()); + } + if (clientRegistration.getTenantId() != null) { + this.tenantId = clientRegistration.getTenantId().getId(); + } + this.domainName = clientRegistration.getDomainName(); + this.createdTime = clientRegistration.getCreatedTime(); + this.registrationId = clientRegistration.getRegistrationId(); + this.clientId = clientRegistration.getClientId(); + this.clientSecret = clientRegistration.getClientSecret(); + this.authorizationUri = clientRegistration.getAuthorizationUri(); + this.tokenUri = clientRegistration.getAccessTokenUri(); + this.redirectUriTemplate = clientRegistration.getRedirectUriTemplate(); + this.scope = clientRegistration.getScope().stream().reduce((result, element) -> result + "," + element).orElse(""); + this.userInfoUri = clientRegistration.getUserInfoUri(); + this.userNameAttributeName = clientRegistration.getUserNameAttributeName(); + this.jwkSetUri = clientRegistration.getJwkSetUri(); + this.clientAuthenticationMethod = clientRegistration.getClientAuthenticationMethod(); + this.loginButtonLabel = clientRegistration.getLoginButtonLabel(); + this.loginButtonIcon = clientRegistration.getLoginButtonIcon(); + OAuth2MapperConfig mapperConfig = clientRegistration.getMapperConfig(); + if (mapperConfig != null) { + this.allowUserCreation = mapperConfig.isAllowUserCreation(); + this.activateUser = mapperConfig.isActivateUser(); + this.type = mapperConfig.getType(); + OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic(); + if (basicConfig != null) { + this.emailAttributeKey = basicConfig.getEmailAttributeKey(); + this.firstNameAttributeKey = basicConfig.getFirstNameAttributeKey(); + this.lastNameAttributeKey = basicConfig.getLastNameAttributeKey(); + this.tenantNameStrategy = basicConfig.getTenantNameStrategy(); + this.tenantNamePattern = basicConfig.getTenantNamePattern(); + this.customerNamePattern = basicConfig.getCustomerNamePattern(); + this.defaultDashboardName = basicConfig.getDefaultDashboardName(); + this.alwaysFullScreen = basicConfig.isAlwaysFullScreen(); + } + OAuth2CustomMapperConfig customConfig = mapperConfig.getCustom(); + if (customConfig != null) { + this.url = customConfig.getUrl(); + this.username = customConfig.getUsername(); + this.password = customConfig.getPassword(); + } + } + } + + @Override + public OAuth2ClientRegistration toData() { + OAuth2ClientRegistration clientRegistration = new OAuth2ClientRegistration(); + clientRegistration.setId(new OAuth2ClientRegistrationId(id)); + clientRegistration.setTenantId(new TenantId(tenantId)); + clientRegistration.setRegistrationId(registrationId); + clientRegistration.setDomainName(domainName); + clientRegistration.setCreatedTime(createdTime); + clientRegistration.setMapperConfig( + OAuth2MapperConfig.builder() + .allowUserCreation(allowUserCreation) + .activateUser(activateUser) + .type(type) + .basic( + type == MapperType.BASIC ? + OAuth2BasicMapperConfig.builder() + .emailAttributeKey(emailAttributeKey) + .firstNameAttributeKey(firstNameAttributeKey) + .lastNameAttributeKey(lastNameAttributeKey) + .tenantNameStrategy(tenantNameStrategy) + .tenantNamePattern(tenantNamePattern) + .customerNamePattern(customerNamePattern) + .defaultDashboardName(defaultDashboardName) + .alwaysFullScreen(alwaysFullScreen) + .build() + : null + ) + .custom( + type == MapperType.CUSTOM ? + OAuth2CustomMapperConfig.builder() + .url(url) + .username(username) + .password(password) + .build() + : null + ) + .build() + ); + clientRegistration.setClientId(clientId); + clientRegistration.setClientSecret(clientSecret); + clientRegistration.setAuthorizationUri(authorizationUri); + clientRegistration.setAccessTokenUri(tokenUri); + clientRegistration.setRedirectUriTemplate(redirectUriTemplate); + clientRegistration.setScope(Arrays.asList(scope.split(","))); + clientRegistration.setUserInfoUri(userInfoUri); + clientRegistration.setUserNameAttributeName(userNameAttributeName); + clientRegistration.setJwkSetUri(jwkSetUri); + clientRegistration.setClientAuthenticationMethod(clientAuthenticationMethod); + clientRegistration.setLoginButtonLabel(loginButtonLabel); + clientRegistration.setLoginButtonIcon(loginButtonIcon); + return clientRegistration; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java index 7309cc7edb..bdd97322a7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java @@ -32,19 +32,19 @@ public class HybridClientRegistrationRepository implements ClientRegistrationRep @Override public ClientRegistration findByRegistrationId(String registrationId) { - ExtendedOAuth2ClientRegistration localExtendedClientRegistration = oAuth2Service.getExtendedClientRegistration(registrationId); - return localExtendedClientRegistration == null ? - null : toSpringClientRegistration(localExtendedClientRegistration.getRedirectUriTemplate(), localExtendedClientRegistration.getClientRegistration()); + OAuth2ClientRegistration oAuth2ClientRegistration = oAuth2Service.findClientRegistrationByRegistrationId(registrationId); + return oAuth2ClientRegistration == null ? + null : toSpringClientRegistration(oAuth2ClientRegistration); } - private ClientRegistration toSpringClientRegistration(String redirectUriTemplate, OAuth2ClientRegistration localClientRegistration){ + private ClientRegistration toSpringClientRegistration(OAuth2ClientRegistration localClientRegistration){ return ClientRegistration.withRegistrationId(localClientRegistration.getRegistrationId()) .clientName(localClientRegistration.getRegistrationId()) .clientId(localClientRegistration.getClientId()) .authorizationUri(localClientRegistration.getAuthorizationUri()) .clientSecret(localClientRegistration.getClientSecret()) .tokenUri(localClientRegistration.getAccessTokenUri()) - .redirectUriTemplate(redirectUriTemplate) + .redirectUriTemplate(localClientRegistration.getRedirectUriTemplate()) .scope(localClientRegistration.getScope()) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .userInfoUri(localClientRegistration.getUserInfoUri()) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java new file mode 100644 index 0000000000..d262702efa --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.oauth2; + +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; +import org.thingsboard.server.dao.Dao; + +import java.util.List; +import java.util.UUID; + +public interface OAuth2ClientRegistrationDao extends Dao { + OAuth2ClientRegistration findByRegistrationId(String registrationId); + + List findAll(); + + List findByTenantId(UUID tenantId); + + List findByDomainName(String domainName); + + boolean removeByRegistrationId(String registrationId); + + int removeByTenantId(UUID tenantId); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index df1cd5a049..f06d906e7c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -15,366 +15,119 @@ */ package org.thingsboard.server.dao.oauth2; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; +import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.*; -import org.thingsboard.server.common.data.id.*; -import org.thingsboard.server.common.data.kv.*; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.*; -import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.exception.IncorrectParameterException; -import org.thingsboard.server.dao.lock.LockKey; -import org.thingsboard.server.dao.lock.LockService; -import org.thingsboard.server.dao.settings.AdminSettingsService; +import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.tenant.TenantService; -import java.io.IOException; -import java.util.*; -import java.util.concurrent.ExecutionException; -import java.util.function.Consumer; +import javax.transaction.Transactional; +import java.util.List; +import java.util.UUID; import java.util.stream.Collectors; -import static org.thingsboard.server.dao.oauth2.OAuth2Utils.*; +import static org.thingsboard.server.dao.oauth2.OAuth2Utils.ALLOW_OAUTH2_CONFIGURATION; +import static org.thingsboard.server.dao.service.Validator.validateId; +import static org.thingsboard.server.dao.service.Validator.validateString; @Slf4j @Service -public class OAuth2ServiceImpl implements OAuth2Service { - private static final ObjectMapper mapper = new ObjectMapper(); - - @Autowired - private Environment environment; - - @Autowired - private AdminSettingsService adminSettingsService; - - @Autowired - private AttributesService attributesService; +public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Service { + public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; + public static final String INCORRECT_CLIENT_REGISTRATION_ID = "Incorrect clientRegistrationId "; + public static final String INCORRECT_REGISTRATION_ID = "Incorrect registrationId "; + public static final String INCORRECT_DOMAIN_NAME = "Incorrect domainName "; @Autowired private TenantService tenantService; @Autowired - private LockService lockService; - - private boolean isInstall() { - return environment.acceptsProfiles("install"); - } - - @Override - public Pair getClientRegistrationWithTenant(String registrationId) { - return getExtendedOAuth2ClientRegistrationWithTenant(registrationId) - .map(pair -> ImmutablePair.of(pair.getLeft(), pair.getRight().getClientRegistration())) - .orElse(null); - } - - @Override - public ExtendedOAuth2ClientRegistration getExtendedClientRegistration(String registrationId) { - return getExtendedOAuth2ClientRegistrationWithTenant(registrationId) - .map(Pair::getValue) - .orElse(null); - - } - - private Optional> getExtendedOAuth2ClientRegistrationWithTenant(String registrationId) { - return getAllOAuth2ClientsParams().entrySet().stream() - .map(entry -> { - TenantId tenantId = entry.getKey(); - return entry.getValue().getClientsDomainsParams().stream() - .flatMap(domainParams -> - domainParams.getClientRegistrations().stream() - .map(clientRegistration -> new ExtendedOAuth2ClientRegistration(domainParams.getRedirectUriTemplate(), clientRegistration)) - ) - .filter(registration -> registrationId.equals(registration.getClientRegistration().getRegistrationId())) - .findFirst() - .map(extendedClientRegistration -> ImmutablePair.of(tenantId, extendedClientRegistration)) - .orElse(null); - - }) - .filter(Objects::nonNull) - .findFirst() - .map(entry -> ImmutablePair.of(entry.getKey(), entry.getValue())) - ; - } - + private OAuth2ClientRegistrationDao clientRegistrationDao; @Override public List getOAuth2Clients(String domainName) { - OAuth2ClientsDomainParams oAuth2ClientsDomainParams = getMergedOAuth2ClientsParams(domainName); - return oAuth2ClientsDomainParams != null && oAuth2ClientsDomainParams.getClientRegistrations() != null ? - oAuth2ClientsDomainParams.getClientRegistrations().stream() - .map(OAuth2Utils::toClientInfo) - .collect(Collectors.toList()) - : Collections.emptyList() - ; - } - - @Override - public OAuth2ClientsParams saveSystemOAuth2ClientsParams(OAuth2ClientsParams oAuth2ClientsParams) { - validate(oAuth2ClientsParams); - validateRegistrationIdUniqueness(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); - - transactionalSaveSystemOAuth2ClientsParams(oAuth2ClientsParams); - - return getSystemOAuth2ClientsParams(); - } - - @Transactional - private void transactionalSaveSystemOAuth2ClientsParams(OAuth2ClientsParams oAuth2ClientsParams) { - long acquireStart = System.currentTimeMillis(); - lockService.transactionLock(LockKey.OAUTH2_CONFIG); - log.trace("[{}] Waited for lock {} ms.", TenantId.SYS_TENANT_ID, System.currentTimeMillis() - acquireStart); - - validateRegistrationIdUniqueness(oAuth2ClientsParams, TenantId.SYS_TENANT_ID); - AdminSettings oauth2SystemAdminSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); - if (oauth2SystemAdminSettings == null) { - oauth2SystemAdminSettings = createSystemAdminSettings(); - } - String json = toJson(oAuth2ClientsParams); - ((ObjectNode) oauth2SystemAdminSettings.getJsonValue()).put(SYSTEM_SETTINGS_OAUTH2_VALUE, json); - adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, oauth2SystemAdminSettings); + log.trace("Executing getOAuth2Clients [{}]", domainName); + validateString(domainName, INCORRECT_DOMAIN_NAME + domainName); + return clientRegistrationDao.findByDomainName(domainName).stream() + .map(OAuth2Utils::toClientInfo) + .collect(Collectors.toList()); } @Override - public OAuth2ClientsParams saveTenantOAuth2ClientsParams(TenantId tenantId, OAuth2ClientsParams oAuth2ClientsParams) { - validate(oAuth2ClientsParams); - validateRegistrationIdUniqueness(oAuth2ClientsParams, tenantId); - - transactionalSaveTenantOAuth2ClientsParams(tenantId, oAuth2ClientsParams); - - return getTenantOAuth2ClientsParams(tenantId); - } - - @Transactional - private void transactionalSaveTenantOAuth2ClientsParams(TenantId tenantId, OAuth2ClientsParams oAuth2ClientsParams) { - long acquireStart = System.currentTimeMillis(); - lockService.transactionLock(LockKey.OAUTH2_CONFIG); - log.trace("[{}] Waited for lock {} ms.", tenantId, System.currentTimeMillis() - acquireStart); - - validateRegistrationIdUniqueness(oAuth2ClientsParams, tenantId); - - Set domainNames = oAuth2ClientsParams.getClientsDomainsParams().stream() - .map(OAuth2ClientsDomainParams::getDomainName) - .collect(Collectors.toSet()); - processTenantAdminSettings(tenantId, domainNames); - - List attributes = createOAuth2ClientsParamsAttributes(oAuth2ClientsParams); + public OAuth2ClientRegistration saveClientRegistration(OAuth2ClientRegistration clientRegistration) { + log.trace("Executing saveClientRegistration [{}]", clientRegistration); + clientRegistrationValidator.validate(clientRegistration, OAuth2ClientRegistration::getTenantId); + OAuth2ClientRegistration savedClientRegistration; try { - attributesService.save(tenantId, tenantId, DataConstants.SERVER_SCOPE, attributes).get(); - } catch (Exception e) { - log.error("Unable to save OAuth2 Client Registration Params to attributes!", e); - throw new IncorrectParameterException("Unable to save OAuth2 Client Registration Params to attributes!"); - } - } - - private List createOAuth2ClientsParamsAttributes(OAuth2ClientsParams oAuth2ClientsParams) { - String json = toJson(oAuth2ClientsParams); - List attributes = new ArrayList<>(); - long ts = System.currentTimeMillis(); - attributes.add(new BaseAttributeKvEntry(new StringDataEntry(OAUTH2_CLIENT_REGISTRATIONS_PARAMS, json), ts)); - return attributes; - } - - private void processTenantAdminSettings(TenantId tenantId, Set domainNames) { - OAuth2ClientsParams existentClientsParams = getTenantOAuth2ClientsParams(tenantId); - - Set existentDomainNames = existentClientsParams != null && existentClientsParams.getClientsDomainsParams() != null ? - existentClientsParams.getClientsDomainsParams().stream() - .map(OAuth2ClientsDomainParams::getDomainName) - .collect(Collectors.toSet()) - : Collections.emptySet(); - - Set domainNamesToAdd = domainNames.stream() - .filter(domainName -> !existentDomainNames.contains(domainName)) - .collect(Collectors.toSet()); - Set domainNamesToDelete = existentDomainNames.stream() - .filter(domainName -> !domainNames.contains(domainName)) - .collect(Collectors.toSet()); - - domainNamesToAdd.forEach(domainName -> { - String domainSettingsKey = constructAdminSettingsDomainKey(domainName); - if (adminSettingsService.findAdminSettingsByKey(tenantId, domainSettingsKey) != null) { - log.error("Current domain name [{}] already registered in the system!", domainName); - throw new IncorrectParameterException("Current domain name [" + domainName + "] already registered in the system!"); - } - }); - - domainNamesToAdd.forEach(domainName -> { - String domainSettingsKey = constructAdminSettingsDomainKey(domainName); - AdminSettings tenantAdminSettings = createTenantAdminSettings(tenantId, domainSettingsKey); - adminSettingsService.saveAdminSettings(tenantId, tenantAdminSettings); - }); - - domainNamesToDelete.forEach(domainName -> { - String domainSettingsKey = constructAdminSettingsDomainKey(domainName); - adminSettingsService.deleteAdminSettingsByKey(tenantId, domainSettingsKey); - }); - } - - private AdminSettings createTenantAdminSettings(TenantId tenantId, String clientRegistrationsKey) { - AdminSettings clientRegistrationParamsSettings = new AdminSettings(); - clientRegistrationParamsSettings.setKey(clientRegistrationsKey); - ObjectNode node = mapper.createObjectNode(); - node.put("entityType", EntityType.TENANT.name()); - node.put("entityId", tenantId.toString()); - clientRegistrationParamsSettings.setJsonValue(node); - return clientRegistrationParamsSettings; - } - - private AdminSettings createSystemAdminSettings() { - AdminSettings clientRegistrationParamsSettings = new AdminSettings(); - clientRegistrationParamsSettings.setKey(OAUTH2_CLIENT_REGISTRATIONS_PARAMS); - ObjectNode clientRegistrationsNode = mapper.createObjectNode(); - - clientRegistrationParamsSettings.setJsonValue(clientRegistrationsNode); - - return clientRegistrationParamsSettings; - } - - private void validateRegistrationIdUniqueness(OAuth2ClientsParams inputOAuth2ClientsParams, TenantId tenantId) { - List registrationIds = toClientRegistrationStream(inputOAuth2ClientsParams) - .map(OAuth2ClientRegistration::getRegistrationId) - .collect(Collectors.toList()); - - boolean regIdDuplicates = registrationIds.stream() - .anyMatch(registrationId -> Collections.frequency(registrationIds, registrationId) > 1); - if (regIdDuplicates) { - throw new DataValidationException("All registration IDs should be unique!"); - } - - getAllOAuth2ClientsParams().forEach((paramsTenantId, oAuth2ClientsParams) -> { - if (tenantId.equals(paramsTenantId)) return; - Set duplicatedRegistrationIds = toClientRegistrationStream(oAuth2ClientsParams) - .map(OAuth2ClientRegistration::getRegistrationId) - .filter(registrationIds::contains) - .collect(Collectors.toSet()); - if (!duplicatedRegistrationIds.isEmpty()) { - log.error("RegistrationIds [{}] are already registered in the system!", duplicatedRegistrationIds); - throw new IncorrectParameterException("RegistrationIds [" + duplicatedRegistrationIds + "] are already registered in the system!"); - } - }); - } - - private void validate(OAuth2ClientsParams oAuth2ClientsParams) { - validateRedirectUris(oAuth2ClientsParams); - validateDomainNames(oAuth2ClientsParams); - - toClientRegistrationStream(oAuth2ClientsParams) - .forEach(validator); - } - - private void validateDomainNames(OAuth2ClientsParams oAuth2ClientsParams) { - List domainNames = oAuth2ClientsParams.getClientsDomainsParams().stream() - .map(OAuth2ClientsDomainParams::getDomainName) - .collect(Collectors.toList()); - - domainNames.forEach(domainName -> { - if (StringUtils.isEmpty(domainName)) { - throw new DataValidationException("Domain name should be specified!"); + savedClientRegistration = clientRegistrationDao.save(clientRegistration.getTenantId(), clientRegistration); + } catch (Exception t) { + ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); + if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("oauth2_registration_id_unq_key")) { + throw new DataValidationException("Client registration with such registrationId already exists!"); + } else { + throw t; } - }); - - boolean duplicateDomainNames = domainNames.stream() - .anyMatch(domainName -> Collections.frequency(domainNames, domainName) > 1); - if (duplicateDomainNames) { - throw new DataValidationException("All domain names should be unique!"); } + return savedClientRegistration; } - private void validateRedirectUris(OAuth2ClientsParams oAuth2ClientsParams) { - oAuth2ClientsParams.getClientsDomainsParams().stream() - .forEach(oAuth2ClientsDomainParams -> { - if (StringUtils.isEmpty(oAuth2ClientsDomainParams.getRedirectUriTemplate())) { - throw new DataValidationException("Redirect uri template should be specified!"); - } - }); + @Override + public List findClientRegistrationsByTenantId(TenantId tenantId) { + log.trace("Executing findClientRegistrationsByTenantId [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + return clientRegistrationDao.findByTenantId(tenantId.getId()); } @Override - public OAuth2ClientsParams getSystemOAuth2ClientsParams() { - AdminSettings oauth2ClientsParamsSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAUTH2_CLIENT_REGISTRATIONS_PARAMS); - String json = null; - if (oauth2ClientsParamsSettings != null) { - json = oauth2ClientsParamsSettings.getJsonValue().get(SYSTEM_SETTINGS_OAUTH2_VALUE).asText(); - } - return constructOAuth2ClientsParams(json); + public OAuth2ClientRegistration findClientRegistrationByRegistrationId(String registrationId) { + log.trace("Executing findClientRegistrationByRegistrationId [{}]", registrationId); + validateString(registrationId, INCORRECT_REGISTRATION_ID + registrationId); + return clientRegistrationDao.findByRegistrationId(registrationId); } @Override - public OAuth2ClientsParams getTenantOAuth2ClientsParams(TenantId tenantId) { - ListenableFuture jsonFuture; - if (isOAuth2ClientRegistrationAllowed(tenantId)) { - jsonFuture = getOAuth2ClientsParamsAttribute(tenantId); - } else { - jsonFuture = Futures.immediateFuture(""); - } - try { - return Futures.transform(jsonFuture, this::constructOAuth2ClientsParams, MoreExecutors.directExecutor()).get(); - } catch (InterruptedException | ExecutionException e) { - log.error("Failed to read OAuth2 Clients Params from attributes!", e); - throw new RuntimeException("Failed to read OAuth2 Clients Params from attributes!", e); - } + public OAuth2ClientRegistration findClientRegistrationById(TenantId tenantId, OAuth2ClientRegistrationId id) { + log.trace("Executing findClientRegistrationById [{}]", id); + validateId(id, INCORRECT_CLIENT_REGISTRATION_ID + id); + return clientRegistrationDao.findById(tenantId, id.getId()); } - // TODO this is just for test, maybe there's a better way to test it without exporting to interface @Override - public Map getAllOAuth2ClientsParams() { - OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParams(); - ListenableFuture> jsonFuture = getAllOAuth2ClientsParamsAttribute(); - try { - return Futures.transform(jsonFuture, - clientsParamsByKvEntryKey -> { - Map tenantClientParams = clientsParamsByKvEntryKey != null ? - clientsParamsByKvEntryKey.entrySet().stream() - .collect(Collectors.toMap( - entry -> new TenantId(entry.getKey()), - entry -> constructOAuth2ClientsParams(entry.getValue()) - )) - : new HashMap<>(); - if (systemOAuth2ClientsParams.getClientsDomainsParams() != null) { - tenantClientParams.put(TenantId.SYS_TENANT_ID, systemOAuth2ClientsParams); - } - return tenantClientParams; - }, - MoreExecutors.directExecutor() - ).get(); - } catch (InterruptedException | ExecutionException e) { - log.error("Failed to read OAuth2 Clients Params from attributes!", e); - throw new RuntimeException("Failed to read OAuth2 Clients Params from attributes!", e); - } + public List findAllClientRegistrations() { + log.trace("Executing findAllClientRegistrations"); + return clientRegistrationDao.findAll(); } @Override - public void deleteTenantOAuth2ClientsParams(TenantId tenantId) { - OAuth2ClientsParams params = getTenantOAuth2ClientsParams(tenantId); - if (params == null || params.getClientsDomainsParams() == null) return; - params.getClientsDomainsParams().forEach(domainParams -> { - String settingsKey = constructAdminSettingsDomainKey(domainParams.getDomainName()); - adminSettingsService.deleteAdminSettingsByKey(tenantId, settingsKey); - }); - attributesService.removeAll(tenantId, tenantId, DataConstants.SERVER_SCOPE, Collections.singletonList(OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); + @Transactional + public void deleteClientRegistrationsByTenantId(TenantId tenantId) { + log.trace("Executing deleteClientRegistrationsByTenantId [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + clientRegistrationDao.removeByTenantId(tenantId.getId()); } @Override - public void deleteSystemOAuth2ClientsParams() { - adminSettingsService.deleteAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAuth2Utils.OAUTH2_CLIENT_REGISTRATIONS_PARAMS); + public void deleteClientRegistrationById(TenantId tenantId, OAuth2ClientRegistrationId id) { + log.trace("Executing deleteClientRegistrationById [{}]", id); + validateId(id, INCORRECT_CLIENT_REGISTRATION_ID + id); + clientRegistrationDao.removeById(tenantId, id.getId()); } @Override public boolean isOAuth2ClientRegistrationAllowed(TenantId tenantId) { + log.trace("Executing isOAuth2ClientRegistrationAllowed [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); Tenant tenant = tenantService.findTenantById(tenantId); if (tenant == null) return false; JsonNode allowOAuth2ConfigurationJsonNode = tenant.getAdditionalInfo() != null ? tenant.getAdditionalInfo().get(ALLOW_OAUTH2_CONFIGURATION) : null; @@ -385,185 +138,98 @@ public class OAuth2ServiceImpl implements OAuth2Service { } } - private ListenableFuture getOAuth2ClientsParamsAttribute(TenantId tenantId) { - ListenableFuture> attributeKvEntriesFuture; - try { - attributeKvEntriesFuture = attributesService.find(tenantId, tenantId, DataConstants.SERVER_SCOPE, - Collections.singletonList(OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); - } catch (Exception e) { - log.error("Unable to read OAuth2 Clients Params from attributes!", e); - throw new IncorrectParameterException("Unable to read OAuth2 Clients Params from attributes!"); - } - return Futures.transform(attributeKvEntriesFuture, attributeKvEntries -> { - if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) { - AttributeKvEntry kvEntry = attributeKvEntries.get(0); - return kvEntry.getValueAsString(); - } else { - return ""; - } - }, MoreExecutors.directExecutor()); - } - - // TODO maybe it's better to load all tenants and get attribute for each one - private ListenableFuture> getAllOAuth2ClientsParamsAttribute() { - ListenableFuture> entityAttributeKvEntriesFuture; - try { - entityAttributeKvEntriesFuture = attributesService.findAllByAttributeKey(OAUTH2_CLIENT_REGISTRATIONS_PARAMS); - } catch (Exception e) { - log.error("Unable to read OAuth2 Clients Params from attributes!", e); - throw new IncorrectParameterException("Unable to read OAuth2 Clients Params from attributes!"); - } - return Futures.transform(entityAttributeKvEntriesFuture, attributeKvEntries -> { - if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) { - return attributeKvEntries.stream() - .collect(Collectors.toMap(EntityAttributeKvEntry::getEntityId, EntityAttributeKvEntry::getValueAsString)); - } else { - return Collections.emptyMap(); - } - }, MoreExecutors.directExecutor()); - } - - private OAuth2ClientsDomainParams getMergedOAuth2ClientsParams(String domainName) { - OAuth2ClientsDomainParams result = OAuth2ClientsDomainParams.builder() - .domainName(domainName) - .clientRegistrations(new ArrayList<>()) - .build(); - - OAuth2ClientsParams systemOAuth2ClientsParams = getSystemOAuth2ClientsParams(); - OAuth2ClientsDomainParams systemOAuth2ClientsDomainParams = systemOAuth2ClientsParams != null && systemOAuth2ClientsParams.getClientsDomainsParams() != null ? - systemOAuth2ClientsParams.getClientsDomainsParams().stream() - .filter(oAuth2ClientsDomainParams -> domainName.equals(oAuth2ClientsDomainParams.getDomainName())) - .findFirst() - .orElse(null) - : null; - - result = mergeDomainParams(result, systemOAuth2ClientsDomainParams); - - AdminSettings oauth2ClientsSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, constructAdminSettingsDomainKey(domainName)); - if (oauth2ClientsSettings != null) { - String strEntityType = oauth2ClientsSettings.getJsonValue().get("entityType").asText(); - String strEntityId = oauth2ClientsSettings.getJsonValue().get("entityId").asText(); - EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId); - if (!entityId.getEntityType().equals(EntityType.TENANT)) { - log.error("Only tenant can configure OAuth2 for certain domain!"); - throw new IllegalStateException("Only tenant can configure OAuth2 for certain domain!"); - } - TenantId tenantId = (TenantId) entityId; - OAuth2ClientsParams tenantOAuth2ClientsParams = getTenantOAuth2ClientsParams(tenantId); - OAuth2ClientsDomainParams tenantDomainsParams = tenantOAuth2ClientsParams != null && tenantOAuth2ClientsParams.getClientsDomainsParams() != null ? - tenantOAuth2ClientsParams.getClientsDomainsParams().stream().findFirst().orElse(null) : null; - result = mergeDomainParams(result, tenantDomainsParams); - } - return result; - } - - private OAuth2ClientsDomainParams mergeDomainParams(OAuth2ClientsDomainParams sourceParams, OAuth2ClientsDomainParams newParams) { - if (newParams == null) return sourceParams; - - OAuth2ClientsDomainParams.OAuth2ClientsDomainParamsBuilder mergedParamsBuilder = sourceParams.toBuilder(); - - if (newParams.getClientRegistrations() != null) { - List mergedClientRegistrations = sourceParams.getClientRegistrations() != null ? - sourceParams.getClientRegistrations() : new ArrayList<>(); - mergedClientRegistrations.addAll(newParams.getClientRegistrations()); - mergedParamsBuilder.clientRegistrations(mergedClientRegistrations); - } - - return mergedParamsBuilder.build(); - } + private DataValidator clientRegistrationValidator = + new DataValidator() { - private OAuth2ClientsParams constructOAuth2ClientsParams(String json) { - OAuth2ClientsParams result = null; - if (!StringUtils.isEmpty(json)) { - try { - result = mapper.readValue(json, OAuth2ClientsParams.class); - } catch (IOException e) { - log.error("Unable to read OAuth2 Clients Params from JSON!", e); - throw new IncorrectParameterException("Unable to read OAuth2 Clients Params from JSON!"); - } - } - if (result == null) { - result = new OAuth2ClientsParams(); - } - return result; - } + @Override + protected void validateCreate(TenantId tenantId, OAuth2ClientRegistration clientRegistration) { + } - private String toJson(OAuth2ClientsParams oAuth2ClientsParams) { - String json; - try { - json = mapper.writeValueAsString(oAuth2ClientsParams); - } catch (JsonProcessingException e) { - log.error("Unable to convert OAuth2 Client Registration Params to JSON!", e); - throw new IncorrectParameterException("Unable to convert OAuth2 Client Registration Params to JSON!"); - } - return json; - } + @Override + protected void validateUpdate(TenantId tenantId, OAuth2ClientRegistration clientRegistration) { + } - private final Consumer validator = clientRegistration -> { - if (StringUtils.isEmpty(clientRegistration.getRegistrationId())) { - throw new DataValidationException("Registration ID should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getClientId())) { - throw new DataValidationException("Client ID should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getClientSecret())) { - throw new DataValidationException("Client secret should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getAuthorizationUri())) { - throw new DataValidationException("Authorization uri should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getAccessTokenUri())) { - throw new DataValidationException("Token uri should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getScope())) { - throw new DataValidationException("Scope should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getUserInfoUri())) { - throw new DataValidationException("User info uri should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getUserNameAttributeName())) { - throw new DataValidationException("User name attribute name should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getJwkSetUri())) { - throw new DataValidationException("Jwk set uri should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getClientAuthenticationMethod())) { - throw new DataValidationException("Client authentication method should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getLoginButtonLabel())) { - throw new DataValidationException("Login button label should be specified!"); - } - OAuth2MapperConfig mapperConfig = clientRegistration.getMapperConfig(); - if (mapperConfig == null) { - throw new DataValidationException("Mapper config should be specified!"); - } - if (mapperConfig.getType() == null) { - throw new DataValidationException("Mapper config type should be specified!"); - } - if (mapperConfig.getType() == MapperType.BASIC) { - OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic(); - if (basicConfig == null) { - throw new DataValidationException("Basic config should be specified!"); - } - if (StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) { - throw new DataValidationException("Email attribute key should be specified!"); - } - if (basicConfig.getTenantNameStrategy() == null) { - throw new DataValidationException("Tenant name strategy should be specified!"); - } - if (basicConfig.getTenantNameStrategy() == TenantNameStrategyType.CUSTOM - && StringUtils.isEmpty(basicConfig.getTenantNamePattern())) { - throw new DataValidationException("Tenant name pattern should be specified!"); - } - } - if (mapperConfig.getType() == MapperType.CUSTOM) { - OAuth2CustomMapperConfig customConfig = mapperConfig.getCustom(); - if (customConfig == null) { - throw new DataValidationException("Custom config should be specified!"); - } - if (StringUtils.isEmpty(customConfig.getUrl())) { - throw new DataValidationException("Custom mapper URL should be specified!"); - } - } - }; + @Override + protected void validateDataImpl(TenantId tenantId, OAuth2ClientRegistration clientRegistration) { + if (StringUtils.isEmpty(clientRegistration.getRegistrationId())) { + throw new DataValidationException("Registration ID should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getDomainName())) { + throw new DataValidationException("Domain name should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getRedirectUriTemplate())) { + throw new DataValidationException("Redirect URI template should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getClientId())) { + throw new DataValidationException("Client ID should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getClientSecret())) { + throw new DataValidationException("Client secret should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getAuthorizationUri())) { + throw new DataValidationException("Authorization uri should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getAccessTokenUri())) { + throw new DataValidationException("Token uri should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getScope())) { + throw new DataValidationException("Scope should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getUserInfoUri())) { + throw new DataValidationException("User info uri should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getUserNameAttributeName())) { + throw new DataValidationException("User name attribute name should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getJwkSetUri())) { + throw new DataValidationException("Jwk set uri should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getClientAuthenticationMethod())) { + throw new DataValidationException("Client authentication method should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getLoginButtonLabel())) { + throw new DataValidationException("Login button label should be specified!"); + } + OAuth2MapperConfig mapperConfig = clientRegistration.getMapperConfig(); + if (mapperConfig == null) { + throw new DataValidationException("Mapper config should be specified!"); + } + if (mapperConfig.getType() == null) { + throw new DataValidationException("Mapper config type should be specified!"); + } + if (mapperConfig.getType() == MapperType.BASIC) { + OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic(); + if (basicConfig == null) { + throw new DataValidationException("Basic config should be specified!"); + } + if (StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) { + throw new DataValidationException("Email attribute key should be specified!"); + } + if (basicConfig.getTenantNameStrategy() == null) { + throw new DataValidationException("Tenant name strategy should be specified!"); + } + if (basicConfig.getTenantNameStrategy() == TenantNameStrategyType.CUSTOM + && StringUtils.isEmpty(basicConfig.getTenantNamePattern())) { + throw new DataValidationException("Tenant name pattern should be specified!"); + } + } + if (mapperConfig.getType() == MapperType.CUSTOM) { + OAuth2CustomMapperConfig customConfig = mapperConfig.getCustom(); + if (customConfig == null) { + throw new DataValidationException("Custom config should be specified!"); + } + if (StringUtils.isEmpty(customConfig.getUrl())) { + throw new DataValidationException("Custom mapper URL should be specified!"); + } + } + if (clientRegistration.getTenantId() == null) { + throw new DataValidationException("Client registration should be assigned to tenant!"); + } else if (!TenantId.SYS_TENANT_ID.equals(clientRegistration.getTenantId())) { + Tenant tenant = tenantService.findTenantById(clientRegistration.getTenantId()); + if (tenant == null) { + throw new DataValidationException("Client registration is referencing to non-existent tenant!"); + } + } + } + }; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java index 003f87cc73..7ccad60bd7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java @@ -23,22 +23,9 @@ import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; import java.util.stream.Stream; public class OAuth2Utils { - public static final String OAUTH2_CLIENT_REGISTRATIONS_PARAMS = "oauth2ClientRegistrationsParams"; - public static final String OAUTH2_CLIENT_REGISTRATIONS_DOMAIN_NAME_PREFIX = "oauth2ClientRegistrationsDomainNamePrefix"; public static final String ALLOW_OAUTH2_CONFIGURATION = "allowOAuth2Configuration"; - public static final String SYSTEM_SETTINGS_OAUTH2_VALUE = "value"; public static final String OAUTH2_AUTHORIZATION_PATH_TEMPLATE = "/oauth2/authorization/%s"; - public static String constructAdminSettingsDomainKey(String domainName) { - String clientRegistrationsKey; - if (StringUtils.isEmpty(domainName)) { - clientRegistrationsKey = OAUTH2_CLIENT_REGISTRATIONS_PARAMS; - } else { - clientRegistrationsKey = OAUTH2_CLIENT_REGISTRATIONS_DOMAIN_NAME_PREFIX + "_" + domainName; - } - return clientRegistrationsKey; - } - public static OAuth2ClientInfo toClientInfo(OAuth2ClientRegistration clientRegistration) { OAuth2ClientInfo client = new OAuth2ClientInfo(); client.setName(clientRegistration.getLoginButtonLabel()); @@ -46,9 +33,4 @@ public class OAuth2Utils { client.setIcon(clientRegistration.getLoginButtonIcon()); return client; } - - public static Stream toClientRegistrationStream(OAuth2ClientsParams oAuth2ClientsParams) { - return oAuth2ClientsParams.getClientsDomainsParams().stream() - .flatMap(oAuth2ClientsDomainParams -> oAuth2ClientsDomainParams.getClientRegistrations().stream()); - } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java new file mode 100644 index 0000000000..d40fb060dc --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java @@ -0,0 +1,86 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.oauth2; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationEntity; +import org.thingsboard.server.dao.oauth2.OAuth2ClientRegistrationDao; +import org.thingsboard.server.dao.sql.JpaAbstractDao; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +public class JpaOAuth2ClientRegistrationDao extends JpaAbstractDao implements OAuth2ClientRegistrationDao { + private final OAuth2ClientRegistrationRepository repository; + + @Override + protected Class getEntityClass() { + return OAuth2ClientRegistrationEntity.class; + } + + @Override + protected CrudRepository getCrudRepository() { + return repository; + } + + @Override + public OAuth2ClientRegistration findByRegistrationId(String registrationId) { + Optional entity = repository.findByRegistrationId(registrationId); + return DaoUtil.getData(entity); + } + + @Override + public List findAll() { + Iterable entities = repository.findAll(); + List result = new ArrayList<>(); + entities.forEach(entity -> { + result.add(DaoUtil.getData(entity)); + }); + return result; + } + + @Override + public List findByTenantId(UUID tenantId) { + List entities = repository.findAllByTenantId(tenantId); + return entities.stream().map(DaoUtil::getData).collect(Collectors.toList()); + } + + @Override + public List findByDomainName(String domainName) { + List entities = repository.findAllByDomainName(domainName); + return entities.stream().map(DaoUtil::getData).collect(Collectors.toList()); + } + + @Override + public boolean removeByRegistrationId(String registrationId) { + repository.deleteByRegistrationId(registrationId); + return !repository.existsByRegistrationId(registrationId); + } + + @Override + public int removeByTenantId(UUID tenantId) { + return repository.deleteByTenantId(tenantId); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java new file mode 100644 index 0000000000..17984ee057 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.oauth2; + +import org.springframework.data.repository.CrudRepository; +import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationEntity; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface OAuth2ClientRegistrationRepository extends CrudRepository { + Optional findByRegistrationId(String registrationId); + + List findAllByTenantId(UUID tenantId); + + List findAllByDomainName(String domainName); + + int deleteByRegistrationId(String registrationId); + + int deleteByTenantId(UUID tenantId); + + boolean existsByRegistrationId(String registrationId); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index 4708694cee..df0dfb666a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -105,7 +105,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe public void deleteTenant(TenantId tenantId) { log.trace("Executing deleteTenant [{}]", tenantId); Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - oAuth2Service.deleteTenantOAuth2ClientsParams(tenantId); + oAuth2Service.deleteClientRegistrationsByTenantId(tenantId); customerService.deleteCustomersByTenantId(tenantId); widgetsBundleService.deleteWidgetsBundlesByTenantId(tenantId); dashboardService.deleteDashboardsByTenantId(tenantId); diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index 664925db76..bb38f27aef 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -289,3 +289,40 @@ CREATE TABLE IF NOT EXISTS ts_kv_dictionary ( key_id int GENERATED BY DEFAULT AS IDENTITY(start with 0 increment by 1) UNIQUE, CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) ); + + +CREATE TABLE IF NOT EXISTS oauth2_client_registration ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + created_time bigint NOT NULL, + additional_info varchar, + tenant_id uuid, + registration_id varchar(255), + domain_name varchar(255), + client_id varchar(255), + client_secret varchar(255), + authorization_uri varchar(255), + token_uri varchar(255), + redirect_uri_template varchar(255), + scope varchar(255), + user_info_uri varchar(255), + user_name_attribute_name varchar(255), + jwk_set_uri varchar(255), + client_authentication_method varchar(255), + login_button_label varchar(255), + login_button_icon varchar(255), + allow_user_creation boolean, + activate_user boolean, + type varchar(31), + basic_email_attribute_key varchar(31), + basic_first_name_attribute_key varchar(31), + basic_last_name_attribute_key varchar(31), + basic_tenant_name_strategy varchar(31), + basic_tenant_name_pattern varchar(255), + basic_customer_name_pattern varchar(255), + basic_default_dashboard_name varchar(255), + basic_always_full_screen boolean, + custom_url varchar(255), + custom_username varchar(255), + custom_password varchar(255), + CONSTRAINT oauth2_registration_id_unq_key UNIQUE (registration_id) +); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 50924bbbd4..441b676647 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -306,6 +306,42 @@ CREATE TABLE IF NOT EXISTS ts_kv_dictionary CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) ); +CREATE TABLE IF NOT EXISTS oauth2_client_registration ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + created_time bigint NOT NULL, + additional_info varchar, + tenant_id uuid, + registration_id varchar(255), + domain_name varchar(255), + client_id varchar(255), + client_secret varchar(255), + authorization_uri varchar(255), + token_uri varchar(255), + redirect_uri_template varchar(255), + scope varchar(255), + user_info_uri varchar(255), + user_name_attribute_name varchar(255), + jwk_set_uri varchar(255), + client_authentication_method varchar(255), + login_button_label varchar(255), + login_button_icon varchar(255), + allow_user_creation boolean, + activate_user boolean, + type varchar(31), + basic_email_attribute_key varchar(31), + basic_first_name_attribute_key varchar(31), + basic_last_name_attribute_key varchar(31), + basic_tenant_name_strategy varchar(31), + basic_tenant_name_pattern varchar(255), + basic_customer_name_pattern varchar(255), + basic_default_dashboard_name varchar(255), + basic_always_full_screen boolean, + custom_url varchar(255), + custom_username varchar(255), + custom_password varchar(255), + CONSTRAINT oauth2_registration_id_unq_key UNIQUE (registration_id) +); + CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint) LANGUAGE plpgsql AS $$ diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java index ab572e7e53..a0e430fdcd 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -23,6 +23,7 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.*; import org.thingsboard.server.dao.attributes.AttributesService; @@ -30,6 +31,7 @@ import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.dao.oauth2.OAuth2Utils; +import javax.transaction.Transactional; import java.io.IOException; import java.util.*; import java.util.stream.Collectors; @@ -55,24 +57,15 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { Assert.assertNotNull(savedTenant); tenantId = savedTenant.getId(); - Assert.assertNull(oAuth2Service.getSystemOAuth2ClientsParams().getClientsDomainsParams()); - Assert.assertNull(oAuth2Service.getTenantOAuth2ClientsParams(tenantId).getClientsDomainsParams()); - - Assert.assertTrue(attributesService.findAll(tenantId, tenantId, DataConstants.SERVER_SCOPE).get().isEmpty()); - Assert.assertNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAuth2Utils.OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); + Assert.assertTrue(oAuth2Service.findAllClientRegistrations().isEmpty()); } @After public void after() throws Exception { - clearSysAdmin(); - tenantService.deleteTenant(tenantId); + oAuth2Service.deleteClientRegistrationsByTenantId(TenantId.SYS_TENANT_ID); - Assert.assertNull(oAuth2Service.getSystemOAuth2ClientsParams().getClientsDomainsParams()); - Assert.assertNull(oAuth2Service.getTenantOAuth2ClientsParams(tenantId).getClientsDomainsParams()); - - Assert.assertTrue(attributesService.findAll(tenantId, tenantId, DataConstants.SERVER_SCOPE).get().isEmpty()); - Assert.assertNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAuth2Utils.OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); + Assert.assertTrue(oAuth2Service.findAllClientRegistrations().isEmpty()); } @Test @@ -93,226 +86,107 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { Assert.assertTrue(oAuth2Service.isOAuth2ClientRegistrationAllowed(tenantId)); } - @Test - public void testSaveSystemOAuth2() throws IOException { - updateTenantAllowOAuth2Setting(true); - Assert.assertTrue(oAuth2Service.isOAuth2ClientRegistrationAllowed(tenantId)); - } - - @Test(expected = DataValidationException.class) - public void testSaveSystemParamsWithDuplicateDomains() { - oAuth2Service.saveSystemOAuth2ClientsParams(clientsParamsWithDuplicateDomains()); - } - - @Test(expected = DataValidationException.class) - public void testSaveSystemParamsWithDuplicateRegistrationIds() { - oAuth2Service.saveSystemOAuth2ClientsParams(clientsParamsWithDuplicateRegistrationIds()); - } - @Test(expected = DataValidationException.class) - public void testSaveTenantParamsWithDuplicateRegistrationIds() { - oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, clientsParamsWithDuplicateRegistrationIds()); - } - - @Test - public void testSaveSystemParams() { - OAuth2ClientsParams clientsParams = validClientsParams(); - OAuth2ClientsParams savedClientParams = oAuth2Service.saveSystemOAuth2ClientsParams(clientsParams); - - Assert.assertNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAuth2Utils.OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); - Assert.assertEquals(clientsParams, savedClientParams); - } - - @Test - public void testSaveSystemParamsWithMultipleDomains() { - OAuth2ClientsParams clientsParams = validClientsParamsWithThreeDomains(); - OAuth2ClientsParams savedClientParams = oAuth2Service.saveSystemOAuth2ClientsParams(clientsParams); + public void testSaveDuplicateRegistrationId() { + OAuth2ClientRegistration first = validClientRegistration("duplicated_reg_id", TenantId.SYS_TENANT_ID); + OAuth2ClientRegistration second = validClientRegistration("duplicated_reg_id", tenantId); - Assert.assertNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAuth2Utils.OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); - Assert.assertEquals(clientsParams, savedClientParams); + oAuth2Service.saveClientRegistration(first); + oAuth2Service.saveClientRegistration(second); } @Test - public void testFindSystemParams() { - OAuth2ClientsParams clientsParams = validClientsParams(); - oAuth2Service.saveSystemOAuth2ClientsParams(clientsParams); - - Assert.assertNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAuth2Utils.OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); - - OAuth2ClientsParams foundClientParams = oAuth2Service.getSystemOAuth2ClientsParams(); - Assert.assertNotNull(foundClientParams); - Assert.assertEquals(clientsParams, foundClientParams); - } - - @Test - public void testSaveTenantParams() { - OAuth2ClientsParams clientsParams = validClientsParams(); - OAuth2ClientsDomainParams domainParams = clientsParams.getClientsDomainsParams().get(0); - String domainKey = OAuth2Utils.constructAdminSettingsDomainKey(domainParams.getDomainName()); - - Assert.assertNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); - - OAuth2ClientsParams savedClientParams = oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, clientsParams); - - Assert.assertNotNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); - Assert.assertNotNull(savedClientParams); - - OAuth2ClientsDomainParams savedDomainParams = savedClientParams.getClientsDomainsParams().get(0); - Assert.assertEquals(domainParams.getDomainName(), savedDomainParams.getDomainName()); - Assert.assertEquals(domainParams.getClientRegistrations(), savedDomainParams.getClientRegistrations()); + public void testCreateNewSystemParams() { + OAuth2ClientRegistration clientRegistration = validClientRegistration(UUID.randomUUID().toString(), TenantId.SYS_TENANT_ID); + OAuth2ClientRegistration savedClientRegistration = oAuth2Service.saveClientRegistration(clientRegistration); + + Assert.assertNotNull(savedClientRegistration); + Assert.assertNotNull(savedClientRegistration.getId()); + clientRegistration.setId(savedClientRegistration.getId()); + clientRegistration.setCreatedTime(savedClientRegistration.getCreatedTime()); + Assert.assertEquals(clientRegistration, savedClientRegistration); } @Test - public void testSaveTenantMultipleParams() { - OAuth2ClientsParams clientsParams = validClientsParamsWithThreeDomains(); - - clientsParams.getClientsDomainsParams().forEach(oAuth2ClientsDomainParams -> { - String domainName = oAuth2ClientsDomainParams.getDomainName(); - String domainKey = OAuth2Utils.constructAdminSettingsDomainKey(domainName); - Assert.assertNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); - }); - - OAuth2ClientsParams savedClientParams = oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, clientsParams); - Assert.assertNotNull(savedClientParams); - - clientsParams.getClientsDomainsParams().forEach(oAuth2ClientsDomainParams -> { - String domainName = oAuth2ClientsDomainParams.getDomainName(); - String domainKey = OAuth2Utils.constructAdminSettingsDomainKey(domainName); - Assert.assertNotNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); - }); - - Assert.assertEquals(clientsParams, savedClientParams); + public void testFindSystemParamsByTenant() { + OAuth2ClientRegistration clientRegistration = validClientRegistration(UUID.randomUUID().toString(), TenantId.SYS_TENANT_ID); + oAuth2Service.saveClientRegistration(clientRegistration); + + List clientRegistrationsByTenantId = oAuth2Service.findClientRegistrationsByTenantId(TenantId.SYS_TENANT_ID); + Assert.assertEquals(1, clientRegistrationsByTenantId.size()); + Assert.assertEquals(1, oAuth2Service.findAllClientRegistrations().size()); + OAuth2ClientRegistration foundClientRegistration = clientRegistrationsByTenantId.get(0); + Assert.assertNotNull(foundClientRegistration); + clientRegistration.setId(foundClientRegistration.getId()); + clientRegistration.setCreatedTime(foundClientRegistration.getCreatedTime()); + Assert.assertEquals(clientRegistration, foundClientRegistration); } @Test - public void testRewriteSameDomainTenantParams() { - OAuth2ClientsParams clientsParams = validClientsParamsWithThreeDomains(); - oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, clientsParams); - - List clientsDomainsParams = clientsParams.getClientsDomainsParams(); - OAuth2ClientsParams updatedClientsParams = validClientsParamsWithThreeDomains(); - String sameDomainName = clientsDomainsParams.get(0).getDomainName(); - updatedClientsParams.getClientsDomainsParams().get(0).setDomainName(sameDomainName); - OAuth2ClientsParams rewrittenClientParams = oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, updatedClientsParams); - Assert.assertEquals(updatedClientsParams, rewrittenClientParams); - - clientsParams.getClientsDomainsParams().forEach(oAuth2ClientsDomainParams -> { - String domainName = oAuth2ClientsDomainParams.getDomainName(); - String domainKey = OAuth2Utils.constructAdminSettingsDomainKey(domainName); - if (domainName.equals(sameDomainName)) { - Assert.assertNotNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); - } else { - Assert.assertNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); - } - }); - updatedClientsParams.getClientsDomainsParams().forEach(oAuth2ClientsDomainParams -> { - String domainName = oAuth2ClientsDomainParams.getDomainName(); - String domainKey = OAuth2Utils.constructAdminSettingsDomainKey(domainName); - Assert.assertNotNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); - }); + public void testCreateNewTenantParams() { + OAuth2ClientRegistration clientRegistration = validClientRegistration(UUID.randomUUID().toString(), tenantId); + OAuth2ClientRegistration savedClientRegistration = oAuth2Service.saveClientRegistration(clientRegistration); + + Assert.assertNotNull(savedClientRegistration); + Assert.assertNotNull(savedClientRegistration.getId()); + clientRegistration.setId(savedClientRegistration.getId()); + clientRegistration.setCreatedTime(savedClientRegistration.getCreatedTime()); + Assert.assertEquals(clientRegistration, savedClientRegistration); } @Test - public void testAddDeleteTenantDomainParams() { - OAuth2ClientsParams clientsParams = validClientsParamsWithThreeDomains(); - oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, clientsParams); - - List clientsDomainsParams = clientsParams.getClientsDomainsParams(); - OAuth2ClientsParams updatedClientsParams = validClientsParamsWithThreeDomains(); - for (int i = 0; i < updatedClientsParams.getClientsDomainsParams().size(); i++) { - String domainName = clientsDomainsParams.get(i).getDomainName(); - updatedClientsParams.getClientsDomainsParams().get(i).setDomainName(domainName); - } - OAuth2ClientsParams rewrittenClientParams = oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, updatedClientsParams); - Assert.assertEquals(updatedClientsParams, rewrittenClientParams); - - clientsParams.getClientsDomainsParams().forEach(oAuth2ClientsDomainParams -> { - String domainName = oAuth2ClientsDomainParams.getDomainName(); - String domainKey = OAuth2Utils.constructAdminSettingsDomainKey(domainName); - Assert.assertNotNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); - }); + public void testFindTenantParams() { + OAuth2ClientRegistration clientRegistration = validClientRegistration(UUID.randomUUID().toString(), tenantId); + oAuth2Service.saveClientRegistration(clientRegistration); + + List clientRegistrationsByTenantId = oAuth2Service.findClientRegistrationsByTenantId(tenantId); + Assert.assertEquals(1, clientRegistrationsByTenantId.size()); + Assert.assertEquals(1, oAuth2Service.findAllClientRegistrations().size()); + OAuth2ClientRegistration foundClientRegistration = clientRegistrationsByTenantId.get(0); + Assert.assertNotNull(foundClientRegistration); + clientRegistration.setId(foundClientRegistration.getId()); + clientRegistration.setCreatedTime(foundClientRegistration.getCreatedTime()); + Assert.assertEquals(clientRegistration, foundClientRegistration); } @Test - public void testFindTenantParams() { - OAuth2ClientsParams clientsParams = validClientsParams(); - OAuth2ClientsDomainParams domainParams = clientsParams.getClientsDomainsParams().get(0); - String domainKey = OAuth2Utils.constructAdminSettingsDomainKey(domainParams.getDomainName()); + public void testGetClientRegistrationWithTenant() { + OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(UUID.randomUUID().toString(), tenantId); + OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(UUID.randomUUID().toString(), TenantId.SYS_TENANT_ID); - Assert.assertNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); + OAuth2ClientRegistration savedTenantClientRegistration = oAuth2Service.saveClientRegistration(tenantClientRegistration); + OAuth2ClientRegistration savedSysAdminClientRegistration = oAuth2Service.saveClientRegistration(sysAdminClientRegistration); - OAuth2ClientsParams savedClientsParams = oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, clientsParams); + Assert.assertEquals(2, oAuth2Service.findAllClientRegistrations().size()); - Assert.assertNotNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); + Assert.assertEquals(savedTenantClientRegistration, oAuth2Service.findClientRegistrationsByTenantId(tenantId).get(0)); + Assert.assertEquals(savedSysAdminClientRegistration, oAuth2Service.findClientRegistrationsByTenantId(TenantId.SYS_TENANT_ID).get(0)); - OAuth2ClientsParams foundClientsParams = oAuth2Service.getTenantOAuth2ClientsParams(tenantId); - Assert.assertEquals(savedClientsParams, foundClientsParams); - } + Assert.assertEquals(savedTenantClientRegistration, + oAuth2Service.findClientRegistrationByRegistrationId(savedTenantClientRegistration.getRegistrationId())); + Assert.assertEquals(savedSysAdminClientRegistration, + oAuth2Service.findClientRegistrationByRegistrationId(savedSysAdminClientRegistration.getRegistrationId())); - @Test - public void testGetClientRegistrationWithTenant() { - OAuth2ClientsParams tenantClientsParams = validClientsParams(); - OAuth2ClientsParams sysAdminClientsParams = validClientsParams(); - - oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, tenantClientsParams); - oAuth2Service.saveSystemOAuth2ClientsParams(sysAdminClientsParams); - - OAuth2Utils.toClientRegistrationStream(tenantClientsParams) - .forEach(clientRegistration -> { - Pair pair = oAuth2Service.getClientRegistrationWithTenant(clientRegistration.getRegistrationId()); - Assert.assertEquals(tenantId, pair.getKey()); - Assert.assertEquals(clientRegistration.getRegistrationId(), pair.getValue().getRegistrationId()); - }); - OAuth2Utils.toClientRegistrationStream(sysAdminClientsParams) - .forEach(clientRegistration -> { - Pair pair = oAuth2Service.getClientRegistrationWithTenant(clientRegistration.getRegistrationId()); - Assert.assertNotNull(pair); - Assert.assertEquals(TenantId.SYS_TENANT_ID, pair.getKey()); - Assert.assertEquals(clientRegistration.getRegistrationId(), pair.getValue().getRegistrationId()); - }); - } - - @Test - public void testGetExtendedClientRegistration() { - OAuth2ClientsParams tenantClientsParams = validClientsParams(); - OAuth2ClientsParams sysAdminClientsParams = validClientsParams(); - - oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, tenantClientsParams); - oAuth2Service.saveSystemOAuth2ClientsParams(sysAdminClientsParams); - - Stream.concat( - OAuth2Utils.toClientRegistrationStream(tenantClientsParams), - OAuth2Utils.toClientRegistrationStream(sysAdminClientsParams) - ) - .forEach(clientRegistration -> { - ExtendedOAuth2ClientRegistration foundExtendedClientRegistration = oAuth2Service.getExtendedClientRegistration(clientRegistration.getRegistrationId()); - Assert.assertNotNull(foundExtendedClientRegistration); - Assert.assertEquals(clientRegistration, foundExtendedClientRegistration.getClientRegistration()); - }); + Assert.assertEquals(savedTenantClientRegistration, + oAuth2Service.findClientRegistrationById(tenantId, savedTenantClientRegistration.getId())); + Assert.assertEquals(savedSysAdminClientRegistration, + oAuth2Service.findClientRegistrationById(TenantId.SYS_TENANT_ID, savedSysAdminClientRegistration.getId())); } @Test public void testGetOAuth2Clients() { - OAuth2ClientsParams tenantClientsParams = validClientsParams(); - OAuth2ClientsParams sysAdminClientsParams = validClientsParams(); - - OAuth2ClientsDomainParams tenantDomainParams = tenantClientsParams.getClientsDomainsParams().get(0); - OAuth2ClientsDomainParams systemDomainParams = sysAdminClientsParams.getClientsDomainsParams().get(0); - systemDomainParams.setDomainName(tenantDomainParams.getDomainName()); + String testDomainName = "test_domain"; + OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(testDomainName, UUID.randomUUID().toString(), tenantId); + OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(testDomainName, UUID.randomUUID().toString(), TenantId.SYS_TENANT_ID); - oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, tenantClientsParams); - oAuth2Service.saveSystemOAuth2ClientsParams(sysAdminClientsParams); + oAuth2Service.saveClientRegistration(tenantClientRegistration); + oAuth2Service.saveClientRegistration(sysAdminClientRegistration); - List oAuth2Clients = oAuth2Service.getOAuth2Clients(tenantDomainParams.getDomainName()); - - Set actualLabels = Stream.concat( - tenantDomainParams.getClientRegistrations().stream() - .map(OAuth2ClientRegistration::getLoginButtonLabel), - systemDomainParams.getClientRegistrations().stream() - .map(OAuth2ClientRegistration::getLoginButtonLabel) - ).collect(Collectors.toSet()); + List oAuth2Clients = oAuth2Service.getOAuth2Clients(testDomainName); + Set actualLabels = new HashSet<>(Arrays.asList(tenantClientRegistration.getLoginButtonLabel(), + sysAdminClientRegistration.getLoginButtonLabel())); Set foundLabels = oAuth2Clients.stream().map(OAuth2ClientInfo::getName).collect(Collectors.toSet()); Assert.assertEquals(actualLabels, foundLabels); @@ -320,67 +194,39 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testGetEmptyOAuth2Clients() { + String testDomainName = "test_domain"; + OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(testDomainName, UUID.randomUUID().toString(), tenantId); + OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(testDomainName, UUID.randomUUID().toString(), TenantId.SYS_TENANT_ID); + oAuth2Service.saveClientRegistration(tenantClientRegistration); + oAuth2Service.saveClientRegistration(sysAdminClientRegistration); List oAuth2Clients = oAuth2Service.getOAuth2Clients("random-domain"); Assert.assertTrue(oAuth2Clients.isEmpty()); } @Test - public void testGetAllOAuth2ClientsParams() { - OAuth2ClientsParams tenantClientsParams = validClientsParams(); - OAuth2ClientsParams sysAdminClientsParams = validClientsParams(); - - Map emptyParams = oAuth2Service.getAllOAuth2ClientsParams(); - Assert.assertTrue(emptyParams.isEmpty()); - - OAuth2ClientsParams savedTenantParams = oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, tenantClientsParams); - OAuth2ClientsParams savedSystemParams = oAuth2Service.saveSystemOAuth2ClientsParams(sysAdminClientsParams); - - Map clientsParams = oAuth2Service.getAllOAuth2ClientsParams(); - - OAuth2ClientsParams foundTenantParams = clientsParams.get(tenantId); - Assert.assertEquals(savedTenantParams, foundTenantParams); - - OAuth2ClientsParams foundSystemParams = clientsParams.get(TenantId.SYS_TENANT_ID); - Assert.assertEquals(savedSystemParams, foundSystemParams); - } - - @Test - public void testDeleteSystemOAuth2ClientsParams() { - OAuth2ClientsParams sysAdminClientsParams = validClientsParams(); - - Assert.assertNull(oAuth2Service.getSystemOAuth2ClientsParams().getClientsDomainsParams()); - - oAuth2Service.saveSystemOAuth2ClientsParams(sysAdminClientsParams); - - Assert.assertNotNull(oAuth2Service.getSystemOAuth2ClientsParams().getClientsDomainsParams()); - - oAuth2Service.deleteSystemOAuth2ClientsParams(); - Assert.assertNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAuth2Utils.OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); + public void testDeleteOAuth2ClientRegistration() { + OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(UUID.randomUUID().toString(), tenantId); + OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(UUID.randomUUID().toString(), TenantId.SYS_TENANT_ID); + OAuth2ClientRegistration savedTenantRegistration = oAuth2Service.saveClientRegistration(tenantClientRegistration); + OAuth2ClientRegistration savedSysAdminRegistration = oAuth2Service.saveClientRegistration(sysAdminClientRegistration); + + oAuth2Service.deleteClientRegistrationById(tenantId, savedTenantRegistration.getId()); + List foundRegistrations = oAuth2Service.findAllClientRegistrations(); + Assert.assertEquals(1, foundRegistrations.size()); + Assert.assertEquals(savedSysAdminRegistration, foundRegistrations.get(0)); } @Test - public void testDeleteTenantOAuth2ClientsParams() { - OAuth2ClientsParams tenantClientsParams = validClientsParams(); - - Assert.assertNull(oAuth2Service.getTenantOAuth2ClientsParams(tenantId).getClientsDomainsParams()); - - oAuth2Service.saveTenantOAuth2ClientsParams(tenantId, tenantClientsParams); - - Assert.assertNotNull(oAuth2Service.getTenantOAuth2ClientsParams(tenantId).getClientsDomainsParams()); - - oAuth2Service.deleteTenantOAuth2ClientsParams(tenantId); - Assert.assertNull(oAuth2Service.getTenantOAuth2ClientsParams(tenantId).getClientsDomainsParams()); - tenantClientsParams.getClientsDomainsParams().forEach(oAuth2ClientsDomainParams -> { - String domainName = oAuth2ClientsDomainParams.getDomainName(); - String domainKey = OAuth2Utils.constructAdminSettingsDomainKey(domainName); - Assert.assertNull(adminSettingsService.findAdminSettingsByKey(tenantId, domainKey)); - }); - } - - - private void clearSysAdmin() { - oAuth2Service.deleteSystemOAuth2ClientsParams(); - Assert.assertNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, OAuth2Utils.OAUTH2_CLIENT_REGISTRATIONS_PARAMS)); + public void testDeleteTenantOAuth2ClientRegistrations() { + oAuth2Service.saveClientRegistration(validClientRegistration(UUID.randomUUID().toString(), tenantId)); + oAuth2Service.saveClientRegistration(validClientRegistration(UUID.randomUUID().toString(), tenantId)); + oAuth2Service.saveClientRegistration(validClientRegistration(UUID.randomUUID().toString(), tenantId)); + Assert.assertEquals(3, oAuth2Service.findAllClientRegistrations().size()); + Assert.assertEquals(3, oAuth2Service.findClientRegistrationsByTenantId(tenantId).size()); + + oAuth2Service.deleteClientRegistrationsByTenantId(tenantId); + Assert.assertEquals(0, oAuth2Service.findAllClientRegistrations().size()); + Assert.assertEquals(0, oAuth2Service.findClientRegistrationsByTenantId(tenantId).size()); } private void updateTenantAllowOAuth2Setting(Boolean allowOAuth2) throws IOException { @@ -394,87 +240,17 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { } } - private OAuth2ClientsParams validClientsParams() { - OAuth2ClientRegistration first = validClientRegistration(); - OAuth2ClientRegistration second = validClientRegistration(); - return OAuth2ClientsParams.builder() - .clientsDomainsParams(Collections.singletonList( - OAuth2ClientsDomainParams.builder() - .domainName(UUID.randomUUID().toString()) - .redirectUriTemplate("http://localhost:8080/login/oauth2/code/") - .clientRegistrations(Arrays.asList(first, second)) - .build() - )) - .build(); - } - - private OAuth2ClientsParams validClientsParamsWithThreeDomains() { - OAuth2ClientRegistration first = validClientRegistration(); - OAuth2ClientRegistration second = validClientRegistration(); - OAuth2ClientRegistration third = validClientRegistration(); - return OAuth2ClientsParams.builder() - .clientsDomainsParams(Arrays.asList( - OAuth2ClientsDomainParams.builder() - .domainName(UUID.randomUUID().toString()) - .redirectUriTemplate("http://localhost:8080/login/oauth2/code/") - .clientRegistrations(Arrays.asList(first, second)) - .build(), - OAuth2ClientsDomainParams.builder() - .domainName(UUID.randomUUID().toString()) - .redirectUriTemplate("http://localhost:8080/login/oauth2/code/") - .clientRegistrations(Arrays.asList(third)) - .build() - )) - .build(); - } - - - private OAuth2ClientsParams clientsParamsWithDuplicateDomains() { - OAuth2ClientRegistration first = validClientRegistration(); - OAuth2ClientRegistration second = validClientRegistration(); - OAuth2ClientRegistration third = validClientRegistration(); - return OAuth2ClientsParams.builder() - .clientsDomainsParams(Arrays.asList( - OAuth2ClientsDomainParams.builder() - .domainName("domain") - .redirectUriTemplate("http://localhost:8080/login/oauth2/code/") - .clientRegistrations(Collections.singletonList(first)) - .build(), - OAuth2ClientsDomainParams.builder() - .domainName("domain") - .redirectUriTemplate("http://localhost:8080/login/oauth2/code/") - .clientRegistrations(Collections.singletonList(second)) - .build(), - OAuth2ClientsDomainParams.builder() - .domainName(UUID.randomUUID().toString()) - .redirectUriTemplate("http://localhost:8080/login/oauth2/code/") - .clientRegistrations(Collections.singletonList(third)) - .build() - )) - .build(); - } - - private OAuth2ClientsParams clientsParamsWithDuplicateRegistrationIds() { - OAuth2ClientRegistration first = validClientRegistration(); - first.setRegistrationId("registrationId"); - OAuth2ClientRegistration second = validClientRegistration(); - OAuth2ClientRegistration third = validClientRegistration(); - third.setRegistrationId("registrationId"); - return OAuth2ClientsParams.builder() - .clientsDomainsParams(Arrays.asList( - OAuth2ClientsDomainParams.builder() - .domainName(UUID.randomUUID().toString()) - .redirectUriTemplate("http://localhost:8080/login/oauth2/code/") - .clientRegistrations(Arrays.asList(first, second, third)) - .build() - )) - .build(); + private OAuth2ClientRegistration validClientRegistration(String registrationId, TenantId tenantId) { + return validClientRegistration("domainName", registrationId, tenantId); } - private OAuth2ClientRegistration validClientRegistration() { - return OAuth2ClientRegistration.builder() - .registrationId(UUID.randomUUID().toString()) - .mapperConfig(OAuth2MapperConfig.builder() + private OAuth2ClientRegistration validClientRegistration(String domainName, String registrationId, TenantId tenantId) { + OAuth2ClientRegistration clientRegistration = new OAuth2ClientRegistration(); + clientRegistration.setTenantId(tenantId); + clientRegistration.setRegistrationId(registrationId); + clientRegistration.setDomainName(domainName); + clientRegistration.setMapperConfig( + OAuth2MapperConfig.builder() .allowUserCreation(true) .activateUser(true) .type(MapperType.CUSTOM) @@ -483,17 +259,20 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { .url("localhost:8082") .build() ) - .build()) - .clientId("clientId") - .clientSecret("clientSecret") - .authorizationUri("authorizationUri") - .accessTokenUri("tokenUri") - .scope(Arrays.asList("scope1", "scope2")) - .userInfoUri("userInfoUri") - .userNameAttributeName("userNameAttributeName") - .jwkSetUri("jwkSetUri") - .clientAuthenticationMethod("clientAuthenticationMethod") - .loginButtonLabel("loginButtonLabel") - .build(); + .build() + ); + clientRegistration.setClientId("clientId"); + clientRegistration.setClientSecret("clientSecret"); + clientRegistration.setAuthorizationUri("authorizationUri"); + clientRegistration.setAccessTokenUri("tokenUri"); + clientRegistration.setRedirectUriTemplate("redirectUriTemplate"); + clientRegistration.setScope(Arrays.asList("scope1", "scope2")); + clientRegistration.setUserInfoUri("userInfoUri"); + clientRegistration.setUserNameAttributeName("userNameAttributeName"); + clientRegistration.setJwkSetUri("jwkSetUri"); + clientRegistration.setClientAuthenticationMethod("clientAuthenticationMethod"); + clientRegistration.setLoginButtonLabel("loginButtonLabel"); + clientRegistration.setLoginButtonIcon("loginButtonIcon"); + return clientRegistration; } } diff --git a/dao/src/test/resources/sql/hsql/drop-all-tables.sql b/dao/src/test/resources/sql/hsql/drop-all-tables.sql index d99a6d5e0f..e55c9bfcd5 100644 --- a/dao/src/test/resources/sql/hsql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/hsql/drop-all-tables.sql @@ -20,4 +20,5 @@ DROP TABLE IF EXISTS widgets_bundle; DROP TABLE IF EXISTS rule_node; DROP TABLE IF EXISTS rule_chain; DROP TABLE IF EXISTS entity_view; +DROP TABLE IF EXISTS oauth2_client_registration; DROP FUNCTION IF EXISTS to_uuid; diff --git a/dao/src/test/resources/sql/psql/drop-all-tables.sql b/dao/src/test/resources/sql/psql/drop-all-tables.sql index b2e4a27963..259a3e9fed 100644 --- a/dao/src/test/resources/sql/psql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/psql/drop-all-tables.sql @@ -21,4 +21,5 @@ DROP TABLE IF EXISTS widgets_bundle; DROP TABLE IF EXISTS rule_node; DROP TABLE IF EXISTS rule_chain; DROP TABLE IF EXISTS entity_view; +DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS tb_schema_settings; \ No newline at end of file diff --git a/dao/src/test/resources/sql/timescale/drop-all-tables.sql b/dao/src/test/resources/sql/timescale/drop-all-tables.sql index b2e4a27963..259a3e9fed 100644 --- a/dao/src/test/resources/sql/timescale/drop-all-tables.sql +++ b/dao/src/test/resources/sql/timescale/drop-all-tables.sql @@ -21,4 +21,5 @@ DROP TABLE IF EXISTS widgets_bundle; DROP TABLE IF EXISTS rule_node; DROP TABLE IF EXISTS rule_chain; DROP TABLE IF EXISTS entity_view; +DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS tb_schema_settings; \ No newline at end of file From 9b38574faea0e76fafe0501f3492253c26b33f05 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Wed, 5 Aug 2020 18:14:30 +0300 Subject: [PATCH 074/463] Fix sysadmin permissions --- .../permission/SysAdminPermissions.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java index 869217e4cf..f85b6c4906 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java @@ -39,7 +39,7 @@ public class SysAdminPermissions extends AbstractPermissions { put(Resource.USER, userPermissionChecker); put(Resource.WIDGETS_BUNDLE, systemEntityPermissionChecker); put(Resource.WIDGET_TYPE, systemEntityPermissionChecker); - put(Resource.OAUTH2_CONFIGURATION, PermissionChecker.allowAllPermissionChecker); + put(Resource.OAUTH2_CONFIGURATION, sysAdminOAuth2ConfigPermissionChecker); } private static final PermissionChecker systemEntityPermissionChecker = new PermissionChecker() { @@ -66,4 +66,19 @@ public class SysAdminPermissions extends AbstractPermissions { }; + private final PermissionChecker sysAdminOAuth2ConfigPermissionChecker = new PermissionChecker() { + @Override + public boolean hasPermission(SecurityUser user, Operation operation) { + return true; + } + + @Override + public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { + if (entity.getTenantId() != null && !entity.getTenantId().isNullUid()) { + return false; + } + return true; + } + }; + } From 32c8d7e5fd58b485762d4aa88765c179abffb352 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Wed, 12 Aug 2020 13:14:10 +0300 Subject: [PATCH 075/463] Removed 'registrationId' from ClientRegistration --- .../main/data/upgrade/3.1.0/schema_update.sql | 4 +- .../server/controller/BaseController.java | 2 +- .../Oauth2AuthenticationSuccessHandler.java | 3 +- .../server/dao/oauth2/OAuth2Service.java | 4 +- .../data/oauth2/OAuth2ClientRegistration.java | 2 - .../server/dao/model/ModelConstants.java | 1 - .../sql/OAuth2ClientRegistrationEntity.java | 4 -- .../HybridClientRegistrationRepository.java | 9 ++- .../oauth2/OAuth2ClientRegistrationDao.java | 4 -- .../server/dao/oauth2/OAuth2ServiceImpl.java | 32 ++--------- .../server/dao/oauth2/OAuth2Utils.java | 2 +- .../JpaOAuth2ClientRegistrationDao.java | 12 ---- .../OAuth2ClientRegistrationRepository.java | 7 --- .../resources/sql/schema-entities-hsql.sql | 4 +- .../main/resources/sql/schema-entities.sql | 4 +- .../dao/service/BaseOAuth2ServiceTest.java | 55 +++++++------------ 16 files changed, 39 insertions(+), 110 deletions(-) diff --git a/application/src/main/data/upgrade/3.1.0/schema_update.sql b/application/src/main/data/upgrade/3.1.0/schema_update.sql index 06daa9541c..76c3efcdfa 100644 --- a/application/src/main/data/upgrade/3.1.0/schema_update.sql +++ b/application/src/main/data/upgrade/3.1.0/schema_update.sql @@ -21,7 +21,6 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( created_time bigint NOT NULL, additional_info varchar, tenant_id uuid, - registration_id varchar(255), domain_name varchar(255), client_id varchar(255), client_secret varchar(255), @@ -48,6 +47,5 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( basic_always_full_screen boolean, custom_url varchar(255), custom_username varchar(255), - custom_password varchar(255), - CONSTRAINT oauth2_registration_id_unq_key UNIQUE (registration_id) + custom_password varchar(255) ); \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 3757223fda..1a2a54b1d1 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -540,7 +540,7 @@ public abstract class BaseController { OAuth2ClientRegistration checkOAuth2ClientRegistrationId(OAuth2ClientRegistrationId clientRegistrationId, Operation operation) throws ThingsboardException { try { validateId(clientRegistrationId, "Incorrect oAuth2ClientRegistrationId " + clientRegistrationId); - OAuth2ClientRegistration clientRegistration = oAuth2Service.findClientRegistrationById(getCurrentUser().getTenantId(), clientRegistrationId); + OAuth2ClientRegistration clientRegistration = oAuth2Service.findClientRegistration(clientRegistrationId.getId()); checkNotNull(clientRegistration); accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION, operation, clientRegistrationId, clientRegistration); return clientRegistration; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index e3d21b448d..9274e08939 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -36,6 +36,7 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.UUID; @Component(value = "oauth2AuthenticationSuccessHandler") public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { @@ -65,7 +66,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS try { OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; - OAuth2ClientRegistration clientRegistration = oAuth2Service.findClientRegistrationByRegistrationId(token.getAuthorizedClientRegistrationId()); + OAuth2ClientRegistration clientRegistration = oAuth2Service.findClientRegistration(UUID.fromString(token.getAuthorizedClientRegistrationId())); OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(clientRegistration.getMapperConfig().getType()); SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, clientRegistration.getTenantId(), clientRegistration.getMapperConfig()); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java index 6d4be61dc7..c8da37e9e5 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java @@ -30,9 +30,7 @@ public interface OAuth2Service { List findClientRegistrationsByTenantId(TenantId tenantId); - OAuth2ClientRegistration findClientRegistrationByRegistrationId(String registrationId); - - OAuth2ClientRegistration findClientRegistrationById(TenantId tenantId, OAuth2ClientRegistrationId id); + OAuth2ClientRegistration findClientRegistration(UUID id); List findAllClientRegistrations(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java index a0a7dcc48b..1da7942b17 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java @@ -32,7 +32,6 @@ import java.util.List; public class OAuth2ClientRegistration extends BaseData implements HasTenantId, HasName { private TenantId tenantId; - private String registrationId; private String domainName; private String redirectUriTemplate; private OAuth2MapperConfig mapperConfig; @@ -51,7 +50,6 @@ public class OAuth2ClientRegistration extends BaseData { - OAuth2ClientRegistration findByRegistrationId(String registrationId); - List findAll(); List findByTenantId(UUID tenantId); List findByDomainName(String domainName); - boolean removeByRegistrationId(String registrationId); - int removeByTenantId(UUID tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index f06d906e7c..535378c9a8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -46,7 +46,6 @@ import static org.thingsboard.server.dao.service.Validator.validateString; public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Service { public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; public static final String INCORRECT_CLIENT_REGISTRATION_ID = "Incorrect clientRegistrationId "; - public static final String INCORRECT_REGISTRATION_ID = "Incorrect registrationId "; public static final String INCORRECT_DOMAIN_NAME = "Incorrect domainName "; @Autowired @@ -68,18 +67,7 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se public OAuth2ClientRegistration saveClientRegistration(OAuth2ClientRegistration clientRegistration) { log.trace("Executing saveClientRegistration [{}]", clientRegistration); clientRegistrationValidator.validate(clientRegistration, OAuth2ClientRegistration::getTenantId); - OAuth2ClientRegistration savedClientRegistration; - try { - savedClientRegistration = clientRegistrationDao.save(clientRegistration.getTenantId(), clientRegistration); - } catch (Exception t) { - ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); - if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("oauth2_registration_id_unq_key")) { - throw new DataValidationException("Client registration with such registrationId already exists!"); - } else { - throw t; - } - } - return savedClientRegistration; + return clientRegistrationDao.save(clientRegistration.getTenantId(), clientRegistration); } @Override @@ -90,17 +78,10 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se } @Override - public OAuth2ClientRegistration findClientRegistrationByRegistrationId(String registrationId) { - log.trace("Executing findClientRegistrationByRegistrationId [{}]", registrationId); - validateString(registrationId, INCORRECT_REGISTRATION_ID + registrationId); - return clientRegistrationDao.findByRegistrationId(registrationId); - } - - @Override - public OAuth2ClientRegistration findClientRegistrationById(TenantId tenantId, OAuth2ClientRegistrationId id) { - log.trace("Executing findClientRegistrationById [{}]", id); + public OAuth2ClientRegistration findClientRegistration(UUID id) { + log.trace("Executing findClientRegistration [{}]", id); validateId(id, INCORRECT_CLIENT_REGISTRATION_ID + id); - return clientRegistrationDao.findById(tenantId, id.getId()); + return clientRegistrationDao.findById(null, id); } @Override @@ -138,7 +119,7 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se } } - private DataValidator clientRegistrationValidator = + private final DataValidator clientRegistrationValidator = new DataValidator() { @Override @@ -151,9 +132,6 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se @Override protected void validateDataImpl(TenantId tenantId, OAuth2ClientRegistration clientRegistration) { - if (StringUtils.isEmpty(clientRegistration.getRegistrationId())) { - throw new DataValidationException("Registration ID should be specified!"); - } if (StringUtils.isEmpty(clientRegistration.getDomainName())) { throw new DataValidationException("Domain name should be specified!"); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java index 7ccad60bd7..948c894540 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java @@ -29,7 +29,7 @@ public class OAuth2Utils { public static OAuth2ClientInfo toClientInfo(OAuth2ClientRegistration clientRegistration) { OAuth2ClientInfo client = new OAuth2ClientInfo(); client.setName(clientRegistration.getLoginButtonLabel()); - client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, clientRegistration.getRegistrationId())); + client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, clientRegistration.getUuidId().toString())); client.setIcon(clientRegistration.getLoginButtonIcon()); return client; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java index d40fb060dc..ef10245e19 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java @@ -45,12 +45,6 @@ public class JpaOAuth2ClientRegistrationDao extends JpaAbstractDao entity = repository.findByRegistrationId(registrationId); - return DaoUtil.getData(entity); - } - @Override public List findAll() { Iterable entities = repository.findAll(); @@ -73,12 +67,6 @@ public class JpaOAuth2ClientRegistrationDao extends JpaAbstractDao { - Optional findByRegistrationId(String registrationId); - List findAllByTenantId(UUID tenantId); List findAllByDomainName(String domainName); - int deleteByRegistrationId(String registrationId); - int deleteByTenantId(UUID tenantId); - - boolean existsByRegistrationId(String registrationId); } diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index bb38f27aef..711ab8037e 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -296,7 +296,6 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( created_time bigint NOT NULL, additional_info varchar, tenant_id uuid, - registration_id varchar(255), domain_name varchar(255), client_id varchar(255), client_secret varchar(255), @@ -323,6 +322,5 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( basic_always_full_screen boolean, custom_url varchar(255), custom_username varchar(255), - custom_password varchar(255), - CONSTRAINT oauth2_registration_id_unq_key UNIQUE (registration_id) + custom_password varchar(255) ); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 441b676647..84bfaea563 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -311,7 +311,6 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( created_time bigint NOT NULL, additional_info varchar, tenant_id uuid, - registration_id varchar(255), domain_name varchar(255), client_id varchar(255), client_secret varchar(255), @@ -338,8 +337,7 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( basic_always_full_screen boolean, custom_url varchar(255), custom_username varchar(255), - custom_password varchar(255), - CONSTRAINT oauth2_registration_id_unq_key UNIQUE (registration_id) + custom_password varchar(255) ); CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint) diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java index a0e430fdcd..d9165b08b8 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -86,18 +86,10 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { Assert.assertTrue(oAuth2Service.isOAuth2ClientRegistrationAllowed(tenantId)); } - @Test(expected = DataValidationException.class) - public void testSaveDuplicateRegistrationId() { - OAuth2ClientRegistration first = validClientRegistration("duplicated_reg_id", TenantId.SYS_TENANT_ID); - OAuth2ClientRegistration second = validClientRegistration("duplicated_reg_id", tenantId); - - oAuth2Service.saveClientRegistration(first); - oAuth2Service.saveClientRegistration(second); - } @Test public void testCreateNewSystemParams() { - OAuth2ClientRegistration clientRegistration = validClientRegistration(UUID.randomUUID().toString(), TenantId.SYS_TENANT_ID); + OAuth2ClientRegistration clientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID); OAuth2ClientRegistration savedClientRegistration = oAuth2Service.saveClientRegistration(clientRegistration); Assert.assertNotNull(savedClientRegistration); @@ -109,7 +101,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testFindSystemParamsByTenant() { - OAuth2ClientRegistration clientRegistration = validClientRegistration(UUID.randomUUID().toString(), TenantId.SYS_TENANT_ID); + OAuth2ClientRegistration clientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID); oAuth2Service.saveClientRegistration(clientRegistration); List clientRegistrationsByTenantId = oAuth2Service.findClientRegistrationsByTenantId(TenantId.SYS_TENANT_ID); @@ -124,7 +116,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testCreateNewTenantParams() { - OAuth2ClientRegistration clientRegistration = validClientRegistration(UUID.randomUUID().toString(), tenantId); + OAuth2ClientRegistration clientRegistration = validClientRegistration(tenantId); OAuth2ClientRegistration savedClientRegistration = oAuth2Service.saveClientRegistration(clientRegistration); Assert.assertNotNull(savedClientRegistration); @@ -136,7 +128,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testFindTenantParams() { - OAuth2ClientRegistration clientRegistration = validClientRegistration(UUID.randomUUID().toString(), tenantId); + OAuth2ClientRegistration clientRegistration = validClientRegistration(tenantId); oAuth2Service.saveClientRegistration(clientRegistration); List clientRegistrationsByTenantId = oAuth2Service.findClientRegistrationsByTenantId(tenantId); @@ -151,8 +143,8 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testGetClientRegistrationWithTenant() { - OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(UUID.randomUUID().toString(), tenantId); - OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(UUID.randomUUID().toString(), TenantId.SYS_TENANT_ID); + OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(tenantId); + OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID); OAuth2ClientRegistration savedTenantClientRegistration = oAuth2Service.saveClientRegistration(tenantClientRegistration); OAuth2ClientRegistration savedSysAdminClientRegistration = oAuth2Service.saveClientRegistration(sysAdminClientRegistration); @@ -163,22 +155,16 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { Assert.assertEquals(savedSysAdminClientRegistration, oAuth2Service.findClientRegistrationsByTenantId(TenantId.SYS_TENANT_ID).get(0)); Assert.assertEquals(savedTenantClientRegistration, - oAuth2Service.findClientRegistrationByRegistrationId(savedTenantClientRegistration.getRegistrationId())); - Assert.assertEquals(savedSysAdminClientRegistration, - oAuth2Service.findClientRegistrationByRegistrationId(savedSysAdminClientRegistration.getRegistrationId())); - - Assert.assertEquals(savedTenantClientRegistration, - oAuth2Service.findClientRegistrationById(tenantId, savedTenantClientRegistration.getId())); + oAuth2Service.findClientRegistration(savedTenantClientRegistration.getUuidId())); Assert.assertEquals(savedSysAdminClientRegistration, - oAuth2Service.findClientRegistrationById(TenantId.SYS_TENANT_ID, savedSysAdminClientRegistration.getId())); - + oAuth2Service.findClientRegistration(savedSysAdminClientRegistration.getUuidId())); } @Test public void testGetOAuth2Clients() { String testDomainName = "test_domain"; - OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(testDomainName, UUID.randomUUID().toString(), tenantId); - OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(testDomainName, UUID.randomUUID().toString(), TenantId.SYS_TENANT_ID); + OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(tenantId, testDomainName); + OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID, testDomainName); oAuth2Service.saveClientRegistration(tenantClientRegistration); oAuth2Service.saveClientRegistration(sysAdminClientRegistration); @@ -195,8 +181,8 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testGetEmptyOAuth2Clients() { String testDomainName = "test_domain"; - OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(testDomainName, UUID.randomUUID().toString(), tenantId); - OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(testDomainName, UUID.randomUUID().toString(), TenantId.SYS_TENANT_ID); + OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(tenantId, testDomainName); + OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID, testDomainName); oAuth2Service.saveClientRegistration(tenantClientRegistration); oAuth2Service.saveClientRegistration(sysAdminClientRegistration); List oAuth2Clients = oAuth2Service.getOAuth2Clients("random-domain"); @@ -205,8 +191,8 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testDeleteOAuth2ClientRegistration() { - OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(UUID.randomUUID().toString(), tenantId); - OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(UUID.randomUUID().toString(), TenantId.SYS_TENANT_ID); + OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(tenantId); + OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID); OAuth2ClientRegistration savedTenantRegistration = oAuth2Service.saveClientRegistration(tenantClientRegistration); OAuth2ClientRegistration savedSysAdminRegistration = oAuth2Service.saveClientRegistration(sysAdminClientRegistration); @@ -218,9 +204,9 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testDeleteTenantOAuth2ClientRegistrations() { - oAuth2Service.saveClientRegistration(validClientRegistration(UUID.randomUUID().toString(), tenantId)); - oAuth2Service.saveClientRegistration(validClientRegistration(UUID.randomUUID().toString(), tenantId)); - oAuth2Service.saveClientRegistration(validClientRegistration(UUID.randomUUID().toString(), tenantId)); + oAuth2Service.saveClientRegistration(validClientRegistration(tenantId)); + oAuth2Service.saveClientRegistration(validClientRegistration(tenantId)); + oAuth2Service.saveClientRegistration(validClientRegistration(tenantId)); Assert.assertEquals(3, oAuth2Service.findAllClientRegistrations().size()); Assert.assertEquals(3, oAuth2Service.findClientRegistrationsByTenantId(tenantId).size()); @@ -240,14 +226,13 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { } } - private OAuth2ClientRegistration validClientRegistration(String registrationId, TenantId tenantId) { - return validClientRegistration("domainName", registrationId, tenantId); + private OAuth2ClientRegistration validClientRegistration(TenantId tenantId) { + return validClientRegistration(tenantId, "domainName"); } - private OAuth2ClientRegistration validClientRegistration(String domainName, String registrationId, TenantId tenantId) { + private OAuth2ClientRegistration validClientRegistration(TenantId tenantId, String domainName) { OAuth2ClientRegistration clientRegistration = new OAuth2ClientRegistration(); clientRegistration.setTenantId(tenantId); - clientRegistration.setRegistrationId(registrationId); clientRegistration.setDomainName(domainName); clientRegistration.setMapperConfig( OAuth2MapperConfig.builder() From 2c537b28f4376911f8c744fc511252e12daaac39 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Wed, 12 Aug 2020 17:31:34 +0300 Subject: [PATCH 076/463] Added ClientRegistrationTemplate --- .../main/data/upgrade/3.1.0/schema_update.sql | 25 ++++ .../server/controller/BaseController.java | 20 +++ .../server/controller/OAuth2Controller.java | 62 +++++++- .../service/security/permission/Resource.java | 1 + .../permission/SysAdminPermissions.java | 1 + .../permission/TenantAdminPermissions.java | 15 +- .../oauth2/OAuth2ConfigTemplateService.java | 31 ++++ .../server/common/data/EntityType.java | 2 +- .../common/data/id/EntityIdFactory.java | 2 + .../OAuth2ClientRegistrationTemplateId.java | 39 +++++ .../data/oauth2/OAuth2ClientRegistration.java | 6 +- .../OAuth2ClientRegistrationTemplate.java | 65 ++++++++ .../server/dao/model/ModelConstants.java | 2 + .../sql/OAuth2ClientRegistrationEntity.java | 2 +- ...Auth2ClientRegistrationTemplateEntity.java | 139 ++++++++++++++++++ .../OAuth2ClientRegistrationTemplateDao.java | 25 ++++ .../OAuth2ConfigTemplateServiceImpl.java | 107 ++++++++++++++ ...paOAuth2ClientRegistrationTemplateDao.java | 53 +++++++ ...2ClientRegistrationTemplateRepository.java | 24 +++ .../resources/sql/schema-entities-hsql.sql | 23 +++ .../main/resources/sql/schema-entities.sql | 23 +++ .../BaseOAuth2ConfigTemplateServiceTest.java | 129 ++++++++++++++++ .../OAuth2ConfigTemplateServiceSqlTest.java | 23 +++ .../resources/sql/hsql/drop-all-tables.sql | 1 + .../resources/sql/psql/drop-all-tables.sql | 1 + .../sql/timescale/drop-all-tables.sql | 1 + 26 files changed, 812 insertions(+), 10 deletions(-) create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateService.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationTemplateId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationTemplateEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationTemplateDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationTemplateDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationTemplateRepository.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/sql/OAuth2ConfigTemplateServiceSqlTest.java diff --git a/application/src/main/data/upgrade/3.1.0/schema_update.sql b/application/src/main/data/upgrade/3.1.0/schema_update.sql index 76c3efcdfa..a0eacbacbd 100644 --- a/application/src/main/data/upgrade/3.1.0/schema_update.sql +++ b/application/src/main/data/upgrade/3.1.0/schema_update.sql @@ -48,4 +48,29 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( custom_url varchar(255), custom_username varchar(255), custom_password varchar(255) +); + +DROP TABLE IF EXISTS oauth2_client_registration_template; + +CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY, + created_time bigint NOT NULL, + tenant_id uuid, + provider_id varchar(255), + authorization_uri varchar(255), + token_uri varchar(255), + scope varchar(255), + user_info_uri varchar(255), + user_name_attribute_name varchar(255), + jwk_set_uri varchar(255), + client_authentication_method varchar(255), + basic_email_attribute_key varchar(31), + basic_first_name_attribute_key varchar(31), + basic_last_name_attribute_key varchar(31), + basic_tenant_name_strategy varchar(31), + basic_tenant_name_pattern varchar(255), + basic_customer_name_pattern varchar(255), + basic_default_dashboard_name varchar(255), + basic_always_full_screen boolean, + CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) ); \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 1a2a54b1d1..371b423105 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -51,6 +51,7 @@ import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; @@ -75,6 +76,7 @@ import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rule.RuleChainService; @@ -153,6 +155,9 @@ public abstract class BaseController { @Autowired protected OAuth2Service oAuth2Service; + @Autowired + protected OAuth2ConfigTemplateService oAuth2ConfigTemplateService; + @Autowired protected ComponentDiscoveryService componentDescriptorService; @@ -385,6 +390,9 @@ public abstract class BaseController { case OAUTH2_CLIENT_REGISTRATION: checkOAuth2ClientRegistrationId(new OAuth2ClientRegistrationId(entityId.getId()), operation); return; + case OAUTH2_CLIENT_REGISTRATION_TEMPLATE: + checkOAuth2ClientRegistrationTemplateId(new OAuth2ClientRegistrationTemplateId(entityId.getId()), operation); + return; default: throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType()); } @@ -549,6 +557,18 @@ public abstract class BaseController { } } + OAuth2ClientRegistrationTemplate checkOAuth2ClientRegistrationTemplateId(OAuth2ClientRegistrationTemplateId clientRegistrationTemplateId, Operation operation) throws ThingsboardException { + try { + validateId(clientRegistrationTemplateId, "Incorrect oAuth2ClientRegistrationTemplateId " + clientRegistrationTemplateId); + OAuth2ClientRegistrationTemplate clientRegistrationTemplate = oAuth2ConfigTemplateService.findClientRegistrationTemplateById(clientRegistrationTemplateId); + checkNotNull(clientRegistrationTemplate); + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, operation, clientRegistrationTemplateId, clientRegistrationTemplate); + return clientRegistrationTemplate; + } catch (Exception e) { + throw handleException(e, false); + } + } + ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException { try { log.debug("[{}] Lookup component descriptor", clazz); diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index 9033b17c47..da510fe84e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -26,9 +26,11 @@ import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.oauth2.OAuth2Service; @@ -44,8 +46,8 @@ import java.util.List; @RequestMapping("/api") @Slf4j public class OAuth2Controller extends BaseController { - public static final String CLIENT_REGISTRATION_ID = "clientRegistrationId"; - private static final String REGISTRATION_ID = "registrationId"; + private static final String CLIENT_REGISTRATION_ID = "clientRegistrationId"; + private static final String CLIENT_REGISTRATION_TEMPLATE_ID = "clientRegistrationTemplateId"; @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) @ResponseBody @@ -91,6 +93,19 @@ public class OAuth2Controller extends BaseController { } } + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + @RequestMapping(value = "/oauth2/config/template", method = RequestMethod.POST) + @ResponseStatus(value = HttpStatus.OK) + public OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(@RequestBody OAuth2ClientRegistrationTemplate clientRegistrationTemplate) throws ThingsboardException { + try { + clientRegistrationTemplate.setTenantId(getCurrentUser().getTenantId()); + checkEntity(clientRegistrationTemplate.getId(), clientRegistrationTemplate, Resource.OAUTH2_CONFIGURATION_TEMPLATE); + return oAuth2ConfigTemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate); + } catch (Exception e) { + throw handleException(e); + } + } + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/oauth2/config/{clientRegistrationId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) @@ -116,6 +131,31 @@ public class OAuth2Controller extends BaseController { } } + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + @RequestMapping(value = "/oauth2/config/template/{clientRegistrationTemplateId}", method = RequestMethod.DELETE) + @ResponseStatus(value = HttpStatus.OK) + public void deleteClientRegistrationTemplate(@PathVariable(CLIENT_REGISTRATION_TEMPLATE_ID) String strClientRegistrationTemplateId) throws ThingsboardException { + checkParameter(CLIENT_REGISTRATION_TEMPLATE_ID, strClientRegistrationTemplateId); + try { + OAuth2ClientRegistrationTemplateId clientRegistrationTemplateId = new OAuth2ClientRegistrationTemplateId(toUUID(strClientRegistrationTemplateId)); + OAuth2ClientRegistrationTemplate clientRegistrationTemplate = checkOAuth2ClientRegistrationTemplateId(clientRegistrationTemplateId, Operation.DELETE); + oAuth2ConfigTemplateService.deleteClientRegistrationTemplateById(clientRegistrationTemplateId); + + logEntityAction(clientRegistrationTemplateId, clientRegistrationTemplate, + null, + ActionType.DELETED, null, strClientRegistrationTemplateId); + + } catch (Exception e) { + + logEntityAction(emptyId(EntityType.OAUTH2_CLIENT_REGISTRATION_TEMPLATE), + null, + null, + ActionType.DELETED, e, strClientRegistrationTemplateId); + + throw handleException(e); + } + } + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/oauth2/config/isAllowed", method = RequestMethod.GET) @ResponseBody @@ -127,11 +167,25 @@ public class OAuth2Controller extends BaseController { } } + + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(value = "/oauth2/config/template", method = RequestMethod.GET, produces = "application/json") + @ResponseBody + public List getClientRegistrationTemplates() throws ThingsboardException { + try { + checkOAuth2ConfigTemplatePermissions(Operation.READ); + return oAuth2ConfigTemplateService.findAllClientRegistrationTemplates(); + } catch (Exception e) { + throw handleException(e); + } + } + private void checkOAuth2ConfigPermissions(Operation operation) throws ThingsboardException { accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION, operation); } - private void checkOAuth2ConfigPermissions(Operation operation, OAuth2ClientRegistration clientRegistration) throws ThingsboardException { - accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION, operation, clientRegistration.getId(), clientRegistration); + private void checkOAuth2ConfigTemplatePermissions(Operation operation) throws ThingsboardException { + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, operation); } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java index 84819dda48..7bae743c22 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java @@ -33,6 +33,7 @@ public enum Resource { WIDGETS_BUNDLE(EntityType.WIDGETS_BUNDLE), WIDGET_TYPE(EntityType.WIDGET_TYPE), OAUTH2_CONFIGURATION(EntityType.OAUTH2_CLIENT_REGISTRATION), + OAUTH2_CONFIGURATION_TEMPLATE(EntityType.OAUTH2_CLIENT_REGISTRATION_TEMPLATE), ; private final EntityType entityType; diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java index f85b6c4906..ba295a064d 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java @@ -40,6 +40,7 @@ public class SysAdminPermissions extends AbstractPermissions { put(Resource.WIDGETS_BUNDLE, systemEntityPermissionChecker); put(Resource.WIDGET_TYPE, systemEntityPermissionChecker); put(Resource.OAUTH2_CONFIGURATION, sysAdminOAuth2ConfigPermissionChecker); + put(Resource.OAUTH2_CONFIGURATION_TEMPLATE, PermissionChecker.allowAllPermissionChecker); } private static final PermissionChecker systemEntityPermissionChecker = new PermissionChecker() { diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java index 8476994a4e..0153affa23 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java @@ -48,6 +48,7 @@ public class TenantAdminPermissions extends AbstractPermissions { put(Resource.WIDGETS_BUNDLE, widgetsPermissionChecker); put(Resource.WIDGET_TYPE, widgetsPermissionChecker); put(Resource.OAUTH2_CONFIGURATION, tenantOAuth2ConfigPermissionChecker); + put(Resource.OAUTH2_CONFIGURATION_TEMPLATE, tenantOAuth2ConfigTemplatePermissionChecker); } public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() { @@ -108,7 +109,7 @@ public class TenantAdminPermissions extends AbstractPermissions { }; - private final PermissionChecker tenantOAuth2ConfigPermissionChecker = new PermissionChecker() { + private final PermissionChecker tenantOAuth2ConfigPermissionChecker = new PermissionChecker() { @Override public boolean hasPermission(SecurityUser user, Operation operation) { return oAuth2Service.isOAuth2ClientRegistrationAllowed(user.getTenantId()); @@ -122,4 +123,16 @@ public class TenantAdminPermissions extends AbstractPermissions { return hasPermission(user, operation); } }; + + private static final PermissionChecker tenantOAuth2ConfigTemplatePermissionChecker = new PermissionChecker() { + @Override + public boolean hasPermission(SecurityUser user, Operation operation) { + return operation == Operation.READ; + } + + @Override + public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { + return operation == Operation.READ; + } + }; } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateService.java new file mode 100644 index 0000000000..b3718a8d92 --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateService.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.oauth2; + +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; + +import java.util.List; + +public interface OAuth2ConfigTemplateService { + OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate); + + OAuth2ClientRegistrationTemplate findClientRegistrationTemplateById(OAuth2ClientRegistrationTemplateId templateId); + + List findAllClientRegistrationTemplates(); + + void deleteClientRegistrationTemplateById(OAuth2ClientRegistrationTemplateId templateId); +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index d3ea208aa5..7791597141 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -19,5 +19,5 @@ package org.thingsboard.server.common.data; * @author Andrew Shvayka */ public enum EntityType { - TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, OAUTH2_CLIENT_REGISTRATION + TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, OAUTH2_CLIENT_REGISTRATION, OAUTH2_CLIENT_REGISTRATION_TEMPLATE } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java index 03f05c54a4..41d9a929f2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java @@ -64,6 +64,8 @@ public class EntityIdFactory { return new WidgetTypeId(uuid); case OAUTH2_CLIENT_REGISTRATION: return new OAuth2ClientRegistrationId(uuid); + case OAUTH2_CLIENT_REGISTRATION_TEMPLATE: + return new OAuth2ClientRegistrationTemplateId(uuid); } throw new IllegalArgumentException("EntityType " + type + " is not supported!"); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationTemplateId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationTemplateId.java new file mode 100644 index 0000000000..03edd5054b --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationTemplateId.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; + +import java.util.UUID; + +public class OAuth2ClientRegistrationTemplateId extends UUIDBased implements EntityId { + + @JsonCreator + public OAuth2ClientRegistrationTemplateId(@JsonProperty("id") UUID id) { + super(id); + } + + public static OAuth2ClientRegistrationTemplateId fromString(String clientRegistrationTemplateId) { + return new OAuth2ClientRegistrationTemplateId(UUID.fromString(clientRegistrationTemplateId)); + } + + @Override + public EntityType getEntityType() { + return EntityType.OAUTH2_CLIENT_REGISTRATION_TEMPLATE; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java index 1da7942b17..1e7ff2a1e7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java @@ -49,9 +49,9 @@ public class OAuth2ClientRegistration extends BaseData implements HasTenantId, HasName { + + private TenantId tenantId; + private String providerId; + private OAuth2BasicMapperConfig basic; + private String authorizationUri; + private String accessTokenUri; + private List scope; + private String userInfoUri; + private String userNameAttributeName; + private String jwkSetUri; + private String clientAuthenticationMethod; + + public OAuth2ClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { + super(clientRegistrationTemplate); + this.tenantId = clientRegistrationTemplate.tenantId; + this.providerId = clientRegistrationTemplate.providerId; + this.basic = clientRegistrationTemplate.basic; + this.authorizationUri = clientRegistrationTemplate.authorizationUri; + this.accessTokenUri = clientRegistrationTemplate.accessTokenUri; + this.scope = clientRegistrationTemplate.scope; + this.userInfoUri = clientRegistrationTemplate.userInfoUri; + this.userNameAttributeName = clientRegistrationTemplate.userNameAttributeName; + this.jwkSetUri = clientRegistrationTemplate.jwkSetUri; + this.clientAuthenticationMethod = clientRegistrationTemplate.clientAuthenticationMethod; + } + + @Override + public String getName() { + return providerId; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 84a1973e20..4015473d9f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -359,6 +359,8 @@ public class ModelConstants { */ public static final String OAUTH2_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; public static final String OAUTH2_CLIENT_REGISTRATION_COLUMN_FAMILY_NAME = "oauth2_client_registration"; + public static final String OAUTH2_CLIENT_REGISTRATION_TEMPLATE_COLUMN_FAMILY_NAME = "oauth2_client_registration_template"; + public static final String OAUTH2_TEMPLATE_PROVIDER_ID_PROPERTY = "provider_id"; public static final String OAUTH2_DOMAIN_NAME_PROPERTY = "domain_name"; public static final String OAUTH2_CLIENT_ID_PROPERTY = "client_id"; public static final String OAUTH2_CLIENT_SECRET_PROPERTY = "client_secret"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java index cc494cd707..db05b02a05 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java @@ -151,8 +151,8 @@ public class OAuth2ClientRegistrationEntity extends BaseSqlEntity { + + @Column(name = ModelConstants.OAUTH2_TENANT_ID_PROPERTY, columnDefinition = "uuid") + private UUID tenantId; + + @Column(name = ModelConstants.OAUTH2_TEMPLATE_PROVIDER_ID_PROPERTY) + private String providerId; + @Column(name = ModelConstants.OAUTH2_AUTHORIZATION_URI_PROPERTY) + private String authorizationUri; + @Column(name = ModelConstants.OAUTH2_TOKEN_URI_PROPERTY) + private String tokenUri; + @Column(name = ModelConstants.OAUTH2_SCOPE_PROPERTY) + private String scope; + @Column(name = ModelConstants.OAUTH2_USER_INFO_URI_PROPERTY) + private String userInfoUri; + @Column(name = ModelConstants.OAUTH2_USER_NAME_ATTRIBUTE_NAME_PROPERTY) + private String userNameAttributeName; + @Column(name = ModelConstants.OAUTH2_JWK_SET_URI_PROPERTY) + private String jwkSetUri; + @Column(name = ModelConstants.OAUTH2_CLIENT_AUTHENTICATION_METHOD_PROPERTY) + private String clientAuthenticationMethod; + @Column(name = ModelConstants.OAUTH2_EMAIL_ATTRIBUTE_KEY_PROPERTY) + private String emailAttributeKey; + @Column(name = ModelConstants.OAUTH2_FIRST_NAME_ATTRIBUTE_KEY_PROPERTY) + private String firstNameAttributeKey; + @Column(name = ModelConstants.OAUTH2_LAST_NAME_ATTRIBUTE_KEY_PROPERTY) + private String lastNameAttributeKey; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.OAUTH2_TENANT_NAME_STRATEGY_PROPERTY) + private TenantNameStrategyType tenantNameStrategy; + @Column(name = ModelConstants.OAUTH2_TENANT_NAME_PATTERN_PROPERTY) + private String tenantNamePattern; + @Column(name = ModelConstants.OAUTH2_CUSTOMER_NAME_PATTERN_PROPERTY) + private String customerNamePattern; + @Column(name = ModelConstants.OAUTH2_DEFAULT_DASHBOARD_NAME_PROPERTY) + private String defaultDashboardName; + @Column(name = ModelConstants.OAUTH2_ALWAYS_FULL_SCREEN_PROPERTY) + private Boolean alwaysFullScreen; + + public OAuth2ClientRegistrationTemplateEntity() { + } + + public OAuth2ClientRegistrationTemplateEntity(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { + if (clientRegistrationTemplate.getId() != null) { + this.setUuid(clientRegistrationTemplate.getId().getId()); + } + if (clientRegistrationTemplate.getTenantId() != null) { + this.tenantId = clientRegistrationTemplate.getTenantId().getId(); + } + this.createdTime = clientRegistrationTemplate.getCreatedTime(); + this.providerId = clientRegistrationTemplate.getProviderId(); + this.authorizationUri = clientRegistrationTemplate.getAuthorizationUri(); + this.tokenUri = clientRegistrationTemplate.getAccessTokenUri(); + this.scope = clientRegistrationTemplate.getScope().stream().reduce((result, element) -> result + "," + element).orElse(""); + this.userInfoUri = clientRegistrationTemplate.getUserInfoUri(); + this.userNameAttributeName = clientRegistrationTemplate.getUserNameAttributeName(); + this.jwkSetUri = clientRegistrationTemplate.getJwkSetUri(); + this.clientAuthenticationMethod = clientRegistrationTemplate.getClientAuthenticationMethod(); + OAuth2BasicMapperConfig basicConfig = clientRegistrationTemplate.getBasic(); + if (basicConfig != null) { + this.emailAttributeKey = basicConfig.getEmailAttributeKey(); + this.firstNameAttributeKey = basicConfig.getFirstNameAttributeKey(); + this.lastNameAttributeKey = basicConfig.getLastNameAttributeKey(); + this.tenantNameStrategy = basicConfig.getTenantNameStrategy(); + this.tenantNamePattern = basicConfig.getTenantNamePattern(); + this.customerNamePattern = basicConfig.getCustomerNamePattern(); + this.defaultDashboardName = basicConfig.getDefaultDashboardName(); + this.alwaysFullScreen = basicConfig.isAlwaysFullScreen(); + } + } + + @Override + public OAuth2ClientRegistrationTemplate toData() { + OAuth2ClientRegistrationTemplate clientRegistrationTemplate = new OAuth2ClientRegistrationTemplate(); + clientRegistrationTemplate.setId(new OAuth2ClientRegistrationTemplateId(id)); + clientRegistrationTemplate.setTenantId(new TenantId(tenantId)); + clientRegistrationTemplate.setCreatedTime(createdTime); + + clientRegistrationTemplate.setProviderId(providerId); + clientRegistrationTemplate.setBasic( + OAuth2BasicMapperConfig.builder() + .emailAttributeKey(emailAttributeKey) + .firstNameAttributeKey(firstNameAttributeKey) + .lastNameAttributeKey(lastNameAttributeKey) + .tenantNameStrategy(tenantNameStrategy) + .tenantNamePattern(tenantNamePattern) + .customerNamePattern(customerNamePattern) + .defaultDashboardName(defaultDashboardName) + .alwaysFullScreen(alwaysFullScreen) + .build() + ); + clientRegistrationTemplate.setAuthorizationUri(authorizationUri); + clientRegistrationTemplate.setAccessTokenUri(tokenUri); + clientRegistrationTemplate.setScope(Arrays.asList(scope.split(","))); + clientRegistrationTemplate.setUserInfoUri(userInfoUri); + clientRegistrationTemplate.setUserNameAttributeName(userNameAttributeName); + clientRegistrationTemplate.setJwkSetUri(jwkSetUri); + clientRegistrationTemplate.setClientAuthenticationMethod(clientAuthenticationMethod); + return clientRegistrationTemplate; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationTemplateDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationTemplateDao.java new file mode 100644 index 0000000000..75fcbf1b10 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationTemplateDao.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.oauth2; + +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; +import org.thingsboard.server.dao.Dao; + +import java.util.List; + +public interface OAuth2ClientRegistrationTemplateDao extends Dao { + List findAll(); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java new file mode 100644 index 0000000000..afe4d845e6 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java @@ -0,0 +1,107 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.oauth2; + +import lombok.extern.slf4j.Slf4j; +import org.hibernate.exception.ConstraintViolationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.oauth2.*; +import org.thingsboard.server.dao.entity.AbstractEntityService; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; + +import java.util.List; + +import static org.thingsboard.server.dao.service.Validator.validateId; +import static org.thingsboard.server.dao.service.Validator.validateString; + +@Slf4j +@Service +public class OAuth2ConfigTemplateServiceImpl extends AbstractEntityService implements OAuth2ConfigTemplateService { + public static final String INCORRECT_CLIENT_REGISTRATION_TEMPLATE_ID = "Incorrect clientRegistrationTemplateId "; + + @Autowired + private OAuth2ClientRegistrationTemplateDao clientRegistrationTemplateDao; + + @Override + public OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { + log.trace("Executing saveClientRegistrationTemplate [{}]", clientRegistrationTemplate); + clientRegistrationTemplateValidator.validate(clientRegistrationTemplate, OAuth2ClientRegistrationTemplate::getTenantId); + OAuth2ClientRegistrationTemplate savedClientRegistrationTemplate; + try { + savedClientRegistrationTemplate = clientRegistrationTemplateDao.save(clientRegistrationTemplate.getTenantId(), clientRegistrationTemplate); + } catch (Exception t) { + ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); + if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("oauth2_template_provider_id_unq_key")) { + throw new DataValidationException("Client registration template with such providerId already exists!"); + } else { + throw t; + } + } + return savedClientRegistrationTemplate; + } + + @Override + public OAuth2ClientRegistrationTemplate findClientRegistrationTemplateById(OAuth2ClientRegistrationTemplateId templateId) { + log.trace("Executing findClientRegistrationTemplateById [{}]", templateId); + validateId(templateId, INCORRECT_CLIENT_REGISTRATION_TEMPLATE_ID + templateId); + return clientRegistrationTemplateDao.findById(TenantId.SYS_TENANT_ID, templateId.getId()); + } + + @Override + public List findAllClientRegistrationTemplates() { + log.trace("Executing findAllClientRegistrationTemplates"); + return clientRegistrationTemplateDao.findAll(); + } + + @Override + public void deleteClientRegistrationTemplateById(OAuth2ClientRegistrationTemplateId templateId) { + log.trace("Executing deleteClientRegistrationTemplateById [{}]", templateId); + validateId(templateId, INCORRECT_CLIENT_REGISTRATION_TEMPLATE_ID + templateId); + clientRegistrationTemplateDao.removeById(TenantId.SYS_TENANT_ID, templateId.getId()); + } + + private DataValidator clientRegistrationTemplateValidator = + new DataValidator() { + + @Override + protected void validateCreate(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { + } + + @Override + protected void validateUpdate(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { + } + + @Override + protected void validateDataImpl(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { + if (StringUtils.isEmpty(clientRegistrationTemplate.getProviderId())) { + throw new DataValidationException("Provider ID should be specified!"); + } + if (clientRegistrationTemplate.getBasic() == null) { + throw new DataValidationException("Basic mapper config should be specified!"); + } + if (clientRegistrationTemplate.getTenantId() == null + || !TenantId.SYS_TENANT_ID.equals(clientRegistrationTemplate.getTenantId())) { + throw new DataValidationException("Client registration template should be assigned to system admin!"); + } + } + }; +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationTemplateDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationTemplateDao.java new file mode 100644 index 0000000000..70c3c03479 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationTemplateDao.java @@ -0,0 +1,53 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.oauth2; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationTemplateEntity; +import org.thingsboard.server.dao.oauth2.OAuth2ClientRegistrationTemplateDao; +import org.thingsboard.server.dao.sql.JpaAbstractDao; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Component +@RequiredArgsConstructor +public class JpaOAuth2ClientRegistrationTemplateDao extends JpaAbstractDao implements OAuth2ClientRegistrationTemplateDao { + private final OAuth2ClientRegistrationTemplateRepository repository; + + @Override + protected Class getEntityClass() { + return OAuth2ClientRegistrationTemplateEntity.class; + } + + @Override + protected CrudRepository getCrudRepository() { + return repository; + } + + @Override + public List findAll() { + Iterable entities = repository.findAll(); + List result = new ArrayList<>(); + entities.forEach(entity -> result.add(DaoUtil.getData(entity))); + return result; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationTemplateRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationTemplateRepository.java new file mode 100644 index 0000000000..8ffd380496 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationTemplateRepository.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.oauth2; + +import org.springframework.data.repository.CrudRepository; +import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationTemplateEntity; + +import java.util.UUID; + +public interface OAuth2ClientRegistrationTemplateRepository extends CrudRepository { +} diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index 711ab8037e..cb05bf800c 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -324,3 +324,26 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( custom_username varchar(255), custom_password varchar(255) ); + +CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY, + created_time bigint NOT NULL, + tenant_id uuid, + provider_id varchar(255), + authorization_uri varchar(255), + token_uri varchar(255), + scope varchar(255), + user_info_uri varchar(255), + user_name_attribute_name varchar(255), + jwk_set_uri varchar(255), + client_authentication_method varchar(255), + basic_email_attribute_key varchar(31), + basic_first_name_attribute_key varchar(31), + basic_last_name_attribute_key varchar(31), + basic_tenant_name_strategy varchar(31), + basic_tenant_name_pattern varchar(255), + basic_customer_name_pattern varchar(255), + basic_default_dashboard_name varchar(255), + basic_always_full_screen boolean, + CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) +); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 84bfaea563..65d1138b99 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -340,6 +340,29 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( custom_password varchar(255) ); +CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY, + created_time bigint NOT NULL, + tenant_id uuid, + provider_id varchar(255), + authorization_uri varchar(255), + token_uri varchar(255), + scope varchar(255), + user_info_uri varchar(255), + user_name_attribute_name varchar(255), + jwk_set_uri varchar(255), + client_authentication_method varchar(255), + basic_email_attribute_key varchar(31), + basic_first_name_attribute_key varchar(31), + basic_last_name_attribute_key varchar(31), + basic_tenant_name_strategy varchar(31), + basic_tenant_name_pattern varchar(255), + basic_customer_name_pattern varchar(255), + basic_default_dashboard_name varchar(255), + basic_always_full_screen boolean, + CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) +); + CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint) LANGUAGE plpgsql AS $$ diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java new file mode 100644 index 0000000000..824e992c21 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java @@ -0,0 +1,129 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.service; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.oauth2.OAuth2BasicMapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; + +import java.util.Arrays; +import java.util.UUID; + +public class BaseOAuth2ConfigTemplateServiceTest extends AbstractServiceTest { + + @Autowired + protected OAuth2ConfigTemplateService oAuth2ConfigTemplateService; + + private TenantId tenantId; + + @Before + public void beforeRun() throws Exception { + Assert.assertTrue(oAuth2ConfigTemplateService.findAllClientRegistrationTemplates().isEmpty()); + } + + @After + public void after() throws Exception { + oAuth2ConfigTemplateService.findAllClientRegistrationTemplates().forEach(clientRegistrationTemplate -> { + oAuth2ConfigTemplateService.deleteClientRegistrationTemplateById(clientRegistrationTemplate.getId()); + }); + + Assert.assertTrue(oAuth2ConfigTemplateService.findAllClientRegistrationTemplates().isEmpty()); + } + + + @Test(expected = DataValidationException.class) + public void testSaveDuplicateProviderId() { + OAuth2ClientRegistrationTemplate first = validClientRegistrationTemplate(TenantId.SYS_TENANT_ID, "providerId"); + OAuth2ClientRegistrationTemplate second = validClientRegistrationTemplate(TenantId.SYS_TENANT_ID, "providerId"); + oAuth2ConfigTemplateService.saveClientRegistrationTemplate(first); + oAuth2ConfigTemplateService.saveClientRegistrationTemplate(second); + } + + @Test + public void testCreateNewTemplate() { + OAuth2ClientRegistrationTemplate clientRegistrationTemplate = validClientRegistrationTemplate(TenantId.SYS_TENANT_ID, UUID.randomUUID().toString()); + OAuth2ClientRegistrationTemplate savedClientRegistrationTemplate = oAuth2ConfigTemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate); + + Assert.assertNotNull(savedClientRegistrationTemplate); + Assert.assertNotNull(savedClientRegistrationTemplate.getId()); + clientRegistrationTemplate.setId(savedClientRegistrationTemplate.getId()); + clientRegistrationTemplate.setCreatedTime(savedClientRegistrationTemplate.getCreatedTime()); + Assert.assertEquals(clientRegistrationTemplate, savedClientRegistrationTemplate); + } + + @Test + public void testFindTemplate() { + OAuth2ClientRegistrationTemplate clientRegistrationTemplate = validClientRegistrationTemplate(TenantId.SYS_TENANT_ID, UUID.randomUUID().toString()); + OAuth2ClientRegistrationTemplate savedClientRegistrationTemplate = oAuth2ConfigTemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate); + + OAuth2ClientRegistrationTemplate foundClientRegistrationTemplate = oAuth2ConfigTemplateService.findClientRegistrationTemplateById(savedClientRegistrationTemplate.getId()); + Assert.assertEquals(savedClientRegistrationTemplate, foundClientRegistrationTemplate); + } + + @Test + public void testFindAll() { + oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(TenantId.SYS_TENANT_ID, UUID.randomUUID().toString())); + oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(TenantId.SYS_TENANT_ID, UUID.randomUUID().toString())); + + Assert.assertEquals(2, oAuth2ConfigTemplateService.findAllClientRegistrationTemplates().size()); + } + + @Test + public void testDeleteTemplate() { + oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(TenantId.SYS_TENANT_ID, UUID.randomUUID().toString())); + oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(TenantId.SYS_TENANT_ID, UUID.randomUUID().toString())); + OAuth2ClientRegistrationTemplate saved = oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(TenantId.SYS_TENANT_ID, UUID.randomUUID().toString())); + + Assert.assertEquals(3, oAuth2ConfigTemplateService.findAllClientRegistrationTemplates().size()); + Assert.assertNotNull(oAuth2ConfigTemplateService.findClientRegistrationTemplateById(saved.getId())); + + oAuth2ConfigTemplateService.deleteClientRegistrationTemplateById(saved.getId()); + + Assert.assertEquals(2, oAuth2ConfigTemplateService.findAllClientRegistrationTemplates().size()); + Assert.assertNull(oAuth2ConfigTemplateService.findClientRegistrationTemplateById(saved.getId())); + } + + private OAuth2ClientRegistrationTemplate validClientRegistrationTemplate(TenantId tenantId, String providerId) { + OAuth2ClientRegistrationTemplate clientRegistrationTemplate = new OAuth2ClientRegistrationTemplate(); + clientRegistrationTemplate.setProviderId(providerId); + clientRegistrationTemplate.setTenantId(tenantId); + clientRegistrationTemplate.setBasic( + OAuth2BasicMapperConfig.builder() + .firstNameAttributeKey("firstName") + .lastNameAttributeKey("lastName") + .emailAttributeKey("email") + .tenantNamePattern("tenant") + .defaultDashboardName("Test") + .alwaysFullScreen(true) + .build() + ); + clientRegistrationTemplate.setAuthorizationUri("authorizationUri"); + clientRegistrationTemplate.setAccessTokenUri("tokenUri"); + clientRegistrationTemplate.setScope(Arrays.asList("scope1", "scope2")); + clientRegistrationTemplate.setUserInfoUri("userInfoUri"); + clientRegistrationTemplate.setUserNameAttributeName("userNameAttributeName"); + clientRegistrationTemplate.setJwkSetUri("jwkSetUri"); + clientRegistrationTemplate.setClientAuthenticationMethod("clientAuthenticationMethod"); + return clientRegistrationTemplate; + } +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/sql/OAuth2ConfigTemplateServiceSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/sql/OAuth2ConfigTemplateServiceSqlTest.java new file mode 100644 index 0000000000..53fd2e37b4 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/service/sql/OAuth2ConfigTemplateServiceSqlTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.service.sql; + +import org.thingsboard.server.dao.service.BaseOAuth2ConfigTemplateServiceTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class OAuth2ConfigTemplateServiceSqlTest extends BaseOAuth2ConfigTemplateServiceTest { +} diff --git a/dao/src/test/resources/sql/hsql/drop-all-tables.sql b/dao/src/test/resources/sql/hsql/drop-all-tables.sql index e55c9bfcd5..432814cb04 100644 --- a/dao/src/test/resources/sql/hsql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/hsql/drop-all-tables.sql @@ -21,4 +21,5 @@ DROP TABLE IF EXISTS rule_node; DROP TABLE IF EXISTS rule_chain; DROP TABLE IF EXISTS entity_view; DROP TABLE IF EXISTS oauth2_client_registration; +DROP TABLE IF EXISTS oauth2_client_registration_template; DROP FUNCTION IF EXISTS to_uuid; diff --git a/dao/src/test/resources/sql/psql/drop-all-tables.sql b/dao/src/test/resources/sql/psql/drop-all-tables.sql index 259a3e9fed..f7ce197023 100644 --- a/dao/src/test/resources/sql/psql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/psql/drop-all-tables.sql @@ -22,4 +22,5 @@ DROP TABLE IF EXISTS rule_node; DROP TABLE IF EXISTS rule_chain; DROP TABLE IF EXISTS entity_view; DROP TABLE IF EXISTS oauth2_client_registration; +DROP TABLE IF EXISTS oauth2_client_registration_template; DROP TABLE IF EXISTS tb_schema_settings; \ No newline at end of file diff --git a/dao/src/test/resources/sql/timescale/drop-all-tables.sql b/dao/src/test/resources/sql/timescale/drop-all-tables.sql index 259a3e9fed..f7ce197023 100644 --- a/dao/src/test/resources/sql/timescale/drop-all-tables.sql +++ b/dao/src/test/resources/sql/timescale/drop-all-tables.sql @@ -22,4 +22,5 @@ DROP TABLE IF EXISTS rule_node; DROP TABLE IF EXISTS rule_chain; DROP TABLE IF EXISTS entity_view; DROP TABLE IF EXISTS oauth2_client_registration; +DROP TABLE IF EXISTS oauth2_client_registration_template; DROP TABLE IF EXISTS tb_schema_settings; \ No newline at end of file From da9f29c9333d859bda350fed7a3949ed1e1bfcb6 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 13 Aug 2020 11:48:14 +0300 Subject: [PATCH 077/463] Added 'comment' to ClientRegistrationTemplate --- application/src/main/data/upgrade/3.1.0/schema_update.sql | 1 + .../common/data/oauth2/OAuth2ClientRegistrationTemplate.java | 2 ++ .../java/org/thingsboard/server/dao/model/ModelConstants.java | 1 + .../dao/model/sql/OAuth2ClientRegistrationTemplateEntity.java | 4 ++++ dao/src/main/resources/sql/schema-entities-hsql.sql | 1 + dao/src/main/resources/sql/schema-entities.sql | 1 + .../dao/service/BaseOAuth2ConfigTemplateServiceTest.java | 1 + 7 files changed, 11 insertions(+) diff --git a/application/src/main/data/upgrade/3.1.0/schema_update.sql b/application/src/main/data/upgrade/3.1.0/schema_update.sql index a0eacbacbd..445df2b97e 100644 --- a/application/src/main/data/upgrade/3.1.0/schema_update.sql +++ b/application/src/main/data/upgrade/3.1.0/schema_update.sql @@ -72,5 +72,6 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( basic_customer_name_pattern varchar(255), basic_default_dashboard_name varchar(255), basic_always_full_screen boolean, + comment varchar, CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) ); \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java index 4e0d0c0e65..f3cb077e78 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java @@ -43,6 +43,7 @@ public class OAuth2ClientRegistrationTemplate extends BaseData Date: Thu, 13 Aug 2020 14:14:38 +0300 Subject: [PATCH 078/463] Added logic to send 'access token' to custom mapper --- .../main/data/upgrade/3.1.0/schema_update.sql | 3 ++- .../auth/oauth2/BasicOAuth2ClientMapper.java | 2 +- .../auth/oauth2/CustomOAuth2ClientMapper.java | 11 ++++++++--- .../security/auth/oauth2/OAuth2ClientMapper.java | 2 +- .../Oauth2AuthenticationSuccessHandler.java | 16 +++++++++++----- .../data/oauth2/OAuth2CustomMapperConfig.java | 1 + .../server/dao/model/ModelConstants.java | 1 + .../sql/OAuth2ClientRegistrationEntity.java | 4 ++++ .../server/dao/oauth2/OAuth2ServiceImpl.java | 3 --- .../main/resources/sql/schema-entities-hsql.sql | 3 ++- dao/src/main/resources/sql/schema-entities.sql | 3 ++- 11 files changed, 33 insertions(+), 16 deletions(-) diff --git a/application/src/main/data/upgrade/3.1.0/schema_update.sql b/application/src/main/data/upgrade/3.1.0/schema_update.sql index 445df2b97e..639fb09432 100644 --- a/application/src/main/data/upgrade/3.1.0/schema_update.sql +++ b/application/src/main/data/upgrade/3.1.0/schema_update.sql @@ -47,7 +47,8 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( basic_always_full_screen boolean, custom_url varchar(255), custom_username varchar(255), - custom_password varchar(255) + custom_password varchar(255), + custom_send_token boolean ); DROP TABLE IF EXISTS oauth2_client_registration_template; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java index 8c7c4d9bb4..5bee8e866e 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java @@ -35,7 +35,7 @@ public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implemen private static final String END_PLACEHOLDER_PREFIX = "}"; @Override - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, TenantId parentTenantId, OAuth2MapperConfig config) { + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, TenantId parentTenantId, OAuth2MapperConfig config) { OAuth2User oauth2User = new OAuth2User(); Map attributes = token.getPrincipal().getAttributes(); String email = getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java index 0dce6f1e98..21c60b943b 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java @@ -32,21 +32,26 @@ import org.thingsboard.server.service.security.model.SecurityUser; @Service(value = "customOAuth2ClientMapper") @Slf4j public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { + private static final String PROVIDER_ACCESS_TOKEN = "provider-access-token"; private static final ObjectMapper json = new ObjectMapper(); private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); @Override - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, TenantId parentTenantId, OAuth2MapperConfig config) { - OAuth2User oauth2User = getOAuth2User(token, config.getCustom()); + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, TenantId parentTenantId, OAuth2MapperConfig config) { + OAuth2User oauth2User = getOAuth2User(token, providerAccessToken, config.getCustom()); return getOrCreateSecurityUserFromOAuth2User(parentTenantId, oauth2User, config.isAllowUserCreation(), config.isActivateUser()); } - private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, OAuth2CustomMapperConfig custom) { + private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2CustomMapperConfig custom) { if (!StringUtils.isEmpty(custom.getUsername()) && !StringUtils.isEmpty(custom.getPassword())) { restTemplateBuilder = restTemplateBuilder.basicAuthentication(custom.getUsername(), custom.getPassword()); } + if (custom.isSendToken() && !StringUtils.isEmpty(providerAccessToken)) { + restTemplateBuilder = restTemplateBuilder.defaultHeader(PROVIDER_ACCESS_TOKEN, providerAccessToken); + } + RestTemplate restTemplate = restTemplateBuilder.build(); String request; try { diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java index 6490322b0b..cdd0313f51 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java @@ -21,5 +21,5 @@ import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.service.security.model.SecurityUser; public interface OAuth2ClientMapper { - SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, TenantId parentTenantId, OAuth2MapperConfig config); + SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, TenantId parentTenantId, OAuth2MapperConfig config); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index 9274e08939..7c0097fbfb 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -15,14 +15,13 @@ */ package org.thingsboard.server.service.security.auth.oauth2; -import org.apache.commons.lang3.tuple.Pair; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; @@ -45,16 +44,19 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS private final RefreshTokenRepository refreshTokenRepository; private final OAuth2ClientMapperProvider oauth2ClientMapperProvider; private final OAuth2Service oAuth2Service; + private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService; @Autowired public Oauth2AuthenticationSuccessHandler(final JwtTokenFactory tokenFactory, final RefreshTokenRepository refreshTokenRepository, final OAuth2ClientMapperProvider oauth2ClientMapperProvider, - final OAuth2Service oAuth2Service) { + final OAuth2Service oAuth2Service, + final OAuth2AuthorizedClientService oAuth2AuthorizedClientService) { this.tokenFactory = tokenFactory; this.refreshTokenRepository = refreshTokenRepository; this.oauth2ClientMapperProvider = oauth2ClientMapperProvider; this.oAuth2Service = oAuth2Service; + this.oAuth2AuthorizedClientService = oAuth2AuthorizedClientService; } @Override @@ -67,8 +69,12 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; OAuth2ClientRegistration clientRegistration = oAuth2Service.findClientRegistration(UUID.fromString(token.getAuthorizedClientRegistrationId())); + OAuth2AuthorizedClient oAuth2AuthorizedClient = oAuth2AuthorizedClientService.loadAuthorizedClient( + token.getAuthorizedClientRegistrationId(), + token.getPrincipal().getName()); OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(clientRegistration.getMapperConfig().getType()); - SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, clientRegistration.getTenantId(), clientRegistration.getMapperConfig()); + SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(), + clientRegistration.getTenantId(), clientRegistration.getMapperConfig()); JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java index 8ac72e9d8a..cead19c39c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2CustomMapperConfig.java @@ -25,4 +25,5 @@ public class OAuth2CustomMapperConfig { private final String url; private final String username; private final String password; + private final boolean sendToken; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 8042d13927..1bd80240bc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -388,6 +388,7 @@ public class ModelConstants { public static final String OAUTH2_MAPPER_URL_PROPERTY = "custom_url"; public static final String OAUTH2_MAPPER_USERNAME_PROPERTY = "custom_username"; public static final String OAUTH2_MAPPER_PASSWORD_PROPERTY = "custom_password"; + public static final String OAUTH2_MAPPER_SEND_TOKEN_PROPERTY = "custom_send_token"; public static final String OAUTH2_TEMPLATE_COMMENT_PROPERTY = "comment"; /** diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java index db05b02a05..4e99dbbb5f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java @@ -95,6 +95,8 @@ public class OAuth2ClientRegistrationEntity extends BaseSqlEntity Date: Thu, 13 Aug 2020 18:21:26 +0300 Subject: [PATCH 079/463] Added new fields to ClientRegistrationTemplate --- .../main/data/upgrade/3.1.0/schema_update.sql | 3 ++ .../OAuth2ClientRegistrationTemplate.java | 29 ++++++++++++++++--- .../server/dao/model/ModelConstants.java | 3 ++ ...Auth2ClientRegistrationTemplateEntity.java | 16 ++++++++++ .../resources/sql/schema-entities-hsql.sql | 3 ++ .../main/resources/sql/schema-entities.sql | 3 ++ .../BaseOAuth2ConfigTemplateServiceTest.java | 2 ++ 7 files changed, 55 insertions(+), 4 deletions(-) diff --git a/application/src/main/data/upgrade/3.1.0/schema_update.sql b/application/src/main/data/upgrade/3.1.0/schema_update.sql index 639fb09432..2f5f3ca41a 100644 --- a/application/src/main/data/upgrade/3.1.0/schema_update.sql +++ b/application/src/main/data/upgrade/3.1.0/schema_update.sql @@ -56,6 +56,7 @@ DROP TABLE IF EXISTS oauth2_client_registration_template; CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY, created_time bigint NOT NULL, + additional_info varchar, tenant_id uuid, provider_id varchar(255), authorization_uri varchar(255), @@ -74,5 +75,7 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( basic_default_dashboard_name varchar(255), basic_always_full_screen boolean, comment varchar, + icon varchar(255), + help_link varchar(255), CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) ); \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java index f3cb077e78..7f16940e7f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java @@ -15,13 +15,13 @@ */ package org.thingsboard.server.common.data.oauth2; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import lombok.ToString; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.*; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; import org.thingsboard.server.common.data.id.TenantId; @@ -44,6 +44,16 @@ public class OAuth2ClientRegistrationTemplate extends BaseData additionalInfo, () -> additionalInfoBytes); + } + + public void setAdditionalInfo(JsonNode addInfo) { + SearchTextBasedWithAdditionalInfo.setJson(addInfo, json -> this.additionalInfo = json, bytes -> this.additionalInfoBytes = bytes); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 1bd80240bc..e1b1907ae1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -390,6 +390,9 @@ public class ModelConstants { public static final String OAUTH2_MAPPER_PASSWORD_PROPERTY = "custom_password"; public static final String OAUTH2_MAPPER_SEND_TOKEN_PROPERTY = "custom_send_token"; public static final String OAUTH2_TEMPLATE_COMMENT_PROPERTY = "comment"; + public static final String OAUTH2_TEMPLATE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY; + public static final String OAUTH2_TEMPLATE_ICON_PROPERTY = "icon"; + public static final String OAUTH2_TEMPLATE_HELP_LINK_PROPERTY = "help_link"; /** * Cassandra attributes and timeseries constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationTemplateEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationTemplateEntity.java index 1c32efd625..40d1a02552 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationTemplateEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationTemplateEntity.java @@ -15,8 +15,10 @@ */ package org.thingsboard.server.dao.model.sql; +import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; @@ -76,6 +78,14 @@ public class OAuth2ClientRegistrationTemplateEntity extends BaseSqlEntity Date: Fri, 14 Aug 2020 13:13:28 +0300 Subject: [PATCH 080/463] Load 'github' and 'google' templates on install --- .../github_config.json | 14 +++++++++++ .../google_config.json | 20 +++++++++++++++ .../install/ThingsboardInstallService.java | 3 +++ .../DefaultSystemDataLoaderService.java | 5 ++++ .../service/install/InstallScripts.java | 25 +++++++++++++++++++ .../install/SystemDataLoaderService.java | 2 ++ 6 files changed, 69 insertions(+) create mode 100644 application/src/main/data/json/system/oauth2_config_templates/github_config.json create mode 100644 application/src/main/data/json/system/oauth2_config_templates/google_config.json diff --git a/application/src/main/data/json/system/oauth2_config_templates/github_config.json b/application/src/main/data/json/system/oauth2_config_templates/github_config.json new file mode 100644 index 0000000000..17dc1fda4a --- /dev/null +++ b/application/src/main/data/json/system/oauth2_config_templates/github_config.json @@ -0,0 +1,14 @@ +{ + "providerId": "Github", + "accessTokenUri": "https://github.com/login/oauth/access_token", + "authorizationUri": "https://github.com/login/oauth/authorize", + "scope": ["read:user","user:email"], + "jwkSetUri": null, + "userInfoUri": "https://api.github.com/user", + "clientAuthenticationMethod": "basic", + "userNameAttributeName": "login", + "basic": {}, + "comment": "In order to log into ThingsBoard you need to have user's email. You may configure and use Custom OAuth2 Mapper to get email information. Please refer to Github Documentation", + "icon": "mdi:github", + "helpLink": "https://docs.github.com/en/developers/apps/creating-an-oauth-app" +} \ No newline at end of file diff --git a/application/src/main/data/json/system/oauth2_config_templates/google_config.json b/application/src/main/data/json/system/oauth2_config_templates/google_config.json new file mode 100644 index 0000000000..99053b68e6 --- /dev/null +++ b/application/src/main/data/json/system/oauth2_config_templates/google_config.json @@ -0,0 +1,20 @@ +{ + "providerId": "Google", + "additionalInfo": null, + "accessTokenUri": "https://oauth2.googleapis.com/token", + "authorizationUri": "https://accounts.google.com/o/oauth2/v2/auth", + "scope": ["email","openid","profile"], + "jwkSetUri": "https://www.googleapis.com/oauth2/v3/certs", + "userInfoUri": "https://openidconnect.googleapis.com/v1/userinfo", + "clientAuthenticationMethod": "basic", + "userNameAttributeName": "email", + "basic": { + "emailAttributeKey": "email", + "firstNameAttributeKey": "given_name", + "lastNameAttributeKey": "family_name", + "tenantNameStrategy": "DOMAIN" + }, + "comment": null, + "icon": "mdi:google", + "helpLink": "https://developers.google.com/adwords/api/docs/guides/authentication" +} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 1b1355571d..7059e0fd63 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -176,6 +176,8 @@ public class ThingsboardInstallService { case "3.1.0": log.info("Upgrading ThingsBoard from version 3.1.0 to 3.2.0 ..."); databaseEntitiesUpgradeService.upgradeDatabase("3.1.0"); + log.info("Updating system data..."); + systemDataLoaderService.createOAuth2Templates(); break; default: throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion); @@ -207,6 +209,7 @@ public class ThingsboardInstallService { systemDataLoaderService.createSysAdmin(); systemDataLoaderService.createAdminSettings(); systemDataLoaderService.loadSystemWidgets(); + systemDataLoaderService.createOAuth2Templates(); // systemDataLoaderService.loadSystemPlugins(); // systemDataLoaderService.loadSystemRules(); diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 6aa3e0cd2d..dcb5ffccde 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -136,6 +136,11 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, mailSettings); } + @Override + public void createOAuth2Templates() throws Exception { + installScripts.createOAuth2Templates(); + } + @Override public void loadDemoData() throws Exception { Tenant demoTenant = new Tenant(); diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index 5181834b80..1b5827b6f7 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -26,11 +26,13 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.dashboard.DashboardService; +import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; @@ -60,6 +62,7 @@ public class InstallScripts { public static final String DEMO_DIR = "demo"; public static final String RULE_CHAINS_DIR = "rule_chains"; public static final String WIDGET_BUNDLES_DIR = "widget_bundles"; + public static final String OAUTH2_CONFIG_TEMPLATES_DIR = "oauth2_config_templates"; public static final String DASHBOARDS_DIR = "dashboards"; public static final String JSON_EXT = ".json"; @@ -79,6 +82,9 @@ public class InstallScripts { @Autowired private WidgetsBundleService widgetsBundleService; + @Autowired + private OAuth2ConfigTemplateService oAuth2TemplateService; + public Path getTenantRuleChainsDir() { return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR); } @@ -209,4 +215,23 @@ public class InstallScripts { throw new RuntimeException("Unable to load dashboard from json", e); } } + + public void createOAuth2Templates() throws Exception { + Path oauth2ConfigTemplatesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, OAUTH2_CONFIG_TEMPLATES_DIR); + try (DirectoryStream dirStream = Files.newDirectoryStream(oauth2ConfigTemplatesDir, path -> path.toString().endsWith(JSON_EXT))) { + dirStream.forEach( + path -> { + try { + JsonNode oauth2ConfigTemplateJson = objectMapper.readTree(path.toFile()); + OAuth2ClientRegistrationTemplate clientRegistrationTemplate = objectMapper.treeToValue(oauth2ConfigTemplateJson, OAuth2ClientRegistrationTemplate.class); + clientRegistrationTemplate.setTenantId(TenantId.SYS_TENANT_ID); + oAuth2TemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate); + } catch (Exception e) { + log.error("Unable to load oauth2 config templates from json: [{}]", path.toString()); + throw new RuntimeException("Unable to load oauth2 config templates from json", e); + } + } + ); + } + } } diff --git a/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java index 76e65deaa4..4742d30277 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java @@ -21,6 +21,8 @@ public interface SystemDataLoaderService { void createAdminSettings() throws Exception; + void createOAuth2Templates() throws Exception; + void loadSystemWidgets() throws Exception; void updateSystemWidgets() throws Exception; From 3109e416709b737c336b07edca12c58f19e94cf8 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Fri, 14 Aug 2020 14:26:04 +0300 Subject: [PATCH 081/463] Added 'facebook' template --- .../facebook_config.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 application/src/main/data/json/system/oauth2_config_templates/facebook_config.json diff --git a/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json b/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json new file mode 100644 index 0000000000..1c81a443f3 --- /dev/null +++ b/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json @@ -0,0 +1,19 @@ +{ + "providerId": "Facebook", + "accessTokenUri": "https://graph.facebook.com/v2.8/oauth/access_token", + "authorizationUri": "https://www.facebook.com/v2.8/dialog/oauth", + "scope": ["email","public_profile"], + "jwkSetUri": null, + "userInfoUri": "https://graph.facebook.com/me?fields=id,name,first_name,last_name,email", + "clientAuthenticationMethod": "basic", + "userNameAttributeName": "email", + "basic": { + "emailAttributeKey": "email", + "firstNameAttributeKey": "first_name", + "lastNameAttributeKey": "last_name", + "tenantNameStrategy": "DOMAIN" + }, + "comment": null, + "icon": "mdi:facebook", + "helpLink": "https://developers.facebook.com/docs/facebook-login/web#logindialog" +} \ No newline at end of file From 2cd5b646e3acfb031ff25de0b58d75a72a3ca857 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Mon, 31 Aug 2020 10:43:15 +0300 Subject: [PATCH 082/463] Fixed merge --- .../main/data/upgrade/3.1.0/schema_update.sql | 66 --------------- .../main/data/upgrade/3.1.1/schema_update.sql | 81 +++++++++++++++++++ .../install/ThingsboardInstallService.java | 6 +- .../install/SqlDatabaseUpgradeService.java | 11 +-- .../dao/settings/AdminSettingsService.java | 2 - .../settings/AdminSettingsServiceImpl.java | 9 --- 6 files changed, 90 insertions(+), 85 deletions(-) create mode 100644 application/src/main/data/upgrade/3.1.1/schema_update.sql diff --git a/application/src/main/data/upgrade/3.1.0/schema_update.sql b/application/src/main/data/upgrade/3.1.0/schema_update.sql index ef313a6000..e3b2cfb9a4 100644 --- a/application/src/main/data/upgrade/3.1.0/schema_update.sql +++ b/application/src/main/data/upgrade/3.1.0/schema_update.sql @@ -14,70 +14,4 @@ -- limitations under the License. -- -DROP TABLE IF EXISTS oauth2_client_registration; - -CREATE TABLE IF NOT EXISTS oauth2_client_registration ( - id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, - created_time bigint NOT NULL, - additional_info varchar, - tenant_id uuid, - domain_name varchar(255), - client_id varchar(255), - client_secret varchar(255), - authorization_uri varchar(255), - token_uri varchar(255), - redirect_uri_template varchar(255), - scope varchar(255), - user_info_uri varchar(255), - user_name_attribute_name varchar(255), - jwk_set_uri varchar(255), - client_authentication_method varchar(255), - login_button_label varchar(255), - login_button_icon varchar(255), - allow_user_creation boolean, - activate_user boolean, - type varchar(31), - basic_email_attribute_key varchar(31), - basic_first_name_attribute_key varchar(31), - basic_last_name_attribute_key varchar(31), - basic_tenant_name_strategy varchar(31), - basic_tenant_name_pattern varchar(255), - basic_customer_name_pattern varchar(255), - basic_default_dashboard_name varchar(255), - basic_always_full_screen boolean, - custom_url varchar(255), - custom_username varchar(255), - custom_password varchar(255), - custom_send_token boolean -); - -DROP TABLE IF EXISTS oauth2_client_registration_template; - -CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( - id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY, - created_time bigint NOT NULL, - additional_info varchar, - tenant_id uuid, - provider_id varchar(255), - authorization_uri varchar(255), - token_uri varchar(255), - scope varchar(255), - user_info_uri varchar(255), - user_name_attribute_name varchar(255), - jwk_set_uri varchar(255), - client_authentication_method varchar(255), - basic_email_attribute_key varchar(31), - basic_first_name_attribute_key varchar(31), - basic_last_name_attribute_key varchar(31), - basic_tenant_name_strategy varchar(31), - basic_tenant_name_pattern varchar(255), - basic_customer_name_pattern varchar(255), - basic_default_dashboard_name varchar(255), - basic_always_full_screen boolean, - comment varchar, - icon varchar(255), - help_link varchar(255), - CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) -); - CREATE INDEX IF NOT EXISTS idx_alarm_tenant_alarm_type_created_time ON alarm(tenant_id, type, created_time DESC); diff --git a/application/src/main/data/upgrade/3.1.1/schema_update.sql b/application/src/main/data/upgrade/3.1.1/schema_update.sql new file mode 100644 index 0000000000..58c4ff5b67 --- /dev/null +++ b/application/src/main/data/upgrade/3.1.1/schema_update.sql @@ -0,0 +1,81 @@ +-- +-- Copyright © 2016-2020 The Thingsboard Authors +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +DROP TABLE IF EXISTS oauth2_client_registration; + +CREATE TABLE IF NOT EXISTS oauth2_client_registration ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + created_time bigint NOT NULL, + additional_info varchar, + tenant_id uuid, + domain_name varchar(255), + client_id varchar(255), + client_secret varchar(255), + authorization_uri varchar(255), + token_uri varchar(255), + redirect_uri_template varchar(255), + scope varchar(255), + user_info_uri varchar(255), + user_name_attribute_name varchar(255), + jwk_set_uri varchar(255), + client_authentication_method varchar(255), + login_button_label varchar(255), + login_button_icon varchar(255), + allow_user_creation boolean, + activate_user boolean, + type varchar(31), + basic_email_attribute_key varchar(31), + basic_first_name_attribute_key varchar(31), + basic_last_name_attribute_key varchar(31), + basic_tenant_name_strategy varchar(31), + basic_tenant_name_pattern varchar(255), + basic_customer_name_pattern varchar(255), + basic_default_dashboard_name varchar(255), + basic_always_full_screen boolean, + custom_url varchar(255), + custom_username varchar(255), + custom_password varchar(255), + custom_send_token boolean +); + +DROP TABLE IF EXISTS oauth2_client_registration_template; + +CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY, + created_time bigint NOT NULL, + additional_info varchar, + tenant_id uuid, + provider_id varchar(255), + authorization_uri varchar(255), + token_uri varchar(255), + scope varchar(255), + user_info_uri varchar(255), + user_name_attribute_name varchar(255), + jwk_set_uri varchar(255), + client_authentication_method varchar(255), + basic_email_attribute_key varchar(31), + basic_first_name_attribute_key varchar(31), + basic_last_name_attribute_key varchar(31), + basic_tenant_name_strategy varchar(31), + basic_tenant_name_pattern varchar(255), + basic_customer_name_pattern varchar(255), + basic_default_dashboard_name varchar(255), + basic_always_full_screen boolean, + comment varchar, + icon varchar(255), + help_link varchar(255), + CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) +); diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 4d90360d6d..205602fca3 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -177,9 +177,9 @@ public class ThingsboardInstallService { databaseEntitiesUpgradeService.upgradeDatabase("3.1.0"); log.info("Updating system data..."); systemDataLoaderService.updateSystemWidgets(); - case "3.1.0": - log.info("Upgrading ThingsBoard from version 3.1.0 to 3.2.0 ..."); - databaseEntitiesUpgradeService.upgradeDatabase("3.1.0"); + case "3.1.1": + log.info("Upgrading ThingsBoard from version 3.1.1 to 3.2.0 ..."); + databaseEntitiesUpgradeService.upgradeDatabase("3.1.1"); log.info("Updating system data..."); systemDataLoaderService.createOAuth2Templates(); break; diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 8ffde0f256..12fb354846 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -294,22 +294,23 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService } catch (Exception e) { log.error("Failed updating schema!!!", e); } + break; case "3.1.0": try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { log.info("Updating schema ..."); - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.0", "schema_update.sql"); + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.0", SCHEMA_UPDATE_SQL); loadSql(schemaUpdateFile, conn); log.info("Schema updated."); - } catch (Exception e) { - log.error("Failed updating schema!!!", e); } break; - case "3.1.0": + case "3.1.1": try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { log.info("Updating schema ..."); - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.0", SCHEMA_UPDATE_SQL); + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.1", "schema_update.sql"); loadSql(schemaUpdateFile, conn); log.info("Schema updated."); + } catch (Exception e) { + log.error("Failed updating schema!!!", e); } break; default: diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java index 58b25f2dee..a33b0c095f 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java @@ -25,8 +25,6 @@ public interface AdminSettingsService { AdminSettings findAdminSettingsByKey(TenantId tenantId, String key); - void deleteAdminSettingsByKey(TenantId tenantId, String key); - AdminSettings saveAdminSettings(TenantId tenantId, AdminSettings adminSettings); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java index 563130bd12..ec57567a1a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java @@ -48,15 +48,6 @@ public class AdminSettingsServiceImpl implements AdminSettingsService { return adminSettingsDao.findByKey(tenantId, key); } - @Override - public void deleteAdminSettingsByKey(TenantId tenantId, String key) { - log.trace("Executing deleteAdminSettingsByKey [{}]", key); - AdminSettings adminSettings = findAdminSettingsByKey(tenantId, key); - if (adminSettings != null) { - adminSettingsDao.removeById(tenantId, adminSettings.getId().getId()); - } - } - @Override public AdminSettings saveAdminSettings(TenantId tenantId, AdminSettings adminSettings) { log.trace("Executing saveAdminSettings [{}]", adminSettings); From edcaebe9ac409fb8f3f036cff3d74a9bd17e9f51 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 7 Sep 2020 17:19:12 +0300 Subject: [PATCH 083/463] Refactoring layout oauth2 templated --- ui-ngx/src/app/core/http/admin.service.ts | 6 +- .../modules/home/pages/admin/admin.module.ts | 4 +- .../edit-redirect-uri-panel.component.html | 49 ++ .../edit-redirect-uri-panel.component.scss | 18 + .../edit-redirect-uri-panel.component.ts | 76 +++ .../admin/oauth2-settings.component.html | 570 +++++++++--------- .../admin/oauth2-settings.component.scss | 1 + .../pages/admin/oauth2-settings.component.ts | 112 +++- .../src/app/shared/models/settings.models.ts | 5 +- .../assets/locale/locale.constant-en_US.json | 2 +- 10 files changed, 529 insertions(+), 314 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.ts diff --git a/ui-ngx/src/app/core/http/admin.service.ts b/ui-ngx/src/app/core/http/admin.service.ts index ddc2069d4a..1fb6f717c3 100644 --- a/ui-ngx/src/app/core/http/admin.service.ts +++ b/ui-ngx/src/app/core/http/admin.service.ts @@ -63,8 +63,12 @@ export class AdminService { return this.http.get(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config)); } + public getOAuth2Template(config?: RequestConfig): Observable { + return this.http.get(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config)); + } + public saveOAuth2Settings(OAuth2Setting: OAuth2Settings, - config?: RequestConfig): Observable { + config?: RequestConfig): Observable { return this.http.post('/api/oauth2/config', OAuth2Setting, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts index 73fef6a32f..189550d1c2 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts @@ -24,6 +24,7 @@ import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-sett import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component'; import { HomeComponentsModule } from '@modules/home/components/home-components.module'; import { OAuth2SettingsComponent } from '@modules/home/pages/admin/oauth2-settings.component'; +import { EditRedirectUriPanelComponent } from './edit-redirect-uri-panel.component'; @NgModule({ declarations: @@ -31,7 +32,8 @@ import { OAuth2SettingsComponent } from '@modules/home/pages/admin/oauth2-settin GeneralSettingsComponent, MailServerComponent, SecuritySettingsComponent, - OAuth2SettingsComponent + OAuth2SettingsComponent, + EditRedirectUriPanelComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.html b/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.html new file mode 100644 index 0000000000..6f4efc061c --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.html @@ -0,0 +1,49 @@ + +
+
+ + admin.oauth2.redirect-uri-template + + + {{ 'admin.oauth2.redirect-uri-template-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + + +
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.scss b/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.scss new file mode 100644 index 0000000000..43b16cf165 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.scss @@ -0,0 +1,18 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + background-color: #f9f9f9; +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.ts b/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.ts new file mode 100644 index 0000000000..44f6bff0f3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.ts @@ -0,0 +1,76 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, InjectionToken, OnInit, SkipSelf } from '@angular/core'; +import { ErrorStateMatcher } from '@angular/material/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; +import { PageComponent } from '@shared/components/page.component'; +import { OverlayRef } from '@angular/cdk/overlay'; + +export const EDIT_REDIRECT_URI_PANEL_DATA = new InjectionToken('EditRedirectUriPanelData'); + +export interface EditRedirectUriPanelData { + redirectURI: any; +} + +@Component({ + selector: 'tb-edit-redirect-uri-panel', + templateUrl: './edit-redirect-uri-panel.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: EditRedirectUriPanelComponent}], + styleUrls: ['./edit-redirect-uri-panel.component.scss'] +}) +export class EditRedirectUriPanelComponent extends PageComponent implements OnInit, ErrorStateMatcher { + + private URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.?+=&%@\-/]*)?$/; + + redirectURIFormGroup: FormGroup; + + result: any = null; + + submitted = false; + + constructor(protected store: Store, + @Inject(EDIT_REDIRECT_URI_PANEL_DATA) public data: EditRedirectUriPanelData, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public overlayRef: OverlayRef, + public fb: FormBuilder) { + super(store); + } + + ngOnInit(): void { + this.redirectURIFormGroup = this.fb.group({ + value: [this.data.redirectURI, [Validators.required, Validators.pattern(this.URL_REGEXP)]] + }); + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.overlayRef.dispose(); + } + + update(): void { + this.submitted = true; + this.result = this.redirectURIFormGroup.get('value').value; + this.overlayRef.dispose(); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html index f1c3bfaf62..bb77210ba8 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -40,300 +40,316 @@ {{ domain.get('domainName').value ? domain.get('domainName').value : ("admin.new-domain" | translate) }} - + + - - admin.domain-name - - - {{ 'admin.error-verification-url' | translate }} - - - {{ 'admin.domain-name-unique' | translate }} - - - - - admin.oauth2.redirect-uri-template - - - {{ 'admin.oauth2.redirect-uri-template-required' | translate }} - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - - - -
- -
-
- -
- - admin.oauth2.registration-id - - - {{ 'admin.oauth2.registration-id-required' | translate }} - - - {{ 'admin.oauth2.registration-id-unique' | translate }} - - - -
- - admin.oauth2.client-id - - - {{ 'admin.oauth2.client-id-required' | translate }} - - - - - admin.oauth2.client-secret - - - {{ 'admin.oauth2.client-secret-required' | translate }} - - -
- -
- - admin.oauth2.access-token-uri - - - {{ 'admin.oauth2.access-token-uri-required' | translate }} - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - - - - admin.oauth2.authorization-uri - - - {{ 'admin.oauth2.authorization-uri-required' | translate }} - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - -
- - - admin.oauth2.scope - - - {{scope}} - cancel - - - - - {{ 'admin.oauth2.jwk-set-uri-required' | translate }} - - - -
- - admin.oauth2.jwk-set-uri - - - {{ 'admin.oauth2.jwk-set-uri-required' | translate }} - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - - - - admin.oauth2.user-info-uri - - - {{ 'admin.oauth2.user-info-uri-required' | translate }} - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - -
- - - admin.oauth2.client-authentication-method - - - {{ clientAuthenticationMethod | uppercase }} - - - - - - admin.oauth2.user-name-attribute-name - - - {{ 'admin.oauth2.user-name-attribute-name-required' | translate }} - - - -
-
- - {{ 'admin.oauth2.allow-user-creation' | translate }} - - - {{ 'admin.oauth2.activate-user' | translate }} - -
- - - admin.oauth2.type - - - {{ converterTypeExternalUser }} - - - - -
- - admin.oauth2.email-attribute-key - - - {{ 'admin.oauth2.email-attribute-key-required' | translate }} - + + + admin.domain-name + + + {{ 'admin.error-verification-url' | translate }} + + + {{ 'admin.domain-name-unique' | translate }} + + + + +
+ + + + {{ registration.get('providerName').value }} + + + + + + + +
+ + Login Provider + + + {{ provider | uppercase }} + +
- admin.oauth2.first-name-attribute-key - - - - - admin.oauth2.last-name-attribute-key - - -
- -
- - admin.oauth2.tenant-name-strategy - - - {{ tenantNameStrategy }} - - - - - - admin.oauth2.tenant-name-pattern - - - {{ 'admin.oauth2.tenant-name-pattern-required' | translate }} + admin.oauth2.client-id + + + {{ 'admin.oauth2.client-id-required' | translate }} -
- - admin.oauth2.customer-name-pattern - - - -
- admin.oauth2.default-dashboard-name - + admin.oauth2.client-secret + + + {{ 'admin.oauth2.client-secret-required' | translate }} + - - - {{ 'admin.oauth2.always-fullscreen' | translate}} -
-
- -
- - admin.oauth2.url - - - {{ 'admin.oauth2.url-required' | translate }} - - - {{ 'admin.oauth2.url-pattern' | translate }} - - + + +
+ + admin.oauth2.access-token-uri + + + {{ 'admin.oauth2.access-token-uri-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + + + + + admin.oauth2.authorization-uri + + + {{ 'admin.oauth2.authorization-uri-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + + +
+ +
+ + admin.oauth2.jwk-set-uri + + + {{ 'admin.oauth2.jwk-set-uri-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + + + + + admin.oauth2.user-info-uri + + + {{ 'admin.oauth2.user-info-uri-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + + +
+ + + admin.oauth2.client-authentication-method + + + {{ clientAuthenticationMethod | uppercase }} + + + + +
+ + admin.oauth2.login-button-label + + + {{ 'admin.oauth2.login-button-label-required' | translate }} + + + + + admin.oauth2.login-button-icon + + +
+ + + admin.oauth2.scope + + + {{scope}} + cancel + + + + + {{ 'admin.oauth2.jwk-set-uri-required' | translate }} + + + +
+ + + admin.oauth2.user-name-attribute-name + + + {{ 'admin.oauth2.user-name-attribute-name-required' | translate }} + + + +
+
+ + {{ 'admin.oauth2.allow-user-creation' | translate }} + + + {{ 'admin.oauth2.activate-user' | translate }} + +
+ + + admin.oauth2.type + + + {{ converterTypeExternalUser }} + + + + +
+ + admin.oauth2.email-attribute-key + + + {{ 'admin.oauth2.email-attribute-key-required' | translate }} + + + +
+ + admin.oauth2.first-name-attribute-key + + + + + admin.oauth2.last-name-attribute-key + + +
+ +
+ + admin.oauth2.tenant-name-strategy + + + {{ tenantNameStrategy }} + + + + + + admin.oauth2.tenant-name-pattern + + + {{ 'admin.oauth2.tenant-name-pattern-required' | translate }} + + +
+ + + admin.oauth2.customer-name-pattern + + + +
+ + admin.oauth2.default-dashboard-name + + + + + {{ 'admin.oauth2.always-fullscreen' | translate}} + +
+
+ +
+ + admin.oauth2.url + + + {{ 'admin.oauth2.url-required' | translate }} + + + {{ 'admin.oauth2.url-pattern' | translate }} + + + +
+ + common.username + + + + + common.password + + +
+
+
+
+
-
- - common.username - - - - - common.password - - -
-
- -
- - admin.oauth2.login-button-label - - - {{ 'admin.oauth2.login-button-label-required' | translate }} - - - - - admin.oauth2.login-button-icon - - -
-
- + + +
+
+ +
+
- - -
- -
+ diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss index 95f47b7023..d174ba2dcf 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss @@ -20,6 +20,7 @@ .registration-card{ margin-bottom: 8px; + border: 1px solid rgba(0, 0, 0, 0.2); } .container{ diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index ab472643a6..0459213ee9 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject, OnDestroy, OnInit, ViewContainerRef } from '@angular/core'; import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ClientAuthenticationMethod, @@ -32,9 +32,16 @@ import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; import { COMMA, ENTER } from '@angular/cdk/keycodes'; import { MatChipInputEvent } from '@angular/material/chips'; import { WINDOW } from '@core/services/window.service'; -import { Subscription } from 'rxjs'; +import { forkJoin, Subscription } from 'rxjs'; import { DialogService } from '@core/services/dialog.service'; import { TranslateService } from '@ngx-translate/core'; +import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; +import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; +import { + EDIT_REDIRECT_URI_PANEL_DATA, + EditRedirectUriPanelComponent, + EditRedirectUriPanelData +} from './edit-redirect-uri-panel.component'; @Component({ selector: 'tb-oauth2-settings', @@ -45,6 +52,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha private URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.?+=&%@\-/]*)?$/; private subscriptions: Subscription[] = []; + private templates = []; readonly separatorKeysCodes: number[] = [ENTER, COMMA]; @@ -55,8 +63,13 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha converterTypesExternalUser: MapperConfigType[] = ['BASIC', 'CUSTOM']; tenantNameStrategies: TenantNameStrategy[] = ['DOMAIN', 'EMAIL', 'CUSTOM']; + templateProvider = ['Custom']; + constructor(protected store: Store, private adminService: AdminService, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + private cd: ChangeDetectorRef, private fb: FormBuilder, private dialogService: DialogService, private translate: TranslateService, @@ -66,8 +79,12 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha ngOnInit(): void { this.buildOAuth2SettingsForm(); - this.adminService.getOAuth2Settings().subscribe( - (oauth2Settings) => { + forkJoin([ + this.adminService.getOAuth2Template(), + this.adminService.getOAuth2Settings() + ]).subscribe( + ([templates, oauth2Settings]) => { + this.initTemplates(templates); this.oauth2Settings = oauth2Settings; this.initOAuth2Settings(this.oauth2Settings); this.oauth2SettingsForm.reset(this.oauth2Settings); @@ -79,7 +96,13 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha super.ngOnDestroy(); this.subscriptions.forEach((subscription) => { subscription.unsubscribe(); - }) + }); + } + + private initTemplates(templates: any): void { + this.templates = templates; + templates.map(provider => this.templateProvider.push(provider.name)); + this.templateProvider.sort(); } get clientsDomainsParams(): FormArray { @@ -114,7 +137,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha url: [null, [Validators.required, Validators.pattern(this.URL_REGEXP)]], username: [null], password: [null] - }) + }); } private buildOAuth2SettingsForm(): void { @@ -136,7 +159,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha const listDomainName = []; control.root.value.clientsDomainsParams.forEach((domain) => { listDomainName.push(domain.domainName); - }) + }); if (listDomainName.indexOf(control.value) > -1) { return {unique: true}; } @@ -144,21 +167,6 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha return null; } - private uniqueRegistrationIdValidator(control: AbstractControl): { [key: string]: boolean } | null { - if (control.value !== null && control?.root) { - const listRegistration = []; - control.root.value.clientsDomainsParams.forEach((domain) => { - domain.clientRegistrations.forEach((client) => { - listRegistration.push(client.registrationId); - }) - }) - if (listRegistration.indexOf(control.value) > -1) { - return {unique: true}; - } - } - return null; - } - private buildSettingsDomain(domainParams?: DomainParams): FormGroup { let url = this.window.location.protocol + '//' + this.window.location.hostname; const port = this.window.location.port; @@ -174,7 +182,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha this.subscriptions.push(formDomain.get('domainName').valueChanges.subscribe((domain) => { if (!domain) { - domain = this.window.location.hostname + domain = this.window.location.hostname; } const uri = this.window.location.protocol + `//${domain}/login/oauth2/code/`; formDomain.get('redirectUriTemplate').patchValue(uri); @@ -183,7 +191,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha if (domainParams) { domainParams.clientRegistrations.forEach((registration) => { this.clientDomainRegistrations(formDomain).push(this.buildSettingsRegistration(registration)); - }) + }); } else { this.clientDomainRegistrations(formDomain).push(this.buildSettingsRegistration()); } @@ -193,7 +201,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha private buildSettingsRegistration(registrationData?: ClientRegistration): FormGroup { const clientRegistration = this.fb.group({ - registrationId: [null, [Validators.required, this.uniqueRegistrationIdValidator]], + providerName: ['Custom', [Validators.required]], loginButtonLabel: [null, [Validators.required]], loginButtonIcon: [null], clientId: ['', [Validators.required]], @@ -227,8 +235,8 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha if (registrationData) { registrationData.scope.forEach(() => { - (clientRegistration.get('scope') as FormArray).push(this.fb.control('')) - }) + (clientRegistration.get('scope') as FormArray).push(this.fb.control('')); + }); if (registrationData.mapperConfig.type !== 'BASIC') { clientRegistration.get('mapperConfig.type').patchValue('CUSTOM'); } @@ -288,7 +296,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha if (data) { this.clientsDomainsParams.removeAt(index); } - }) + }); } clientDomainRegistrations(control: AbstractControl): FormArray { @@ -305,15 +313,57 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha $event.preventDefault(); } - const registrationId = this.clientDomainRegistrations(controler).at(index).get('registrationId').value; + const providerName = this.clientDomainRegistrations(controler).at(index).get('providerName').value; this.dialogService.confirm( - this.translate.instant('admin.oauth2.delete-registration-title', {name: registrationId || ''}), + this.translate.instant('admin.oauth2.delete-registration-title', {name: providerName || ''}), this.translate.instant('admin.oauth2.delete-registration-text'), null, this.translate.instant('action.delete') ).subscribe((data) => { if (data) { this.clientDomainRegistrations(controler).removeAt(index); } - }) + }); + } + + editRedirectURI($event: MouseEvent, index: number) { + if ($event) { + $event.stopPropagation(); + $event.preventDefault(); + } + const target = $event.target || $event.currentTarget; + const config = new OverlayConfig(); + config.backdropClass = 'cdk-overlay-transparent-backdrop'; + config.hasBackdrop = true; + const connectedPosition: ConnectedPosition = { + originX: 'end', + originY: 'bottom', + overlayX: 'end', + overlayY: 'top' + }; + config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) + .withPositions([connectedPosition]); + + const overlayRef = this.overlay.create(config); + overlayRef.backdropClick().subscribe(() => { + overlayRef.dispose(); + }); + + const redirectURI = this.clientsDomainsParams.at(index).get('redirectUriTemplate').value; + + const injectionTokens = new WeakMap([ + [EDIT_REDIRECT_URI_PANEL_DATA, { + redirectURI + } as EditRedirectUriPanelData], + [OverlayRef, overlayRef] + ]); + const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); + const componentRef = overlayRef.attach(new ComponentPortal(EditRedirectUriPanelComponent, + this.viewContainerRef, injector)); + componentRef.onDestroy(() => { + if (componentRef.instance.result !== null) { + const attributeValue = componentRef.instance.result; + this.clientsDomainsParams.at(index).get('redirectUriTemplate').patchValue(attributeValue, {emitEvent: true}); + } + }); } } diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index c4a1504f0f..2016a309c6 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -76,7 +76,6 @@ export interface DomainParams { } export interface ClientRegistration { - registrationId: string; loginButtonLabel: string; loginButtonIcon: string; clientId: string; @@ -86,9 +85,9 @@ export interface ClientRegistration { scope: string[]; jwkSetUri: string; userInfoUri: string; - clientAuthenticationMethod: ClientAuthenticationMethod + clientAuthenticationMethod: ClientAuthenticationMethod; userNameAttributeName: string; - mapperConfig: MapperConfig + mapperConfig: MapperConfig; } export interface MapperConfig { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index c8967f0636..aea8c1cfe4 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -125,7 +125,7 @@ "error-verification-url": "A domain name shouldn't contain symbols '/' and ':'. Example: thingsboard.io", "add-domain": "Add domain", "new-domain": "New domain", - "add-registration": "Add registration", + "add-provider": "Add provider", "settings": "Settings", "oauth2": { "settings": "OAuth2 Settings", From bb06f5ebe078035262544eb3501466b0f3a9f918 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Tue, 15 Sep 2020 18:14:31 +0300 Subject: [PATCH 084/463] Add min/max validation --- .../lib/multiple-input-widget.component.html | 11 ++++++ .../lib/multiple-input-widget.component.ts | 39 +++++++++++++++---- .../assets/locale/locale.constant-en_US.json | 3 ++ 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html index 659a8156cb..1423000d4a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html @@ -53,12 +53,20 @@ [readonly]="key.settings.isEditable === 'readonly'" type="number" step="{{key.settings.step}}" + min="{{key.settings.minValue}}" + max="{{key.settings.maxValue}}" (focus)="key.isFocused = true; focusInputElement($event)" (blur)="key.isFocused = false; inputChanged(source, key)"> {{key.settings.icon}} {{key.settings.requiredErrorMessage}} + + {{ 'widgets.input-widgets.min-value-error' | translate:{value: key.settings.minValue} }} + + + {{ 'widgets.input-widgets.max-value-error' | translate:{value: key.settings.maxValue} }} +
@@ -92,6 +100,9 @@ {{key.settings.requiredErrorMessage}} + + {{ 'widgets.input-widgets.invalid-date' | translate }} +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts index e26600a939..21a8d1d062 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts @@ -24,7 +24,7 @@ import { UtilsService } from '@core/services/utils.service'; import { TranslateService } from '@ngx-translate/core'; import { DataKey, Datasource, DatasourceData, DatasourceType, WidgetConfig } from '@shared/models/widget.models'; import { IWidgetSubscription } from '@core/api/widget-api.models'; -import { isDefined, isEqual, isUndefined, createLabelFromDatasource } from '@core/utils'; +import { isDefined, isEqual, isUndefined, createLabelFromDatasource, isDefinedAndNotNull } from '@core/utils'; import { EntityType } from '@shared/models/entity-type.models'; import * as _moment from 'moment'; import { FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms'; @@ -62,19 +62,32 @@ interface MultipleInputWidgetDataKeySettings { isEditable: MultipleInputWidgetDataKeyEditableType; disabledOnDataKey: string; dataKeyHidden: boolean; - step: number; - requiredErrorMessage: string; + step?: number; + minValue?: number; + maxValue?: number; + requiredErrorMessage?: string; + invalidDateErrorMessage?: string; + minErrorMessage?: string; + maxErrorMessage?: string; icon: string; inputTypeNumber?: boolean; readOnly?: boolean; disabledOnCondition?: boolean; } +interface MultipleInputWidgetDataKeyErrorMessages { + requiredErrorMessage?: string; + invalidDateErrorMessage?: string; + minErrorMessage?: string; + maxErrorMessage?: string; +} + interface MultipleInputWidgetDataKey extends DataKey { formId?: string; settings: MultipleInputWidgetDataKeySettings; isFocused: boolean; value?: any; + errorMessages?: MultipleInputWidgetDataKeyErrorMessages; } interface MultipleInputWidgetSource { @@ -195,7 +208,7 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni if (dataKey.units) { dataKey.label += ' (' + dataKey.units + ')'; } - dataKey.formId = (++keyIndex)+''; + dataKey.formId = (++keyIndex) + ''; dataKey.isFocused = false; // For backward compatibility @@ -245,6 +258,14 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni if (key.settings.dataKeyValueType === 'integer') { validators.push(Validators.pattern(/^-?[0-9]+$/)); } + if (key.settings.dataKeyValueType === 'integer' || key.settings.dataKeyValueType === 'double') { + if (isDefinedAndNotNull(key.settings.minValue) && isFinite(key.settings.minValue)) { + validators.push(Validators.min(key.settings.minValue)); + } + if (isDefinedAndNotNull(key.settings.maxValue) && isFinite(key.settings.maxValue)) { + validators.push(Validators.max(key.settings.maxValue)); + } + } const formControl = this.fb.control( { value: key.value, disabled: key.settings.isEditable === 'disabled' || key.settings.disabledOnCondition}, @@ -265,7 +286,11 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni switch (key.settings.dataKeyValueType) { case 'dateTime': case 'date': - value = _moment(keyData[0][1]).toDate(); + if (isDefinedAndNotNull(keyData[0][1]) && keyData[0][1] !== '') { + value = _moment(keyData[0][1]).toDate(); + } else { + value = null; + } break; case 'time': value = _moment().startOf('day').add(keyData[0][1], 'ms').toDate(); @@ -455,7 +480,7 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni if (tasks.length) { forkJoin(tasks).subscribe( () => { - this.multipleInputFormGroup.reset(undefined, {emitEvent: false}); + // this.multipleInputFormGroup.reset(undefined, {emitEvent: false}); this.multipleInputFormGroup.markAsPristine(); if (this.settings.showResultMessage) { this.ctx.showSuccessToast(this.translate.instant('widgets.input-widgets.update-successful'), @@ -469,7 +494,7 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni } }); } else { - this.multipleInputFormGroup.reset(undefined, {emitEvent: false}); + // this.multipleInputFormGroup.reset(undefined, {emitEvent: false}); this.multipleInputFormGroup.markAsPristine(); } } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 9c36e0ee68..0258408d15 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1991,8 +1991,11 @@ "entity-coordinate-required": "Both fields, latitude and longitude, are required", "entity-timeseries-required": "Entity timeseries is required", "get-location": "Get current location", + "invalid-date": "Invalid Date", "latitude": "Latitude", "longitude": "Longitude", + "min-value-error": "Min value is {{value}}", + "max-value-error": "Max value is {{value}}", "not-allowed-entity": "Selected entity cannot have shared attributes", "no-attribute-selected": "No attribute is selected", "no-datakey-selected": "No datakey is selected", From 14939c27e61a17480cb46688233cd695a55aa492 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Tue, 15 Sep 2020 17:31:55 +0300 Subject: [PATCH 085/463] Added 'deleteByDomain' for OAuth2Service --- .../server/controller/OAuth2Controller.java | 24 +++++++++++++++++++ .../server/dao/oauth2/OAuth2Service.java | 2 ++ .../oauth2/OAuth2ClientRegistrationDao.java | 2 ++ .../server/dao/oauth2/OAuth2ServiceImpl.java | 12 +++++++++- .../JpaOAuth2ClientRegistrationDao.java | 5 ++++ .../OAuth2ClientRegistrationRepository.java | 2 ++ .../dao/service/BaseOAuth2ServiceTest.java | 14 +++++++++++ 7 files changed, 60 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index da510fe84e..bf010ce383 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -47,6 +47,7 @@ import java.util.List; @Slf4j public class OAuth2Controller extends BaseController { private static final String CLIENT_REGISTRATION_ID = "clientRegistrationId"; + private static final String DOMAIN = "domain"; private static final String CLIENT_REGISTRATION_TEMPLATE_ID = "clientRegistrationTemplateId"; @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) @@ -131,6 +132,29 @@ public class OAuth2Controller extends BaseController { } } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(value = "/oauth2/config/domain/{domain}", method = RequestMethod.DELETE) + @ResponseStatus(value = HttpStatus.OK) + public void deleteClientRegistrationForDomain(@PathVariable(DOMAIN) String domain) throws ThingsboardException { + checkParameter(DOMAIN, domain); + try { + oAuth2Service.deleteClientRegistrationsByDomain(getCurrentUser().getTenantId(), domain); + + logEntityAction(emptyId(EntityType.OAUTH2_CLIENT_REGISTRATION), null, + null, + ActionType.DELETED, null, domain); + + } catch (Exception e) { + logEntityAction(emptyId(EntityType.OAUTH2_CLIENT_REGISTRATION), + null, + null, + ActionType.DELETED, e, domain); + + throw handleException(e); + } + } + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @RequestMapping(value = "/oauth2/config/template/{clientRegistrationTemplateId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java index c8da37e9e5..5ad706cf77 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java @@ -38,5 +38,7 @@ public interface OAuth2Service { void deleteClientRegistrationById(TenantId tenantId, OAuth2ClientRegistrationId id); + void deleteClientRegistrationsByDomain(TenantId tenantId, String domain); + boolean isOAuth2ClientRegistrationAllowed(TenantId tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java index d5cded18d3..8152e9a8db 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java @@ -28,5 +28,7 @@ public interface OAuth2ClientRegistrationDao extends Dao findByDomainName(String domainName); + int removeByTenantIdAndDomainName(UUID tenantId, String domainName); + int removeByTenantId(UUID tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index d6a22b3c6b..7522008c06 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -100,11 +100,21 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se @Override public void deleteClientRegistrationById(TenantId tenantId, OAuth2ClientRegistrationId id) { - log.trace("Executing deleteClientRegistrationById [{}]", id); + log.trace("Executing deleteClientRegistrationById [{}], [{}]", tenantId, id); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(id, INCORRECT_CLIENT_REGISTRATION_ID + id); clientRegistrationDao.removeById(tenantId, id.getId()); } + @Override + @Transactional + public void deleteClientRegistrationsByDomain(TenantId tenantId, String domain) { + log.trace("Executing deleteClientRegistrationsByDomain [{}], [{}]", tenantId, domain); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateString(domain, INCORRECT_DOMAIN_NAME + domain); + clientRegistrationDao.removeByTenantIdAndDomainName(tenantId.getId(), domain); + } + @Override public boolean isOAuth2ClientRegistrationAllowed(TenantId tenantId) { log.trace("Executing isOAuth2ClientRegistrationAllowed [{}]", tenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java index ef10245e19..d5e17ac635 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java @@ -67,6 +67,11 @@ public class JpaOAuth2ClientRegistrationDao extends JpaAbstractDao findAllByDomainName(String domainName); + int deleteByTenantIdAndDomainName(UUID tenantId, String domainName); + int deleteByTenantId(UUID tenantId); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java index d9165b08b8..34b10c2af9 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -215,6 +215,20 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { Assert.assertEquals(0, oAuth2Service.findClientRegistrationsByTenantId(tenantId).size()); } + @Test + public void testDeleteTenantDomainOAuth2ClientRegistrations() { + oAuth2Service.saveClientRegistration(validClientRegistration(tenantId, "domain1")); + oAuth2Service.saveClientRegistration(validClientRegistration(tenantId, "domain1")); + oAuth2Service.saveClientRegistration(validClientRegistration(tenantId, "domain2")); + oAuth2Service.saveClientRegistration(validClientRegistration(TenantId.SYS_TENANT_ID, "domain2")); + Assert.assertEquals(4, oAuth2Service.findAllClientRegistrations().size()); + Assert.assertEquals(3, oAuth2Service.findClientRegistrationsByTenantId(tenantId).size()); + + oAuth2Service.deleteClientRegistrationsByDomain(tenantId, "domain1"); + Assert.assertEquals(2, oAuth2Service.findAllClientRegistrations().size()); + Assert.assertEquals(1, oAuth2Service.findClientRegistrationsByTenantId(tenantId).size()); + } + private void updateTenantAllowOAuth2Setting(Boolean allowOAuth2) throws IOException { Tenant tenant = tenantService.findTenantById(tenantId); if (allowOAuth2 == null) { From c3407bfddcc7b6550149e244523e2ccd14d5d8a4 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Wed, 16 Sep 2020 16:42:39 +0300 Subject: [PATCH 086/463] Group ClientRegistrations by Domain --- .../server/controller/OAuth2Controller.java | 33 ++-- .../server/dao/oauth2/OAuth2Service.java | 5 +- .../data/oauth2/ClientRegistrationDto.java | 31 +++ .../oauth2/OAuth2ClientsDomainParams.java | 2 +- .../data/oauth2/OAuth2ClientsParams.java | 5 +- .../server/dao/oauth2/OAuth2ServiceImpl.java | 180 +++++++++--------- .../server/dao/oauth2/OAuth2Utils.java | 74 ++++++- .../dao/service/BaseOAuth2ServiceTest.java | 106 +++++++---- 8 files changed, 293 insertions(+), 143 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index bf010ce383..1067b1f647 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -28,10 +28,7 @@ import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; +import org.thingsboard.server.common.data.oauth2.*; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -40,6 +37,7 @@ import org.thingsboard.server.service.security.permission.Resource; import javax.servlet.http.HttpServletRequest; import java.util.List; +import java.util.stream.Collectors; @RestController @TbCoreComponent @@ -67,15 +65,13 @@ public class OAuth2Controller extends BaseController { try { Authority authority = getCurrentUser().getAuthority(); checkOAuth2ConfigPermissions(Operation.READ); - List clientRegistrations = null; if (Authority.SYS_ADMIN.equals(authority)) { - clientRegistrations = oAuth2Service.findClientRegistrationsByTenantId(TenantId.SYS_TENANT_ID); + return oAuth2Service.findClientsParamsByTenantId(TenantId.SYS_TENANT_ID); } else if (Authority.TENANT_ADMIN.equals(authority)) { - clientRegistrations = oAuth2Service.findClientRegistrationsByTenantId(getCurrentUser().getTenantId()); + return oAuth2Service.findClientsParamsByTenantId(getCurrentUser().getTenantId()); } else { throw new IllegalStateException("Authority " + authority + " cannot get client registrations."); } - return new OAuth2ClientsParams(clientRegistrations); } catch (Exception e) { throw handleException(e); } @@ -84,11 +80,24 @@ public class OAuth2Controller extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/oauth2/config", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) - public OAuth2ClientRegistration saveClientRegistration(@RequestBody OAuth2ClientRegistration clientRegistration) throws ThingsboardException { + public OAuth2ClientsParams saveClientParams(@RequestBody OAuth2ClientsParams clientsParams) throws ThingsboardException { try { - clientRegistration.setTenantId(getCurrentUser().getTenantId()); - checkEntity(clientRegistration.getId(), clientRegistration, Resource.OAUTH2_CONFIGURATION); - return oAuth2Service.saveClientRegistration(clientRegistration); + TenantId tenantId; + Authority authority = getCurrentUser().getAuthority(); + if (Authority.SYS_ADMIN.equals(authority)) { + tenantId = TenantId.SYS_TENANT_ID; + } else if (Authority.TENANT_ADMIN.equals(authority)) { + tenantId = getCurrentUser().getTenantId(); + } else { + throw new IllegalStateException("Authority " + authority + " cannot save client registrations."); + } + List clientRegistrationDtos = clientsParams.getOAuth2DomainDtos().stream() + .flatMap(domainParams -> domainParams.getClientRegistrations().stream()) + .collect(Collectors.toList()); + for (ClientRegistrationDto clientRegistrationDto : clientRegistrationDtos) { + checkEntity(clientRegistrationDto.getId(), () -> tenantId, Resource.OAUTH2_CONFIGURATION); + } + return oAuth2Service.saveClientsParams(tenantId, clientsParams); } catch (Exception e) { throw handleException(e); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java index 5ad706cf77..bc366b130a 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java @@ -19,6 +19,7 @@ import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; import java.util.List; import java.util.UUID; @@ -26,9 +27,9 @@ import java.util.UUID; public interface OAuth2Service { List getOAuth2Clients(String domainName); - OAuth2ClientRegistration saveClientRegistration(OAuth2ClientRegistration clientRegistration); + OAuth2ClientsParams saveClientsParams(TenantId tenantId, OAuth2ClientsParams clientsParams); - List findClientRegistrationsByTenantId(TenantId tenantId); + OAuth2ClientsParams findClientsParamsByTenantId(TenantId tenantId); OAuth2ClientRegistration findClientRegistration(UUID id); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java new file mode 100644 index 0000000000..3394d92f0e --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java @@ -0,0 +1,31 @@ +package org.thingsboard.server.common.data.oauth2; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; +import org.thingsboard.server.common.data.id.TenantId; + +import java.util.List; + +@EqualsAndHashCode +@Data +@ToString(exclude = {"clientSecret"}) +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ClientRegistrationDto { + private OAuth2ClientRegistrationId id; + private long createdTime; + private OAuth2MapperConfig mapperConfig; + private String clientId; + private String clientSecret; + private String authorizationUri; + private String accessTokenUri; + private List scope; + private String userInfoUri; + private String userNameAttributeName; + private String jwkSetUri; + private String clientAuthenticationMethod; + private String loginButtonLabel; + private String loginButtonIcon; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java index 699bf90c37..33670c7b2b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java @@ -28,5 +28,5 @@ import java.util.List; public class OAuth2ClientsDomainParams { private String domainName; private String redirectUriTemplate; - private List clientRegistrations; + private List clientRegistrations; } \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java index f442669b68..b42571b2d6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java @@ -16,8 +16,11 @@ package org.thingsboard.server.common.data.oauth2; import lombok.*; +import org.thingsboard.server.common.data.id.TenantId; +import java.util.Collection; import java.util.List; +import java.util.Objects; @EqualsAndHashCode @Data @@ -26,5 +29,5 @@ import java.util.List; @NoArgsConstructor @AllArgsConstructor public class OAuth2ClientsParams { - private List clientRegistrations; + private List oAuth2DomainDtos; } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 7522008c06..a5464cd8aa 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -33,8 +33,9 @@ import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.tenant.TenantService; import javax.transaction.Transactional; -import java.util.List; -import java.util.UUID; +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.stream.Collectors; import static org.thingsboard.server.dao.oauth2.OAuth2Utils.ALLOW_OAUTH2_CONFIGURATION; @@ -64,17 +65,22 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se } @Override - public OAuth2ClientRegistration saveClientRegistration(OAuth2ClientRegistration clientRegistration) { - log.trace("Executing saveClientRegistration [{}]", clientRegistration); - clientRegistrationValidator.validate(clientRegistration, OAuth2ClientRegistration::getTenantId); - return clientRegistrationDao.save(clientRegistration.getTenantId(), clientRegistration); + @Transactional + public OAuth2ClientsParams saveClientsParams(TenantId tenantId, OAuth2ClientsParams clientsParams) { + log.trace("Executing saveClientsParams [{}] [{}]", tenantId, clientsParams); + clientParamsValidator.accept(tenantId, clientsParams); + List inputClientRegistrations = OAuth2Utils.toClientRegistrations(tenantId, clientsParams); + List savedClientRegistrations = inputClientRegistrations.stream() + .map(clientRegistration -> clientRegistrationDao.save(clientRegistration.getTenantId(), clientRegistration)) + .collect(Collectors.toList()); + return OAuth2Utils.toOAuth2ClientsParams(savedClientRegistrations); } @Override - public List findClientRegistrationsByTenantId(TenantId tenantId) { - log.trace("Executing findClientRegistrationsByTenantId [{}]", tenantId); + public OAuth2ClientsParams findClientsParamsByTenantId(TenantId tenantId) { + log.trace("Executing findClientsParamsByTenantId [{}]", tenantId); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - return clientRegistrationDao.findByTenantId(tenantId.getId()); + return OAuth2Utils.toOAuth2ClientsParams(clientRegistrationDao.findByTenantId(tenantId.getId())); } @Override @@ -129,92 +135,90 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se } } - private final DataValidator clientRegistrationValidator = - new DataValidator() { - - @Override - protected void validateCreate(TenantId tenantId, OAuth2ClientRegistration clientRegistration) { + private final BiConsumer clientParamsValidator = (tenantId, clientsParams) -> { + if (clientsParams == null || clientsParams.getOAuth2DomainDtos() == null + || clientsParams.getOAuth2DomainDtos().isEmpty()) { + throw new DataValidationException("Domain params should be specified!"); + } + for (OAuth2ClientsDomainParams domainParams : clientsParams.getOAuth2DomainDtos()) { + if (StringUtils.isEmpty(domainParams.getDomainName())) { + throw new DataValidationException("Domain name should be specified!"); + } + if (StringUtils.isEmpty(domainParams.getRedirectUriTemplate())) { + throw new DataValidationException("Redirect URI template should be specified!"); + } + if (domainParams.getClientRegistrations() == null || domainParams.getClientRegistrations().isEmpty()) { + throw new DataValidationException("Client registrations should be specified!"); + } + for (ClientRegistrationDto clientRegistration : domainParams.getClientRegistrations()) { + if (StringUtils.isEmpty(clientRegistration.getClientId())) { + throw new DataValidationException("Client ID should be specified!"); } - - @Override - protected void validateUpdate(TenantId tenantId, OAuth2ClientRegistration clientRegistration) { + if (StringUtils.isEmpty(clientRegistration.getClientSecret())) { + throw new DataValidationException("Client secret should be specified!"); } - - @Override - protected void validateDataImpl(TenantId tenantId, OAuth2ClientRegistration clientRegistration) { - if (StringUtils.isEmpty(clientRegistration.getDomainName())) { - throw new DataValidationException("Domain name should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getRedirectUriTemplate())) { - throw new DataValidationException("Redirect URI template should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getClientId())) { - throw new DataValidationException("Client ID should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getClientSecret())) { - throw new DataValidationException("Client secret should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getAuthorizationUri())) { - throw new DataValidationException("Authorization uri should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getAccessTokenUri())) { - throw new DataValidationException("Token uri should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getScope())) { - throw new DataValidationException("Scope should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getUserInfoUri())) { - throw new DataValidationException("User info uri should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getUserNameAttributeName())) { - throw new DataValidationException("User name attribute name should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getClientAuthenticationMethod())) { - throw new DataValidationException("Client authentication method should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getLoginButtonLabel())) { - throw new DataValidationException("Login button label should be specified!"); + if (StringUtils.isEmpty(clientRegistration.getAuthorizationUri())) { + throw new DataValidationException("Authorization uri should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getAccessTokenUri())) { + throw new DataValidationException("Token uri should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getScope())) { + throw new DataValidationException("Scope should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getUserInfoUri())) { + throw new DataValidationException("User info uri should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getUserNameAttributeName())) { + throw new DataValidationException("User name attribute name should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getClientAuthenticationMethod())) { + throw new DataValidationException("Client authentication method should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getLoginButtonLabel())) { + throw new DataValidationException("Login button label should be specified!"); + } + OAuth2MapperConfig mapperConfig = clientRegistration.getMapperConfig(); + if (mapperConfig == null) { + throw new DataValidationException("Mapper config should be specified!"); + } + if (mapperConfig.getType() == null) { + throw new DataValidationException("Mapper config type should be specified!"); + } + if (mapperConfig.getType() == MapperType.BASIC) { + OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic(); + if (basicConfig == null) { + throw new DataValidationException("Basic config should be specified!"); } - OAuth2MapperConfig mapperConfig = clientRegistration.getMapperConfig(); - if (mapperConfig == null) { - throw new DataValidationException("Mapper config should be specified!"); + if (StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) { + throw new DataValidationException("Email attribute key should be specified!"); } - if (mapperConfig.getType() == null) { - throw new DataValidationException("Mapper config type should be specified!"); + if (basicConfig.getTenantNameStrategy() == null) { + throw new DataValidationException("Tenant name strategy should be specified!"); } - if (mapperConfig.getType() == MapperType.BASIC) { - OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic(); - if (basicConfig == null) { - throw new DataValidationException("Basic config should be specified!"); - } - if (StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) { - throw new DataValidationException("Email attribute key should be specified!"); - } - if (basicConfig.getTenantNameStrategy() == null) { - throw new DataValidationException("Tenant name strategy should be specified!"); - } - if (basicConfig.getTenantNameStrategy() == TenantNameStrategyType.CUSTOM - && StringUtils.isEmpty(basicConfig.getTenantNamePattern())) { - throw new DataValidationException("Tenant name pattern should be specified!"); - } + if (basicConfig.getTenantNameStrategy() == TenantNameStrategyType.CUSTOM + && StringUtils.isEmpty(basicConfig.getTenantNamePattern())) { + throw new DataValidationException("Tenant name pattern should be specified!"); } - if (mapperConfig.getType() == MapperType.CUSTOM) { - OAuth2CustomMapperConfig customConfig = mapperConfig.getCustom(); - if (customConfig == null) { - throw new DataValidationException("Custom config should be specified!"); - } - if (StringUtils.isEmpty(customConfig.getUrl())) { - throw new DataValidationException("Custom mapper URL should be specified!"); - } + } + if (mapperConfig.getType() == MapperType.CUSTOM) { + OAuth2CustomMapperConfig customConfig = mapperConfig.getCustom(); + if (customConfig == null) { + throw new DataValidationException("Custom config should be specified!"); } - if (clientRegistration.getTenantId() == null) { - throw new DataValidationException("Client registration should be assigned to tenant!"); - } else if (!TenantId.SYS_TENANT_ID.equals(clientRegistration.getTenantId())) { - Tenant tenant = tenantService.findTenantById(clientRegistration.getTenantId()); - if (tenant == null) { - throw new DataValidationException("Client registration is referencing to non-existent tenant!"); - } + if (StringUtils.isEmpty(customConfig.getUrl())) { + throw new DataValidationException("Custom mapper URL should be specified!"); } } - }; + } + } + if (tenantId == null) { + throw new DataValidationException("Client registration should be assigned to tenant!"); + } else if (!TenantId.SYS_TENANT_ID.equals(tenantId)) { + Tenant tenant = tenantService.findTenantById(tenantId); + if (tenant == null) { + throw new DataValidationException("Client registration is referencing to non-existent tenant!"); + } + } + }; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java index 948c894540..3570b8a1e9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java @@ -16,10 +16,14 @@ package org.thingsboard.server.dao.oauth2; import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.oauth2.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import java.util.stream.Stream; public class OAuth2Utils { @@ -33,4 +37,68 @@ public class OAuth2Utils { client.setIcon(clientRegistration.getLoginButtonIcon()); return client; } + + public static List toClientRegistrations(TenantId tenantId, OAuth2ClientsParams clientsParams) { + return clientsParams.getOAuth2DomainDtos().stream() + .flatMap(domainParams -> domainParams.getClientRegistrations().stream() + .map(clientRegistrationDto -> OAuth2Utils.toClientRegistration(tenantId, domainParams.getDomainName(), + domainParams.getRedirectUriTemplate(), clientRegistrationDto) + )) + .collect(Collectors.toList()); + } + + public static OAuth2ClientsParams toOAuth2ClientsParams(List clientRegistrations) { + Map domainParamsMap = new HashMap<>(); + for (OAuth2ClientRegistration clientRegistration : clientRegistrations) { + String domainName = clientRegistration.getDomainName(); + OAuth2ClientsDomainParams domainParams = domainParamsMap.computeIfAbsent(domainName, + key -> new OAuth2ClientsDomainParams(domainName, clientRegistration.getRedirectUriTemplate(), new ArrayList<>()) + ); + domainParams.getClientRegistrations() + .add(toClientRegistrationDto(clientRegistration)); + } + return new OAuth2ClientsParams(new ArrayList<>(domainParamsMap.values())); + } + + public static ClientRegistrationDto toClientRegistrationDto(OAuth2ClientRegistration oAuth2ClientRegistration) { + return ClientRegistrationDto.builder() + .id(oAuth2ClientRegistration.getId()) + .createdTime(oAuth2ClientRegistration.getCreatedTime()) + .mapperConfig(oAuth2ClientRegistration.getMapperConfig()) + .clientId(oAuth2ClientRegistration.getClientId()) + .clientSecret(oAuth2ClientRegistration.getClientSecret()) + .authorizationUri(oAuth2ClientRegistration.getAuthorizationUri()) + .accessTokenUri(oAuth2ClientRegistration.getAccessTokenUri()) + .scope(oAuth2ClientRegistration.getScope()) + .userInfoUri(oAuth2ClientRegistration.getUserInfoUri()) + .userNameAttributeName(oAuth2ClientRegistration.getUserNameAttributeName()) + .jwkSetUri(oAuth2ClientRegistration.getJwkSetUri()) + .clientAuthenticationMethod(oAuth2ClientRegistration.getClientAuthenticationMethod()) + .loginButtonLabel(oAuth2ClientRegistration.getLoginButtonLabel()) + .loginButtonIcon(oAuth2ClientRegistration.getLoginButtonIcon()) + .build(); + } + + public static OAuth2ClientRegistration toClientRegistration(TenantId tenantId, String domainName, String redirectUriTemplate, + ClientRegistrationDto clientRegistrationDto) { + OAuth2ClientRegistration clientRegistration = new OAuth2ClientRegistration(); + clientRegistration.setId(clientRegistrationDto.getId()); + clientRegistration.setTenantId(tenantId); + clientRegistration.setCreatedTime(clientRegistrationDto.getCreatedTime()); + clientRegistration.setDomainName(domainName); + clientRegistration.setRedirectUriTemplate(redirectUriTemplate); + clientRegistration.setMapperConfig(clientRegistrationDto.getMapperConfig()); + clientRegistration.setClientId(clientRegistrationDto.getClientId()); + clientRegistration.setClientSecret(clientRegistrationDto.getClientSecret()); + clientRegistration.setAuthorizationUri(clientRegistrationDto.getAuthorizationUri()); + clientRegistration.setAccessTokenUri(clientRegistrationDto.getAccessTokenUri()); + clientRegistration.setScope(clientRegistrationDto.getScope()); + clientRegistration.setUserInfoUri(clientRegistrationDto.getUserInfoUri()); + clientRegistration.setUserNameAttributeName(clientRegistrationDto.getUserNameAttributeName()); + clientRegistration.setJwkSetUri(clientRegistrationDto.getJwkSetUri()); + clientRegistration.setClientAuthenticationMethod(clientRegistrationDto.getClientAuthenticationMethod()); + clientRegistration.setLoginButtonLabel(clientRegistrationDto.getLoginButtonLabel()); + clientRegistration.setLoginButtonIcon(clientRegistrationDto.getLoginButtonIcon()); + return clientRegistration; + } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java index 34b10c2af9..d1f6b6f8e8 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -38,6 +38,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import static org.thingsboard.server.dao.oauth2.OAuth2Utils.ALLOW_OAUTH2_CONFIGURATION; +import static org.thingsboard.server.dao.oauth2.OAuth2Utils.toClientRegistrations; public class BaseOAuth2ServiceTest extends AbstractServiceTest { @@ -90,9 +91,13 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testCreateNewSystemParams() { OAuth2ClientRegistration clientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID); - OAuth2ClientRegistration savedClientRegistration = oAuth2Service.saveClientRegistration(clientRegistration); + OAuth2ClientsParams savedClientsParams = oAuth2Service.saveClientsParams(TenantId.SYS_TENANT_ID, OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(clientRegistration))); + Assert.assertNotNull(savedClientsParams); - Assert.assertNotNull(savedClientRegistration); + List savedClientRegistrations = OAuth2Utils.toClientRegistrations(TenantId.SYS_TENANT_ID, savedClientsParams); + Assert.assertEquals(1, savedClientRegistrations.size()); + + OAuth2ClientRegistration savedClientRegistration = savedClientRegistrations.get(0); Assert.assertNotNull(savedClientRegistration.getId()); clientRegistration.setId(savedClientRegistration.getId()); clientRegistration.setCreatedTime(savedClientRegistration.getCreatedTime()); @@ -102,12 +107,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testFindSystemParamsByTenant() { OAuth2ClientRegistration clientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID); - oAuth2Service.saveClientRegistration(clientRegistration); + oAuth2Service.saveClientsParams(TenantId.SYS_TENANT_ID, OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(clientRegistration))); - List clientRegistrationsByTenantId = oAuth2Service.findClientRegistrationsByTenantId(TenantId.SYS_TENANT_ID); - Assert.assertEquals(1, clientRegistrationsByTenantId.size()); + OAuth2ClientsParams foundClientsParams = oAuth2Service.findClientsParamsByTenantId(TenantId.SYS_TENANT_ID); + Assert.assertEquals(1, foundClientsParams.getOAuth2DomainDtos().size()); Assert.assertEquals(1, oAuth2Service.findAllClientRegistrations().size()); - OAuth2ClientRegistration foundClientRegistration = clientRegistrationsByTenantId.get(0); + + List foundClientRegistrations = OAuth2Utils.toClientRegistrations(TenantId.SYS_TENANT_ID, foundClientsParams); + OAuth2ClientRegistration foundClientRegistration = foundClientRegistrations.get(0); Assert.assertNotNull(foundClientRegistration); clientRegistration.setId(foundClientRegistration.getId()); clientRegistration.setCreatedTime(foundClientRegistration.getCreatedTime()); @@ -117,7 +124,13 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testCreateNewTenantParams() { OAuth2ClientRegistration clientRegistration = validClientRegistration(tenantId); - OAuth2ClientRegistration savedClientRegistration = oAuth2Service.saveClientRegistration(clientRegistration); + OAuth2ClientsParams savedClientsParams = oAuth2Service.saveClientsParams(tenantId, OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(clientRegistration))); + Assert.assertNotNull(savedClientsParams); + + List savedClientRegistrations = OAuth2Utils.toClientRegistrations(tenantId, savedClientsParams); + Assert.assertEquals(1, savedClientRegistrations.size()); + + OAuth2ClientRegistration savedClientRegistration = savedClientRegistrations.get(0); Assert.assertNotNull(savedClientRegistration); Assert.assertNotNull(savedClientRegistration.getId()); @@ -129,12 +142,15 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testFindTenantParams() { OAuth2ClientRegistration clientRegistration = validClientRegistration(tenantId); - oAuth2Service.saveClientRegistration(clientRegistration); + oAuth2Service.saveClientsParams(tenantId, OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(clientRegistration))); - List clientRegistrationsByTenantId = oAuth2Service.findClientRegistrationsByTenantId(tenantId); - Assert.assertEquals(1, clientRegistrationsByTenantId.size()); + OAuth2ClientsParams foundClientsParams = oAuth2Service.findClientsParamsByTenantId(tenantId); + Assert.assertEquals(1, foundClientsParams.getOAuth2DomainDtos().size()); Assert.assertEquals(1, oAuth2Service.findAllClientRegistrations().size()); - OAuth2ClientRegistration foundClientRegistration = clientRegistrationsByTenantId.get(0); + + List foundClientRegistrations = OAuth2Utils.toClientRegistrations(tenantId, foundClientsParams); + OAuth2ClientRegistration foundClientRegistration = foundClientRegistrations.get(0); + Assert.assertNotNull(foundClientRegistration); clientRegistration.setId(foundClientRegistration.getId()); clientRegistration.setCreatedTime(foundClientRegistration.getCreatedTime()); @@ -146,18 +162,20 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(tenantId); OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID); - OAuth2ClientRegistration savedTenantClientRegistration = oAuth2Service.saveClientRegistration(tenantClientRegistration); - OAuth2ClientRegistration savedSysAdminClientRegistration = oAuth2Service.saveClientRegistration(sysAdminClientRegistration); + OAuth2ClientsParams savedTenantClientsParams = oAuth2Service.saveClientsParams(tenantId, + OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(tenantClientRegistration))); + OAuth2ClientsParams savedSysAdminClientsParams = oAuth2Service.saveClientsParams(TenantId.SYS_TENANT_ID, + OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(sysAdminClientRegistration))); Assert.assertEquals(2, oAuth2Service.findAllClientRegistrations().size()); - Assert.assertEquals(savedTenantClientRegistration, oAuth2Service.findClientRegistrationsByTenantId(tenantId).get(0)); - Assert.assertEquals(savedSysAdminClientRegistration, oAuth2Service.findClientRegistrationsByTenantId(TenantId.SYS_TENANT_ID).get(0)); + Assert.assertEquals(savedTenantClientsParams, oAuth2Service.findClientsParamsByTenantId(tenantId)); + Assert.assertEquals(savedSysAdminClientsParams, oAuth2Service.findClientsParamsByTenantId(TenantId.SYS_TENANT_ID)); - Assert.assertEquals(savedTenantClientRegistration, - oAuth2Service.findClientRegistration(savedTenantClientRegistration.getUuidId())); - Assert.assertEquals(savedSysAdminClientRegistration, - oAuth2Service.findClientRegistration(savedSysAdminClientRegistration.getUuidId())); + OAuth2ClientRegistration savedTenantClientRegistration = toClientRegistrations(tenantId, savedTenantClientsParams).get(0); + Assert.assertEquals(savedTenantClientRegistration, oAuth2Service.findClientRegistration(savedTenantClientRegistration.getUuidId())); + OAuth2ClientRegistration savedSysAdminClientRegistration = toClientRegistrations(TenantId.SYS_TENANT_ID, savedSysAdminClientsParams).get(0); + Assert.assertEquals(savedSysAdminClientRegistration, oAuth2Service.findClientRegistration(savedSysAdminClientRegistration.getUuidId())); } @Test @@ -166,8 +184,8 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(tenantId, testDomainName); OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID, testDomainName); - oAuth2Service.saveClientRegistration(tenantClientRegistration); - oAuth2Service.saveClientRegistration(sysAdminClientRegistration); + oAuth2Service.saveClientsParams(tenantId, OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(tenantClientRegistration))); + oAuth2Service.saveClientsParams(TenantId.SYS_TENANT_ID, OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(sysAdminClientRegistration))); List oAuth2Clients = oAuth2Service.getOAuth2Clients(testDomainName); @@ -183,8 +201,8 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { String testDomainName = "test_domain"; OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(tenantId, testDomainName); OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID, testDomainName); - oAuth2Service.saveClientRegistration(tenantClientRegistration); - oAuth2Service.saveClientRegistration(sysAdminClientRegistration); + oAuth2Service.saveClientsParams(tenantId, OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(tenantClientRegistration))); + oAuth2Service.saveClientsParams(TenantId.SYS_TENANT_ID, OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(sysAdminClientRegistration))); List oAuth2Clients = oAuth2Service.getOAuth2Clients("random-domain"); Assert.assertTrue(oAuth2Clients.isEmpty()); } @@ -193,8 +211,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { public void testDeleteOAuth2ClientRegistration() { OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(tenantId); OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID); - OAuth2ClientRegistration savedTenantRegistration = oAuth2Service.saveClientRegistration(tenantClientRegistration); - OAuth2ClientRegistration savedSysAdminRegistration = oAuth2Service.saveClientRegistration(sysAdminClientRegistration); + + OAuth2ClientsParams savedTenantClientsParams = oAuth2Service.saveClientsParams(tenantId, + OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(tenantClientRegistration))); + OAuth2ClientsParams savedSysAdminClientsParams = oAuth2Service.saveClientsParams(TenantId.SYS_TENANT_ID, + OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(sysAdminClientRegistration))); + + OAuth2ClientRegistration savedTenantRegistration = toClientRegistrations(tenantId, savedTenantClientsParams).get(0); + OAuth2ClientRegistration savedSysAdminRegistration = toClientRegistrations(TenantId.SYS_TENANT_ID, savedSysAdminClientsParams).get(0); oAuth2Service.deleteClientRegistrationById(tenantId, savedTenantRegistration.getId()); List foundRegistrations = oAuth2Service.findAllClientRegistrations(); @@ -204,29 +228,39 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testDeleteTenantOAuth2ClientRegistrations() { - oAuth2Service.saveClientRegistration(validClientRegistration(tenantId)); - oAuth2Service.saveClientRegistration(validClientRegistration(tenantId)); - oAuth2Service.saveClientRegistration(validClientRegistration(tenantId)); + oAuth2Service.saveClientsParams(tenantId, OAuth2Utils.toOAuth2ClientsParams(Arrays.asList( + validClientRegistration(tenantId, "domain"), + validClientRegistration(tenantId, "domain"), + validClientRegistration(tenantId, "domain") + ))); Assert.assertEquals(3, oAuth2Service.findAllClientRegistrations().size()); - Assert.assertEquals(3, oAuth2Service.findClientRegistrationsByTenantId(tenantId).size()); + Assert.assertEquals(1, oAuth2Service.findClientsParamsByTenantId(tenantId).getOAuth2DomainDtos().size()); oAuth2Service.deleteClientRegistrationsByTenantId(tenantId); Assert.assertEquals(0, oAuth2Service.findAllClientRegistrations().size()); - Assert.assertEquals(0, oAuth2Service.findClientRegistrationsByTenantId(tenantId).size()); + Assert.assertEquals(0, oAuth2Service.findClientsParamsByTenantId(tenantId).getOAuth2DomainDtos().size()); } @Test public void testDeleteTenantDomainOAuth2ClientRegistrations() { - oAuth2Service.saveClientRegistration(validClientRegistration(tenantId, "domain1")); - oAuth2Service.saveClientRegistration(validClientRegistration(tenantId, "domain1")); - oAuth2Service.saveClientRegistration(validClientRegistration(tenantId, "domain2")); - oAuth2Service.saveClientRegistration(validClientRegistration(TenantId.SYS_TENANT_ID, "domain2")); + oAuth2Service.saveClientsParams(tenantId, OAuth2Utils.toOAuth2ClientsParams(Arrays.asList( + validClientRegistration(tenantId, "domain1"), + validClientRegistration(tenantId, "domain1"), + validClientRegistration(tenantId, "domain2") + ))); + oAuth2Service.saveClientsParams(TenantId.SYS_TENANT_ID, OAuth2Utils.toOAuth2ClientsParams(Arrays.asList( + validClientRegistration(TenantId.SYS_TENANT_ID, "domain2") + ))); Assert.assertEquals(4, oAuth2Service.findAllClientRegistrations().size()); - Assert.assertEquals(3, oAuth2Service.findClientRegistrationsByTenantId(tenantId).size()); + OAuth2ClientsParams tenantClientsParams = oAuth2Service.findClientsParamsByTenantId(tenantId); + List tenantClientRegistrations = toClientRegistrations(tenantId, tenantClientsParams); + Assert.assertEquals(2, tenantClientsParams.getOAuth2DomainDtos().size()); + Assert.assertEquals(3, tenantClientRegistrations.size()); oAuth2Service.deleteClientRegistrationsByDomain(tenantId, "domain1"); Assert.assertEquals(2, oAuth2Service.findAllClientRegistrations().size()); - Assert.assertEquals(1, oAuth2Service.findClientRegistrationsByTenantId(tenantId).size()); + Assert.assertEquals(1, oAuth2Service.findClientsParamsByTenantId(tenantId).getOAuth2DomainDtos().size()); + Assert.assertEquals(1, toClientRegistrations(tenantId, oAuth2Service.findClientsParamsByTenantId(tenantId)).size()); } private void updateTenantAllowOAuth2Setting(Boolean allowOAuth2) throws IOException { From ae92f065e68d996432823928f2b2a64e532c5f81 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 8 Sep 2020 17:05:13 +0300 Subject: [PATCH 087/463] Add select login provider templated --- ui-ngx/src/app/core/http/admin.service.ts | 5 +- ui-ngx/src/app/core/services/menu.service.ts | 10 +- .../home/pages/admin/admin-routing.module.ts | 6 +- .../admin/oauth2-settings.component.html | 415 ++++++++++-------- .../admin/oauth2-settings.component.scss | 23 + .../pages/admin/oauth2-settings.component.ts | 55 ++- .../src/app/shared/models/settings.models.ts | 14 + .../assets/locale/locale.constant-en_US.json | 9 +- 8 files changed, 325 insertions(+), 212 deletions(-) diff --git a/ui-ngx/src/app/core/http/admin.service.ts b/ui-ngx/src/app/core/http/admin.service.ts index 1fb6f717c3..38a7c6e5e9 100644 --- a/ui-ngx/src/app/core/http/admin.service.ts +++ b/ui-ngx/src/app/core/http/admin.service.ts @@ -20,6 +20,7 @@ import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { AdminSettings, + ClientProviderTemplated, MailServerSettings, OAuth2Settings, SecuritySettings, @@ -63,8 +64,8 @@ export class AdminService { return this.http.get(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config)); } - public getOAuth2Template(config?: RequestConfig): Observable { - return this.http.get(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config)); + public getOAuth2Template(config?: RequestConfig): Observable> { + return this.http.get>(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config)); } public saveOAuth2Settings(OAuth2Setting: OAuth2Settings, diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index bb39cfa415..7d3503a613 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -120,9 +120,9 @@ export class MenuService { icon: 'security' }, { - name: 'admin.oauth2.settings', + name: 'admin.oauth2.oauth2', type: 'link', - path: '/settings/oauth2-settings', + path: '/settings/oauth2', icon: 'security' } ] @@ -246,13 +246,13 @@ export class MenuService { icon: 'settings', pages: [ { - name: 'admin.oauth2.settings', + name: 'admin.oauth2.oauth2', type: 'link', - path: '/settings/oauth2-settings', + path: '/settings/oauth2', icon: 'security' } ] - }) + }); } return sections; diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts index a6b18cfd29..16635ef53f 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts @@ -81,14 +81,14 @@ const routes: Routes = [ } }, { - path: 'oauth2-settings', + path: 'oauth2', component: OAuth2SettingsComponent, canDeactivate: [ConfirmOnExitGuard], data: { auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], - title: 'admin.oauth2.settings', + title: 'admin.oauth2.oauth2', breadcrumb: { - label: 'admin.oauth2.settings', + label: 'admin.oauth2.oauth2', icon: 'security' } } diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html index bb77210ba8..fce4f0339f 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -19,7 +19,7 @@
- admin.oauth2.settings + admin.oauth2.oauth2
@@ -91,7 +91,7 @@
- Login Provider + admin.oauth2.login-provider {{ provider | uppercase }} @@ -117,223 +117,254 @@ - - -
- - admin.oauth2.access-token-uri - - - {{ 'admin.oauth2.access-token-uri-required' | translate }} - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - - - - admin.oauth2.authorization-uri - - - {{ 'admin.oauth2.authorization-uri-required' | translate }} - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - -
- -
- - admin.oauth2.jwk-set-uri - - - {{ 'admin.oauth2.jwk-set-uri-required' | translate }} - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - - - - admin.oauth2.user-info-uri - - - {{ 'admin.oauth2.user-info-uri-required' | translate }} - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - -
- - - admin.oauth2.client-authentication-method - - - {{ clientAuthenticationMethod | uppercase }} - - - - -
- - admin.oauth2.login-button-label - - - {{ 'admin.oauth2.login-button-label-required' | translate }} - - - - - admin.oauth2.login-button-icon - - -
- - - admin.oauth2.scope - - - {{scope}} - cancel - - - - - {{ 'admin.oauth2.jwk-set-uri-required' | translate }} - - - -
- - - admin.oauth2.user-name-attribute-name - - - {{ 'admin.oauth2.user-name-attribute-name-required' | translate }} - - - -
-
- - {{ 'admin.oauth2.allow-user-creation' | translate }} - - - {{ 'admin.oauth2.activate-user' | translate }} - -
- - - admin.oauth2.type - - - {{ converterTypeExternalUser }} - - - - -
- - admin.oauth2.email-attribute-key - - - {{ 'admin.oauth2.email-attribute-key-required' | translate }} - - - -
+ + + + {{ 'admin.oauth2.custom-setting' | translate }} + + + + + +
- admin.oauth2.first-name-attribute-key - + admin.oauth2.access-token-uri + + + + {{ 'admin.oauth2.access-token-uri-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + - admin.oauth2.last-name-attribute-key - + admin.oauth2.authorization-uri + + + + {{ 'admin.oauth2.authorization-uri-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} +
- - admin.oauth2.tenant-name-strategy - - - {{ tenantNameStrategy }} - - + + admin.oauth2.jwk-set-uri + + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + - - admin.oauth2.tenant-name-pattern - - - {{ 'admin.oauth2.tenant-name-pattern-required' | translate }} + + admin.oauth2.user-info-uri + + + + {{ 'admin.oauth2.user-info-uri-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }}
- admin.oauth2.customer-name-pattern - + admin.oauth2.client-authentication-method + + + {{ clientAuthenticationMethod | uppercase }} + + -
+
- admin.oauth2.default-dashboard-name - + admin.oauth2.login-button-label + + + {{ 'admin.oauth2.login-button-label-required' | translate }} + - - {{ 'admin.oauth2.always-fullscreen' | translate}} - + + admin.oauth2.login-button-icon + +
-
- -
- - admin.oauth2.url - - - {{ 'admin.oauth2.url-required' | translate }} - - - {{ 'admin.oauth2.url-pattern' | translate }} + +
+
+ + {{ 'admin.oauth2.allow-user-creation' | translate }} + + + {{ 'admin.oauth2.activate-user' | translate }} + +
+
+ + + admin.oauth2.scope + + + {{scope}} + cancel + + + + + + + + + admin.oauth2.user-name-attribute-name + + + {{ 'admin.oauth2.user-name-attribute-name-required' | translate }} -
+
- common.username - + admin.oauth2.type + + + {{ converterTypeExternalUser }} + + - - common.password - - -
-
-
-
-
+
+ + admin.oauth2.email-attribute-key + + + {{ 'admin.oauth2.email-attribute-key-required' | translate }} + + + +
+ + admin.oauth2.first-name-attribute-key + + + + + admin.oauth2.last-name-attribute-key + + +
+ +
+ + admin.oauth2.tenant-name-strategy + + + {{ tenantNameStrategy }} + + + + + + admin.oauth2.tenant-name-pattern + + + {{ 'admin.oauth2.tenant-name-pattern-required' | translate }} + + +
+ + + admin.oauth2.customer-name-pattern + + + +
+ + admin.oauth2.default-dashboard-name + + + + + {{ 'admin.oauth2.always-fullscreen' | translate}} + +
+
+ +
+ + admin.oauth2.url + + + {{ 'admin.oauth2.url-required' | translate }} + + + {{ 'admin.oauth2.url-pattern' | translate }} + + + +
+ + common.username + + + + + common.password + + +
+
+
+ + +
+ diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss index d174ba2dcf..ae7e94a5ac 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss @@ -21,9 +21,32 @@ .registration-card{ margin-bottom: 8px; border: 1px solid rgba(0, 0, 0, 0.2); + + .custom-settings{ + font-size: 16px; + .mat-expansion-panel-header{ + padding: 0 2px; + } + } } .container{ margin-bottom: 16px; } } + +:host ::ng-deep{ + .registration-card{ + .custom-settings{ + .mat-expansion-panel-body{ + padding: 0 2px 16px; + } + .mat-tab-label{ + text-transform: none; + } + .mat-form-field-suffix .mat-icon-button .mat-icon{ + font-size: 18px; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index 0459213ee9..074236c006 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -18,6 +18,7 @@ import { ChangeDetectorRef, Component, Inject, OnDestroy, OnInit, ViewContainerR import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ClientAuthenticationMethod, + ClientProviderTemplated, ClientRegistration, DomainParams, MapperConfigType, @@ -50,9 +51,24 @@ import { }) export class OAuth2SettingsComponent extends PageComponent implements OnInit, HasConfirmForm, OnDestroy { - private URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.?+=&%@\-/]*)?$/; + private URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.,?+=&%@\-/]*)?$/; private subscriptions: Subscription[] = []; - private templates = []; + private templates = new Map(); + private defaultProvider = { + providerName: 'Custom', + clientAuthenticationMethod: 'Post', + userNameAttributeName: 'email', + mapperConfig: { + allowUserCreation: true, + activateUser: false, + type: 'BASIC', + basic: { + emailAttributeKey: 'email', + tenantNameStrategy: 'DOMAIN', + alwaysFullScreen: false + } + } + }; readonly separatorKeysCodes: number[] = [ENTER, COMMA]; @@ -99,9 +115,9 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha }); } - private initTemplates(templates: any): void { - this.templates = templates; - templates.map(provider => this.templateProvider.push(provider.name)); + private initTemplates(templates: ClientProviderTemplated[]): void { + templates.map(provider => this.templates.set(provider.name, provider)); + this.templateProvider.push(...Array.from(this.templates.keys())); this.templateProvider.sort(); } @@ -118,7 +134,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha tenantNamePattern: [null], customerNamePattern: [null], defaultDashboardName: [null], - alwaysFullScreen: [false], + alwaysFullScreen: [false] }); this.subscriptions.push(basicGroup.get('tenantNameStrategy').valueChanges.subscribe((domain) => { @@ -209,7 +225,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha accessTokenUri: ['', [Validators.required, Validators.pattern(this.URL_REGEXP)]], authorizationUri: ['', [Validators.required, Validators.pattern(this.URL_REGEXP)]], scope: this.fb.array([], [Validators.required]), - jwkSetUri: ['', [Validators.required, Validators.pattern(this.URL_REGEXP)]], + jwkSetUri: ['', [Validators.pattern(this.URL_REGEXP)]], userInfoUri: ['', [Validators.required, Validators.pattern(this.URL_REGEXP)]], clientAuthenticationMethod: ['POST', [Validators.required]], userNameAttributeName: ['email', [Validators.required]], @@ -233,6 +249,27 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } })); + this.subscriptions.push(clientRegistration.get('providerName').valueChanges.subscribe((provider) => { + (clientRegistration.get('scope') as FormArray).clear(); + if (provider === 'Custom') { + clientRegistration.reset(this.defaultProvider, {emitEvent: false}); + clientRegistration.get('accessTokenUri').enable(); + clientRegistration.get('authorizationUri').enable(); + clientRegistration.get('jwkSetUri').enable(); + clientRegistration.get('userInfoUri').enable(); + } else { + const template = this.templates.get(provider); + template.scope.forEach(() => { + (clientRegistration.get('scope') as FormArray).push(this.fb.control('')); + }); + clientRegistration.get('accessTokenUri').disable(); + clientRegistration.get('authorizationUri').disable(); + clientRegistration.get('jwkSetUri').disable(); + clientRegistration.get('userInfoUri').disable(); + clientRegistration.patchValue(this.templates.get(provider), {emitEvent: false}); + } + })); + if (registrationData) { registrationData.scope.forEach(() => { (clientRegistration.get('scope') as FormArray).push(this.fb.control('')); @@ -366,4 +403,8 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } }); } + + toggleEditMode(control: AbstractControl, path: string) { + control.get(path).disabled ? control.get(path).enable() : control.get(path).disable(); + } } diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index 2016a309c6..8fe83aee09 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -14,6 +14,9 @@ /// limitations under the License. /// +import { EntityId } from '@shared/models/id/entity-id'; +import { TenantId } from '@shared/models/id/tenant-id'; + export const smtpPortPattern: RegExp = /^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/; export interface AdminSettings { @@ -75,6 +78,17 @@ export interface DomainParams { clientRegistrations: ClientRegistration[]; } +export interface ClientProviderTemplated extends ClientRegistration{ + additionalInfo: string; + comment: string; + createdTime: number; + helpLink: string; + id: EntityId; + name: string; + providerId: string; + tenantId: TenantId; +} + export interface ClientRegistration { loginButtonLabel: string; loginButtonIcon: string; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index aea8c1cfe4..802575c0ff 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -128,7 +128,7 @@ "add-provider": "Add provider", "settings": "Settings", "oauth2": { - "settings": "OAuth2 Settings", + "oauth2": "OAuth2", "registration-id": "Registration ID", "registration-id-required": "Registration ID is required.", "registration-id-unique": "Registration ID need to unique for the system.", @@ -145,7 +145,6 @@ "redirect-uri-template": "Redirect URI template", "redirect-uri-template-required": "Redirect URI template is required.", "jwk-set-uri": "JSON Web Key URI", - "jwk-set-uri-required": "JSON Web Key URI is required.", "user-info-uri": "User info URI", "user-info-uri-required": "User info URI is required.", "client-authentication-method": "Client authentication method", @@ -173,7 +172,11 @@ "delete-domain-title": "Are you sure you want to delete the domain '{{domainName}}'?", "delete-domain-text": "Be careful, after the confirmation a domain and all registration data will be unavailable.", "delete-registration-title": "Are you sure you want to delete the registration '{{name}}'?", - "delete-registration-text": "Be careful, after the confirmation a registration data will be unavailable." + "delete-registration-text": "Be careful, after the confirmation a registration data will be unavailable.", + "login-provider": "Login provider", + "custom-setting": "Custom settings", + "general": "General", + "mapper": "Mapper" } }, "alarm": { From d5cfccc1cb3526329a417dca3d4810263a3b6908 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 17 Sep 2020 10:36:50 +0300 Subject: [PATCH 088/463] Updated license header --- .../common/data/oauth2/ClientRegistrationDto.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java index 3394d92f0e..6f762c0e7a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.common.data.oauth2; import com.fasterxml.jackson.annotation.JsonProperty; From bfc3e75fc9707aacee3970252ae0b3ae8fd928d4 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 17 Sep 2020 11:14:27 +0300 Subject: [PATCH 089/463] Unpacked Domain objects from ClientParams --- .../server/controller/OAuth2Controller.java | 16 ++-- .../server/dao/oauth2/OAuth2Service.java | 6 +- .../data/oauth2/OAuth2ClientsParams.java | 33 ------- .../server/dao/oauth2/OAuth2ServiceImpl.java | 23 +++-- .../server/dao/oauth2/OAuth2Utils.java | 8 +- .../dao/service/BaseOAuth2ServiceTest.java | 90 +++++++++---------- 6 files changed, 66 insertions(+), 110 deletions(-) delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index 1067b1f647..1f6f5e0ccf 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -16,21 +16,17 @@ package org.thingsboard.server.controller; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; -import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.*; import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -61,14 +57,14 @@ public class OAuth2Controller extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/oauth2/config", method = RequestMethod.GET, produces = "application/json") @ResponseBody - public OAuth2ClientsParams getCurrentClientsParams() throws ThingsboardException { + public List getCurrentClientsParams() throws ThingsboardException { try { Authority authority = getCurrentUser().getAuthority(); checkOAuth2ConfigPermissions(Operation.READ); if (Authority.SYS_ADMIN.equals(authority)) { - return oAuth2Service.findClientsParamsByTenantId(TenantId.SYS_TENANT_ID); + return oAuth2Service.findDomainsParamsByTenantId(TenantId.SYS_TENANT_ID); } else if (Authority.TENANT_ADMIN.equals(authority)) { - return oAuth2Service.findClientsParamsByTenantId(getCurrentUser().getTenantId()); + return oAuth2Service.findDomainsParamsByTenantId(getCurrentUser().getTenantId()); } else { throw new IllegalStateException("Authority " + authority + " cannot get client registrations."); } @@ -80,7 +76,7 @@ public class OAuth2Controller extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/oauth2/config", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) - public OAuth2ClientsParams saveClientParams(@RequestBody OAuth2ClientsParams clientsParams) throws ThingsboardException { + public List saveClientParams(@RequestBody List domainsParams) throws ThingsboardException { try { TenantId tenantId; Authority authority = getCurrentUser().getAuthority(); @@ -91,13 +87,13 @@ public class OAuth2Controller extends BaseController { } else { throw new IllegalStateException("Authority " + authority + " cannot save client registrations."); } - List clientRegistrationDtos = clientsParams.getOAuth2DomainDtos().stream() + List clientRegistrationDtos = domainsParams.stream() .flatMap(domainParams -> domainParams.getClientRegistrations().stream()) .collect(Collectors.toList()); for (ClientRegistrationDto clientRegistrationDto : clientRegistrationDtos) { checkEntity(clientRegistrationDto.getId(), () -> tenantId, Resource.OAUTH2_CONFIGURATION); } - return oAuth2Service.saveClientsParams(tenantId, clientsParams); + return oAuth2Service.saveDomainsParams(tenantId, domainsParams); } catch (Exception e) { throw handleException(e); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java index bc366b130a..2f7156756a 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java @@ -19,7 +19,7 @@ import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientsDomainParams; import java.util.List; import java.util.UUID; @@ -27,9 +27,9 @@ import java.util.UUID; public interface OAuth2Service { List getOAuth2Clients(String domainName); - OAuth2ClientsParams saveClientsParams(TenantId tenantId, OAuth2ClientsParams clientsParams); + List saveDomainsParams(TenantId tenantId, List domainsParams); - OAuth2ClientsParams findClientsParamsByTenantId(TenantId tenantId); + List findDomainsParamsByTenantId(TenantId tenantId); OAuth2ClientRegistration findClientRegistration(UUID id); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java deleted file mode 100644 index b42571b2d6..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.common.data.oauth2; - -import lombok.*; -import org.thingsboard.server.common.data.id.TenantId; - -import java.util.Collection; -import java.util.List; -import java.util.Objects; - -@EqualsAndHashCode -@Data -@ToString -@Builder(toBuilder = true) -@NoArgsConstructor -@AllArgsConstructor -public class OAuth2ClientsParams { - private List oAuth2DomainDtos; -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index a5464cd8aa..70b0f1edfd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -66,21 +66,21 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se @Override @Transactional - public OAuth2ClientsParams saveClientsParams(TenantId tenantId, OAuth2ClientsParams clientsParams) { - log.trace("Executing saveClientsParams [{}] [{}]", tenantId, clientsParams); - clientParamsValidator.accept(tenantId, clientsParams); - List inputClientRegistrations = OAuth2Utils.toClientRegistrations(tenantId, clientsParams); + public List saveDomainsParams(TenantId tenantId, List domainsParams) { + log.trace("Executing saveDomainsParams [{}] [{}]", tenantId, domainsParams); + clientParamsValidator.accept(tenantId, domainsParams); + List inputClientRegistrations = OAuth2Utils.toClientRegistrations(tenantId, domainsParams); List savedClientRegistrations = inputClientRegistrations.stream() .map(clientRegistration -> clientRegistrationDao.save(clientRegistration.getTenantId(), clientRegistration)) .collect(Collectors.toList()); - return OAuth2Utils.toOAuth2ClientsParams(savedClientRegistrations); + return OAuth2Utils.toDomainsParams(savedClientRegistrations); } @Override - public OAuth2ClientsParams findClientsParamsByTenantId(TenantId tenantId) { - log.trace("Executing findClientsParamsByTenantId [{}]", tenantId); + public List findDomainsParamsByTenantId(TenantId tenantId) { + log.trace("Executing findDomainsParamsByTenantId [{}]", tenantId); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - return OAuth2Utils.toOAuth2ClientsParams(clientRegistrationDao.findByTenantId(tenantId.getId())); + return OAuth2Utils.toDomainsParams(clientRegistrationDao.findByTenantId(tenantId.getId())); } @Override @@ -135,12 +135,11 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se } } - private final BiConsumer clientParamsValidator = (tenantId, clientsParams) -> { - if (clientsParams == null || clientsParams.getOAuth2DomainDtos() == null - || clientsParams.getOAuth2DomainDtos().isEmpty()) { + private final BiConsumer> clientParamsValidator = (tenantId, domainsParams) -> { + if (domainsParams == null || domainsParams.isEmpty()) { throw new DataValidationException("Domain params should be specified!"); } - for (OAuth2ClientsDomainParams domainParams : clientsParams.getOAuth2DomainDtos()) { + for (OAuth2ClientsDomainParams domainParams : domainsParams) { if (StringUtils.isEmpty(domainParams.getDomainName())) { throw new DataValidationException("Domain name should be specified!"); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java index 3570b8a1e9..0434f1d621 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java @@ -38,8 +38,8 @@ public class OAuth2Utils { return client; } - public static List toClientRegistrations(TenantId tenantId, OAuth2ClientsParams clientsParams) { - return clientsParams.getOAuth2DomainDtos().stream() + public static List toClientRegistrations(TenantId tenantId, List domainsParams) { + return domainsParams.stream() .flatMap(domainParams -> domainParams.getClientRegistrations().stream() .map(clientRegistrationDto -> OAuth2Utils.toClientRegistration(tenantId, domainParams.getDomainName(), domainParams.getRedirectUriTemplate(), clientRegistrationDto) @@ -47,7 +47,7 @@ public class OAuth2Utils { .collect(Collectors.toList()); } - public static OAuth2ClientsParams toOAuth2ClientsParams(List clientRegistrations) { + public static List toDomainsParams(List clientRegistrations) { Map domainParamsMap = new HashMap<>(); for (OAuth2ClientRegistration clientRegistration : clientRegistrations) { String domainName = clientRegistration.getDomainName(); @@ -57,7 +57,7 @@ public class OAuth2Utils { domainParams.getClientRegistrations() .add(toClientRegistrationDto(clientRegistration)); } - return new OAuth2ClientsParams(new ArrayList<>(domainParamsMap.values())); + return new ArrayList<>(domainParamsMap.values()); } public static ClientRegistrationDto toClientRegistrationDto(OAuth2ClientRegistration oAuth2ClientRegistration) { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java index d1f6b6f8e8..65db6aa212 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -15,27 +15,21 @@ */ package org.thingsboard.server.dao.service; -import org.apache.commons.lang3.tuple.Pair; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.*; import org.thingsboard.server.dao.attributes.AttributesService; -import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.dao.oauth2.OAuth2Utils; -import javax.transaction.Transactional; import java.io.IOException; import java.util.*; import java.util.stream.Collectors; -import java.util.stream.Stream; import static org.thingsboard.server.dao.oauth2.OAuth2Utils.ALLOW_OAUTH2_CONFIGURATION; import static org.thingsboard.server.dao.oauth2.OAuth2Utils.toClientRegistrations; @@ -91,10 +85,10 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testCreateNewSystemParams() { OAuth2ClientRegistration clientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID); - OAuth2ClientsParams savedClientsParams = oAuth2Service.saveClientsParams(TenantId.SYS_TENANT_ID, OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(clientRegistration))); - Assert.assertNotNull(savedClientsParams); + List savedDomainsParams = oAuth2Service.saveDomainsParams(TenantId.SYS_TENANT_ID, OAuth2Utils.toDomainsParams(Collections.singletonList(clientRegistration))); + Assert.assertNotNull(savedDomainsParams); - List savedClientRegistrations = OAuth2Utils.toClientRegistrations(TenantId.SYS_TENANT_ID, savedClientsParams); + List savedClientRegistrations = OAuth2Utils.toClientRegistrations(TenantId.SYS_TENANT_ID, savedDomainsParams); Assert.assertEquals(1, savedClientRegistrations.size()); OAuth2ClientRegistration savedClientRegistration = savedClientRegistrations.get(0); @@ -107,13 +101,13 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testFindSystemParamsByTenant() { OAuth2ClientRegistration clientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID); - oAuth2Service.saveClientsParams(TenantId.SYS_TENANT_ID, OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(clientRegistration))); + oAuth2Service.saveDomainsParams(TenantId.SYS_TENANT_ID, OAuth2Utils.toDomainsParams(Collections.singletonList(clientRegistration))); - OAuth2ClientsParams foundClientsParams = oAuth2Service.findClientsParamsByTenantId(TenantId.SYS_TENANT_ID); - Assert.assertEquals(1, foundClientsParams.getOAuth2DomainDtos().size()); + List foundDomainsParams = oAuth2Service.findDomainsParamsByTenantId(TenantId.SYS_TENANT_ID); + Assert.assertEquals(1, foundDomainsParams.size()); Assert.assertEquals(1, oAuth2Service.findAllClientRegistrations().size()); - List foundClientRegistrations = OAuth2Utils.toClientRegistrations(TenantId.SYS_TENANT_ID, foundClientsParams); + List foundClientRegistrations = OAuth2Utils.toClientRegistrations(TenantId.SYS_TENANT_ID, foundDomainsParams); OAuth2ClientRegistration foundClientRegistration = foundClientRegistrations.get(0); Assert.assertNotNull(foundClientRegistration); clientRegistration.setId(foundClientRegistration.getId()); @@ -124,10 +118,10 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testCreateNewTenantParams() { OAuth2ClientRegistration clientRegistration = validClientRegistration(tenantId); - OAuth2ClientsParams savedClientsParams = oAuth2Service.saveClientsParams(tenantId, OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(clientRegistration))); - Assert.assertNotNull(savedClientsParams); + List savedDomainsParams = oAuth2Service.saveDomainsParams(tenantId, OAuth2Utils.toDomainsParams(Collections.singletonList(clientRegistration))); + Assert.assertNotNull(savedDomainsParams); - List savedClientRegistrations = OAuth2Utils.toClientRegistrations(tenantId, savedClientsParams); + List savedClientRegistrations = OAuth2Utils.toClientRegistrations(tenantId, savedDomainsParams); Assert.assertEquals(1, savedClientRegistrations.size()); OAuth2ClientRegistration savedClientRegistration = savedClientRegistrations.get(0); @@ -142,13 +136,13 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testFindTenantParams() { OAuth2ClientRegistration clientRegistration = validClientRegistration(tenantId); - oAuth2Service.saveClientsParams(tenantId, OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(clientRegistration))); + oAuth2Service.saveDomainsParams(tenantId, OAuth2Utils.toDomainsParams(Collections.singletonList(clientRegistration))); - OAuth2ClientsParams foundClientsParams = oAuth2Service.findClientsParamsByTenantId(tenantId); - Assert.assertEquals(1, foundClientsParams.getOAuth2DomainDtos().size()); + List foundDomainsParams = oAuth2Service.findDomainsParamsByTenantId(tenantId); + Assert.assertEquals(1, foundDomainsParams.size()); Assert.assertEquals(1, oAuth2Service.findAllClientRegistrations().size()); - List foundClientRegistrations = OAuth2Utils.toClientRegistrations(tenantId, foundClientsParams); + List foundClientRegistrations = OAuth2Utils.toClientRegistrations(tenantId, foundDomainsParams); OAuth2ClientRegistration foundClientRegistration = foundClientRegistrations.get(0); Assert.assertNotNull(foundClientRegistration); @@ -162,19 +156,19 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(tenantId); OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID); - OAuth2ClientsParams savedTenantClientsParams = oAuth2Service.saveClientsParams(tenantId, - OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(tenantClientRegistration))); - OAuth2ClientsParams savedSysAdminClientsParams = oAuth2Service.saveClientsParams(TenantId.SYS_TENANT_ID, - OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(sysAdminClientRegistration))); + List savedTenantDomainsParams = oAuth2Service.saveDomainsParams(tenantId, + OAuth2Utils.toDomainsParams(Collections.singletonList(tenantClientRegistration))); + List savedSysAdminDomainsParams = oAuth2Service.saveDomainsParams(TenantId.SYS_TENANT_ID, + OAuth2Utils.toDomainsParams(Collections.singletonList(sysAdminClientRegistration))); Assert.assertEquals(2, oAuth2Service.findAllClientRegistrations().size()); - Assert.assertEquals(savedTenantClientsParams, oAuth2Service.findClientsParamsByTenantId(tenantId)); - Assert.assertEquals(savedSysAdminClientsParams, oAuth2Service.findClientsParamsByTenantId(TenantId.SYS_TENANT_ID)); + Assert.assertEquals(savedTenantDomainsParams, oAuth2Service.findDomainsParamsByTenantId(tenantId)); + Assert.assertEquals(savedSysAdminDomainsParams, oAuth2Service.findDomainsParamsByTenantId(TenantId.SYS_TENANT_ID)); - OAuth2ClientRegistration savedTenantClientRegistration = toClientRegistrations(tenantId, savedTenantClientsParams).get(0); + OAuth2ClientRegistration savedTenantClientRegistration = toClientRegistrations(tenantId, savedTenantDomainsParams).get(0); Assert.assertEquals(savedTenantClientRegistration, oAuth2Service.findClientRegistration(savedTenantClientRegistration.getUuidId())); - OAuth2ClientRegistration savedSysAdminClientRegistration = toClientRegistrations(TenantId.SYS_TENANT_ID, savedSysAdminClientsParams).get(0); + OAuth2ClientRegistration savedSysAdminClientRegistration = toClientRegistrations(TenantId.SYS_TENANT_ID, savedSysAdminDomainsParams).get(0); Assert.assertEquals(savedSysAdminClientRegistration, oAuth2Service.findClientRegistration(savedSysAdminClientRegistration.getUuidId())); } @@ -184,8 +178,8 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(tenantId, testDomainName); OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID, testDomainName); - oAuth2Service.saveClientsParams(tenantId, OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(tenantClientRegistration))); - oAuth2Service.saveClientsParams(TenantId.SYS_TENANT_ID, OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(sysAdminClientRegistration))); + oAuth2Service.saveDomainsParams(tenantId, OAuth2Utils.toDomainsParams(Collections.singletonList(tenantClientRegistration))); + oAuth2Service.saveDomainsParams(TenantId.SYS_TENANT_ID, OAuth2Utils.toDomainsParams(Collections.singletonList(sysAdminClientRegistration))); List oAuth2Clients = oAuth2Service.getOAuth2Clients(testDomainName); @@ -201,8 +195,8 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { String testDomainName = "test_domain"; OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(tenantId, testDomainName); OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID, testDomainName); - oAuth2Service.saveClientsParams(tenantId, OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(tenantClientRegistration))); - oAuth2Service.saveClientsParams(TenantId.SYS_TENANT_ID, OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(sysAdminClientRegistration))); + oAuth2Service.saveDomainsParams(tenantId, OAuth2Utils.toDomainsParams(Collections.singletonList(tenantClientRegistration))); + oAuth2Service.saveDomainsParams(TenantId.SYS_TENANT_ID, OAuth2Utils.toDomainsParams(Collections.singletonList(sysAdminClientRegistration))); List oAuth2Clients = oAuth2Service.getOAuth2Clients("random-domain"); Assert.assertTrue(oAuth2Clients.isEmpty()); } @@ -212,13 +206,13 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(tenantId); OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID); - OAuth2ClientsParams savedTenantClientsParams = oAuth2Service.saveClientsParams(tenantId, - OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(tenantClientRegistration))); - OAuth2ClientsParams savedSysAdminClientsParams = oAuth2Service.saveClientsParams(TenantId.SYS_TENANT_ID, - OAuth2Utils.toOAuth2ClientsParams(Collections.singletonList(sysAdminClientRegistration))); + List savedTenantDomainsParams = oAuth2Service.saveDomainsParams(tenantId, + OAuth2Utils.toDomainsParams(Collections.singletonList(tenantClientRegistration))); + List savedSysAdminDomainsParams = oAuth2Service.saveDomainsParams(TenantId.SYS_TENANT_ID, + OAuth2Utils.toDomainsParams(Collections.singletonList(sysAdminClientRegistration))); - OAuth2ClientRegistration savedTenantRegistration = toClientRegistrations(tenantId, savedTenantClientsParams).get(0); - OAuth2ClientRegistration savedSysAdminRegistration = toClientRegistrations(TenantId.SYS_TENANT_ID, savedSysAdminClientsParams).get(0); + OAuth2ClientRegistration savedTenantRegistration = toClientRegistrations(tenantId, savedTenantDomainsParams).get(0); + OAuth2ClientRegistration savedSysAdminRegistration = toClientRegistrations(TenantId.SYS_TENANT_ID, savedSysAdminDomainsParams).get(0); oAuth2Service.deleteClientRegistrationById(tenantId, savedTenantRegistration.getId()); List foundRegistrations = oAuth2Service.findAllClientRegistrations(); @@ -228,39 +222,39 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testDeleteTenantOAuth2ClientRegistrations() { - oAuth2Service.saveClientsParams(tenantId, OAuth2Utils.toOAuth2ClientsParams(Arrays.asList( + oAuth2Service.saveDomainsParams(tenantId, OAuth2Utils.toDomainsParams(Arrays.asList( validClientRegistration(tenantId, "domain"), validClientRegistration(tenantId, "domain"), validClientRegistration(tenantId, "domain") ))); Assert.assertEquals(3, oAuth2Service.findAllClientRegistrations().size()); - Assert.assertEquals(1, oAuth2Service.findClientsParamsByTenantId(tenantId).getOAuth2DomainDtos().size()); + Assert.assertEquals(1, oAuth2Service.findDomainsParamsByTenantId(tenantId).size()); oAuth2Service.deleteClientRegistrationsByTenantId(tenantId); Assert.assertEquals(0, oAuth2Service.findAllClientRegistrations().size()); - Assert.assertEquals(0, oAuth2Service.findClientsParamsByTenantId(tenantId).getOAuth2DomainDtos().size()); + Assert.assertEquals(0, oAuth2Service.findDomainsParamsByTenantId(tenantId).size()); } @Test public void testDeleteTenantDomainOAuth2ClientRegistrations() { - oAuth2Service.saveClientsParams(tenantId, OAuth2Utils.toOAuth2ClientsParams(Arrays.asList( + oAuth2Service.saveDomainsParams(tenantId, OAuth2Utils.toDomainsParams(Arrays.asList( validClientRegistration(tenantId, "domain1"), validClientRegistration(tenantId, "domain1"), validClientRegistration(tenantId, "domain2") ))); - oAuth2Service.saveClientsParams(TenantId.SYS_TENANT_ID, OAuth2Utils.toOAuth2ClientsParams(Arrays.asList( + oAuth2Service.saveDomainsParams(TenantId.SYS_TENANT_ID, OAuth2Utils.toDomainsParams(Arrays.asList( validClientRegistration(TenantId.SYS_TENANT_ID, "domain2") ))); Assert.assertEquals(4, oAuth2Service.findAllClientRegistrations().size()); - OAuth2ClientsParams tenantClientsParams = oAuth2Service.findClientsParamsByTenantId(tenantId); - List tenantClientRegistrations = toClientRegistrations(tenantId, tenantClientsParams); - Assert.assertEquals(2, tenantClientsParams.getOAuth2DomainDtos().size()); + List tenantDomainsParams = oAuth2Service.findDomainsParamsByTenantId(tenantId); + List tenantClientRegistrations = toClientRegistrations(tenantId, tenantDomainsParams); + Assert.assertEquals(2, tenantDomainsParams.size()); Assert.assertEquals(3, tenantClientRegistrations.size()); oAuth2Service.deleteClientRegistrationsByDomain(tenantId, "domain1"); Assert.assertEquals(2, oAuth2Service.findAllClientRegistrations().size()); - Assert.assertEquals(1, oAuth2Service.findClientsParamsByTenantId(tenantId).getOAuth2DomainDtos().size()); - Assert.assertEquals(1, toClientRegistrations(tenantId, oAuth2Service.findClientsParamsByTenantId(tenantId)).size()); + Assert.assertEquals(1, oAuth2Service.findDomainsParamsByTenantId(tenantId).size()); + Assert.assertEquals(1, toClientRegistrations(tenantId, oAuth2Service.findDomainsParamsByTenantId(tenantId)).size()); } private void updateTenantAllowOAuth2Setting(Boolean allowOAuth2) throws IOException { From 42a80efdbb641337f0b94911888168a312d59378 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 17 Sep 2020 11:18:57 +0300 Subject: [PATCH 090/463] Moved OAuth2Template endpoints to separate controller --- .../OAuth2ConfigTemplateController.java | 93 +++++++++++++++++++ .../server/controller/OAuth2Controller.java | 58 ------------ 2 files changed, 93 insertions(+), 58 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java new file mode 100644 index 0000000000..ed2c479e1f --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java @@ -0,0 +1,93 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; + +import java.util.List; + +@RestController +@TbCoreComponent +@RequestMapping("/api/oauth2/config/template") +@Slf4j +public class OAuth2ConfigTemplateController extends BaseController { + private static final String CLIENT_REGISTRATION_TEMPLATE_ID = "clientRegistrationTemplateId"; + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + @RequestMapping(method = RequestMethod.POST) + @ResponseStatus(value = HttpStatus.OK) + public OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(@RequestBody OAuth2ClientRegistrationTemplate clientRegistrationTemplate) throws ThingsboardException { + try { + clientRegistrationTemplate.setTenantId(getCurrentUser().getTenantId()); + checkEntity(clientRegistrationTemplate.getId(), clientRegistrationTemplate, Resource.OAUTH2_CONFIGURATION_TEMPLATE); + return oAuth2ConfigTemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + @RequestMapping(value = "/{clientRegistrationTemplateId}", method = RequestMethod.DELETE) + @ResponseStatus(value = HttpStatus.OK) + public void deleteClientRegistrationTemplate(@PathVariable(CLIENT_REGISTRATION_TEMPLATE_ID) String strClientRegistrationTemplateId) throws ThingsboardException { + checkParameter(CLIENT_REGISTRATION_TEMPLATE_ID, strClientRegistrationTemplateId); + try { + OAuth2ClientRegistrationTemplateId clientRegistrationTemplateId = new OAuth2ClientRegistrationTemplateId(toUUID(strClientRegistrationTemplateId)); + OAuth2ClientRegistrationTemplate clientRegistrationTemplate = checkOAuth2ClientRegistrationTemplateId(clientRegistrationTemplateId, Operation.DELETE); + oAuth2ConfigTemplateService.deleteClientRegistrationTemplateById(clientRegistrationTemplateId); + + logEntityAction(clientRegistrationTemplateId, clientRegistrationTemplate, + null, + ActionType.DELETED, null, strClientRegistrationTemplateId); + + } catch (Exception e) { + + logEntityAction(emptyId(EntityType.OAUTH2_CLIENT_REGISTRATION_TEMPLATE), + null, + null, + ActionType.DELETED, e, strClientRegistrationTemplateId); + + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(method = RequestMethod.GET, produces = "application/json") + @ResponseBody + public List getClientRegistrationTemplates() throws ThingsboardException { + try { + checkOAuth2ConfigTemplatePermissions(Operation.READ); + return oAuth2ConfigTemplateService.findAllClientRegistrationTemplates(); + } catch (Exception e) { + throw handleException(e); + } + } + + private void checkOAuth2ConfigTemplatePermissions(Operation operation) throws ThingsboardException { + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, operation); + } +} diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index 1f6f5e0ccf..b4a8ead9db 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -23,7 +23,6 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.*; import org.thingsboard.server.common.data.security.Authority; @@ -42,7 +41,6 @@ import java.util.stream.Collectors; public class OAuth2Controller extends BaseController { private static final String CLIENT_REGISTRATION_ID = "clientRegistrationId"; private static final String DOMAIN = "domain"; - private static final String CLIENT_REGISTRATION_TEMPLATE_ID = "clientRegistrationTemplateId"; @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) @ResponseBody @@ -99,19 +97,6 @@ public class OAuth2Controller extends BaseController { } } - @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") - @RequestMapping(value = "/oauth2/config/template", method = RequestMethod.POST) - @ResponseStatus(value = HttpStatus.OK) - public OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(@RequestBody OAuth2ClientRegistrationTemplate clientRegistrationTemplate) throws ThingsboardException { - try { - clientRegistrationTemplate.setTenantId(getCurrentUser().getTenantId()); - checkEntity(clientRegistrationTemplate.getId(), clientRegistrationTemplate, Resource.OAUTH2_CONFIGURATION_TEMPLATE); - return oAuth2ConfigTemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate); - } catch (Exception e) { - throw handleException(e); - } - } - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/oauth2/config/{clientRegistrationId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) @@ -160,31 +145,6 @@ public class OAuth2Controller extends BaseController { } } - @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") - @RequestMapping(value = "/oauth2/config/template/{clientRegistrationTemplateId}", method = RequestMethod.DELETE) - @ResponseStatus(value = HttpStatus.OK) - public void deleteClientRegistrationTemplate(@PathVariable(CLIENT_REGISTRATION_TEMPLATE_ID) String strClientRegistrationTemplateId) throws ThingsboardException { - checkParameter(CLIENT_REGISTRATION_TEMPLATE_ID, strClientRegistrationTemplateId); - try { - OAuth2ClientRegistrationTemplateId clientRegistrationTemplateId = new OAuth2ClientRegistrationTemplateId(toUUID(strClientRegistrationTemplateId)); - OAuth2ClientRegistrationTemplate clientRegistrationTemplate = checkOAuth2ClientRegistrationTemplateId(clientRegistrationTemplateId, Operation.DELETE); - oAuth2ConfigTemplateService.deleteClientRegistrationTemplateById(clientRegistrationTemplateId); - - logEntityAction(clientRegistrationTemplateId, clientRegistrationTemplate, - null, - ActionType.DELETED, null, strClientRegistrationTemplateId); - - } catch (Exception e) { - - logEntityAction(emptyId(EntityType.OAUTH2_CLIENT_REGISTRATION_TEMPLATE), - null, - null, - ActionType.DELETED, e, strClientRegistrationTemplateId); - - throw handleException(e); - } - } - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/oauth2/config/isAllowed", method = RequestMethod.GET) @ResponseBody @@ -196,25 +156,7 @@ public class OAuth2Controller extends BaseController { } } - - - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - @RequestMapping(value = "/oauth2/config/template", method = RequestMethod.GET, produces = "application/json") - @ResponseBody - public List getClientRegistrationTemplates() throws ThingsboardException { - try { - checkOAuth2ConfigTemplatePermissions(Operation.READ); - return oAuth2ConfigTemplateService.findAllClientRegistrationTemplates(); - } catch (Exception e) { - throw handleException(e); - } - } - private void checkOAuth2ConfigPermissions(Operation operation) throws ThingsboardException { accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION, operation); } - - private void checkOAuth2ConfigTemplatePermissions(Operation operation) throws ThingsboardException { - accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, operation); - } } From 0cab0325854989a6d4fc277e24fd0e2524dcec58 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 17 Sep 2020 11:35:42 +0300 Subject: [PATCH 091/463] Added loginButton label and icon to OAuth2Template --- .../src/main/data/upgrade/3.1.1/schema_update.sql | 3 ++- .../oauth2/OAuth2ClientRegistrationTemplate.java | 6 ++++-- .../server/dao/model/ModelConstants.java | 3 ++- .../sql/OAuth2ClientRegistrationTemplateEntity.java | 13 ++++++++----- .../dao/oauth2/OAuth2ConfigTemplateServiceImpl.java | 2 +- dao/src/main/resources/sql/schema-entities-hsql.sql | 3 ++- dao/src/main/resources/sql/schema-entities.sql | 3 ++- .../BaseOAuth2ConfigTemplateServiceTest.java | 3 ++- 8 files changed, 23 insertions(+), 13 deletions(-) diff --git a/application/src/main/data/upgrade/3.1.1/schema_update.sql b/application/src/main/data/upgrade/3.1.1/schema_update.sql index 58c4ff5b67..6064ec8833 100644 --- a/application/src/main/data/upgrade/3.1.1/schema_update.sql +++ b/application/src/main/data/upgrade/3.1.1/schema_update.sql @@ -75,7 +75,8 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( basic_default_dashboard_name varchar(255), basic_always_full_screen boolean, comment varchar, - icon varchar(255), + login_button_icon varchar(255), + login_button_label varchar(255), help_link varchar(255), CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) ); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java index 7f16940e7f..e066fa05d2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java @@ -44,7 +44,8 @@ public class OAuth2ClientRegistrationTemplate extends BaseData clientRegistrationTemplateValidator = + private final DataValidator clientRegistrationTemplateValidator = new DataValidator() { @Override diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index c9ba64d646..0b7f1c848b 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -348,7 +348,8 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( basic_default_dashboard_name varchar(255), basic_always_full_screen boolean, comment varchar, - icon varchar(255), + login_button_icon varchar(255), + login_button_label varchar(255), help_link varchar(255), CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) ); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 9d50e687e8..a8f014ab22 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -373,7 +373,8 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( basic_default_dashboard_name varchar(255), basic_always_full_screen boolean, comment varchar, - icon varchar(255), + login_button_icon varchar(255), + login_button_label varchar(255), help_link varchar(255), CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) ); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java index 8836474420..44aadaafb1 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java @@ -125,7 +125,8 @@ public class BaseOAuth2ConfigTemplateServiceTest extends AbstractServiceTest { clientRegistrationTemplate.setJwkSetUri("jwkSetUri"); clientRegistrationTemplate.setClientAuthenticationMethod("clientAuthenticationMethod"); clientRegistrationTemplate.setComment("comment"); - clientRegistrationTemplate.setIcon("icon"); + clientRegistrationTemplate.setLoginButtonIcon("icon"); + clientRegistrationTemplate.setLoginButtonLabel("label"); clientRegistrationTemplate.setHelpLink("helpLink"); return clientRegistrationTemplate; } From a3f93554ee48e249ac48981ef537469eaeccd82c Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 17 Sep 2020 11:37:22 +0300 Subject: [PATCH 092/463] Updated OAuth2Templates --- .../json/system/oauth2_config_templates/facebook_config.json | 3 ++- .../json/system/oauth2_config_templates/github_config.json | 3 ++- .../json/system/oauth2_config_templates/google_config.json | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json b/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json index 1c81a443f3..81097ccc49 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json @@ -14,6 +14,7 @@ "tenantNameStrategy": "DOMAIN" }, "comment": null, - "icon": "mdi:facebook", + "loginButtonIcon": "mdi:facebook", + "loginButtonLabel": "Login with Facebook", "helpLink": "https://developers.facebook.com/docs/facebook-login/web#logindialog" } \ No newline at end of file diff --git a/application/src/main/data/json/system/oauth2_config_templates/github_config.json b/application/src/main/data/json/system/oauth2_config_templates/github_config.json index 17dc1fda4a..819ea4b6b6 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/github_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/github_config.json @@ -9,6 +9,7 @@ "userNameAttributeName": "login", "basic": {}, "comment": "In order to log into ThingsBoard you need to have user's email. You may configure and use Custom OAuth2 Mapper to get email information. Please refer to Github Documentation", - "icon": "mdi:github", + "loginButtonIcon": "mdi:github", + "loginButtonLabel": "Login with Github", "helpLink": "https://docs.github.com/en/developers/apps/creating-an-oauth-app" } \ No newline at end of file diff --git a/application/src/main/data/json/system/oauth2_config_templates/google_config.json b/application/src/main/data/json/system/oauth2_config_templates/google_config.json index 99053b68e6..2573b5ecd0 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/google_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/google_config.json @@ -15,6 +15,7 @@ "tenantNameStrategy": "DOMAIN" }, "comment": null, - "icon": "mdi:google", + "loginButtonIcon": "mdi:google", + "loginButtonLabel": "Login with Google", "helpLink": "https://developers.google.com/adwords/api/docs/guides/authentication" } \ No newline at end of file From 26bb51f0e4f6f746abd4ff80cd34944e3c5e1fc9 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 17 Sep 2020 19:31:01 +0300 Subject: [PATCH 093/463] Updated OAuth2 templated --- .../system/oauth2_config_templates/facebook_config.json | 6 +++--- .../json/system/oauth2_config_templates/github_config.json | 6 +++--- .../json/system/oauth2_config_templates/google_config.json | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json b/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json index 81097ccc49..9b059e81c1 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json @@ -5,7 +5,7 @@ "scope": ["email","public_profile"], "jwkSetUri": null, "userInfoUri": "https://graph.facebook.com/me?fields=id,name,first_name,last_name,email", - "clientAuthenticationMethod": "basic", + "clientAuthenticationMethod": "BASIC", "userNameAttributeName": "email", "basic": { "emailAttributeKey": "email", @@ -15,6 +15,6 @@ }, "comment": null, "loginButtonIcon": "mdi:facebook", - "loginButtonLabel": "Login with Facebook", + "loginButtonLabel": "Facebook", "helpLink": "https://developers.facebook.com/docs/facebook-login/web#logindialog" -} \ No newline at end of file +} diff --git a/application/src/main/data/json/system/oauth2_config_templates/github_config.json b/application/src/main/data/json/system/oauth2_config_templates/github_config.json index 819ea4b6b6..58812fdc1e 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/github_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/github_config.json @@ -5,11 +5,11 @@ "scope": ["read:user","user:email"], "jwkSetUri": null, "userInfoUri": "https://api.github.com/user", - "clientAuthenticationMethod": "basic", + "clientAuthenticationMethod": "BASIC", "userNameAttributeName": "login", "basic": {}, "comment": "In order to log into ThingsBoard you need to have user's email. You may configure and use Custom OAuth2 Mapper to get email information. Please refer to Github Documentation", "loginButtonIcon": "mdi:github", - "loginButtonLabel": "Login with Github", + "loginButtonLabel": "Github", "helpLink": "https://docs.github.com/en/developers/apps/creating-an-oauth-app" -} \ No newline at end of file +} diff --git a/application/src/main/data/json/system/oauth2_config_templates/google_config.json b/application/src/main/data/json/system/oauth2_config_templates/google_config.json index 2573b5ecd0..2756bcc957 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/google_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/google_config.json @@ -6,7 +6,7 @@ "scope": ["email","openid","profile"], "jwkSetUri": "https://www.googleapis.com/oauth2/v3/certs", "userInfoUri": "https://openidconnect.googleapis.com/v1/userinfo", - "clientAuthenticationMethod": "basic", + "clientAuthenticationMethod": "BASIC", "userNameAttributeName": "email", "basic": { "emailAttributeKey": "email", @@ -16,6 +16,6 @@ }, "comment": null, "loginButtonIcon": "mdi:google", - "loginButtonLabel": "Login with Google", + "loginButtonLabel": "Google", "helpLink": "https://developers.google.com/adwords/api/docs/guides/authentication" -} \ No newline at end of file +} From 5275a3302ab8d9fd7927c90f549723edc604d076 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Fri, 18 Sep 2020 11:19:41 +0300 Subject: [PATCH 094/463] Added 'additionalInfo' to ClientRegistration --- .../SearchTextBasedWithAdditionalInfo.java | 1 + .../data/oauth2/ClientRegistrationDto.java | 2 ++ .../data/oauth2/OAuth2ClientRegistration.java | 14 ++++++-- .../OAuth2ClientRegistrationTemplate.java | 32 ++++++------------- .../server/dao/model/ModelConstants.java | 1 + .../sql/OAuth2ClientRegistrationEntity.java | 8 +++++ .../server/dao/oauth2/OAuth2Utils.java | 2 ++ .../BaseOAuth2ConfigTemplateServiceTest.java | 1 + .../dao/service/BaseOAuth2ServiceTest.java | 1 + 9 files changed, 37 insertions(+), 25 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBasedWithAdditionalInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBasedWithAdditionalInfo.java index 8dc9bf6abc..34dc711e0c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBasedWithAdditionalInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBasedWithAdditionalInfo.java @@ -59,6 +59,7 @@ public abstract class SearchTextBasedWithAdditionalInfo ext } public void setAdditionalInfo(JsonNode addInfo) { + // TODO why set additionalInfoBytes to [110,117,108,108] if JsonNode is null setJson(addInfo, json -> this.additionalInfo = json, bytes -> this.additionalInfoBytes = bytes); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java index 6f762c0e7a..eef79bda6c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.data.oauth2; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; import lombok.*; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; import org.thingsboard.server.common.data.id.TenantId; @@ -43,4 +44,5 @@ public class ClientRegistrationDto { private String clientAuthenticationMethod; private String loginButtonLabel; private String loginButtonIcon; + private JsonNode additionalInfo; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java index 1e7ff2a1e7..64cfb59511 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java @@ -16,10 +16,13 @@ package org.thingsboard.server.common.data.oauth2; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.*; -import org.thingsboard.server.common.data.BaseData; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; import org.thingsboard.server.common.data.id.TenantId; @@ -29,7 +32,7 @@ import java.util.List; @Data @ToString(exclude = {"clientSecret"}) @NoArgsConstructor -public class OAuth2ClientRegistration extends BaseData implements HasTenantId, HasName { +public class OAuth2ClientRegistration extends SearchTextBasedWithAdditionalInfo implements HasTenantId, HasName { private TenantId tenantId; private String domainName; @@ -71,4 +74,9 @@ public class OAuth2ClientRegistration extends BaseData implements HasTenantId, HasName { +public class OAuth2ClientRegistrationTemplate extends SearchTextBasedWithAdditionalInfo implements HasTenantId, HasName { private TenantId tenantId; private String providerId; @@ -48,14 +48,6 @@ public class OAuth2ClientRegistrationTemplate extends BaseData additionalInfo, () -> additionalInfoBytes); - } - - public void setAdditionalInfo(JsonNode addInfo) { - SearchTextBasedWithAdditionalInfo.setJson(addInfo, json -> this.additionalInfo = json, bytes -> this.additionalInfoBytes = bytes); } @Override public String getName() { return providerId; } + + @Override + public String getSearchText() { + return getName(); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 2705be5d47..b9d75ab6d0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -390,6 +390,7 @@ public class ModelConstants { public static final String OAUTH2_MAPPER_PASSWORD_PROPERTY = "custom_password"; public static final String OAUTH2_MAPPER_SEND_TOKEN_PROPERTY = "custom_send_token"; public static final String OAUTH2_TEMPLATE_COMMENT_PROPERTY = "comment"; + public static final String OAUTH2_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY; public static final String OAUTH2_TEMPLATE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY; public static final String OAUTH2_TEMPLATE_LOGIN_BUTTON_ICON_PROPERTY = OAUTH2_LOGIN_BUTTON_ICON_PROPERTY; public static final String OAUTH2_TEMPLATE_LOGIN_BUTTON_LABEL_PROPERTY = OAUTH2_LOGIN_BUTTON_LABEL_PROPERTY; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java index 4e99dbbb5f..4f2513f2ce 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java @@ -15,8 +15,10 @@ */ package org.thingsboard.server.dao.model.sql; +import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; import org.thingsboard.server.common.data.id.TenantId; @@ -98,6 +100,10 @@ public class OAuth2ClientRegistrationEntity extends BaseSqlEntity Date: Fri, 18 Sep 2020 16:11:49 +0300 Subject: [PATCH 095/463] Provision init --- .../server/controller/BaseController.java | 6 + .../device/DeviceProvisionServiceImpl.java | 267 ++++++++++++++++++ .../dao/device/DeviceProvisionService.java | 24 ++ .../device/provision/ProvisionRequest.java | 29 ++ .../device/provision/ProvisionResponse.java | 27 ++ .../provision/ProvisionResponseStatus.java | 24 ++ .../server/common/data/DataConstants.java | 9 + .../server/common/data/DeviceProfileType.java | 3 +- .../server/common/data/audit/ActionType.java | 4 +- .../profile/DeviceProfileConfiguration.java | 3 +- .../data/device/profile/MqttTopics.java | 4 + .../ProvisionDeviceProfileConfiguration.java | 57 ++++ .../ProvisionRequestValidationStrategy.java | 23 ++ ...rovisionRequestValidationStrategyType.java | 20 ++ common/queue/src/main/proto/queue.proto | 39 +++ .../transport/mqtt/MqttTransportHandler.java | 79 +++++- .../mqtt/adaptors/JsonMqttAdaptor.java | 22 ++ .../mqtt/adaptors/MqttTransportAdaptor.java | 8 + .../mqtt/session/DeviceSessionCtx.java | 5 + .../common/transport/TransportService.java | 4 +- .../transport/adaptor/JsonConverter.java | 70 +++++ .../service/DefaultTransportService.java | 9 + .../server/dao/audit/AuditLogServiceImpl.java | 8 + .../server/dao/device/DeviceProfileDao.java | 4 + .../device/DeviceProvisionServiceImpl.java | 19 ++ .../sql/device/DeviceProfileRepository.java | 20 ++ .../dao/sql/device/JpaDeviceProfileDao.java | 10 + 27 files changed, 786 insertions(+), 11 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponse.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileConfiguration.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategy.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategyType.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionServiceImpl.java diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 8434f67d1d..7b68b18bd6 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -706,6 +706,12 @@ public abstract class BaseController { case ASSIGNED_TO_TENANT: msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT; break; + case PROVISION_SUCCESS: + msgType = DataConstants.PROVISION_SUCCESS; + break; + case PROVISION_FAILURE: + msgType = DataConstants.PROVISION_FAILURE; + break; } if (!StringUtils.isEmpty(msgType)) { try { diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java new file mode 100644 index 0000000000..e7a64c046b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -0,0 +1,267 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.device; + +import com.datastax.oss.driver.api.core.uuid.Uuids; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileType; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileConfiguration; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.audit.AuditLogService; +import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.dao.device.DeviceProfileDao; +import org.thingsboard.server.dao.device.DeviceProvisionService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.device.provision.ProvisionRequest; +import org.thingsboard.server.dao.device.provision.ProvisionResponse; +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.queue.TbQueueCallback; +import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.service.state.DeviceStateService; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.locks.ReentrantLock; + +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; + + +@Service +@Slf4j +public class DeviceProvisionServiceImpl implements DeviceProvisionService { + + protected TbQueueProducer> ruleEngineMsgProducer; + + private static final String DEVICE_PROVISION_STATE = "provisionState"; + private static final String PROVISIONED_STATE = "provisioned"; + + private static final UserId PROVISION_USER_ID = UserId.fromString(NULL_UUID.toString()); + + private final ReentrantLock deviceCreationLock = new ReentrantLock(); + + @Autowired + DeviceProfileDao deviceProfileDao; + + @Autowired + DeviceService deviceService; + + @Autowired + DeviceCredentialsService deviceCredentialsService; + + @Autowired + AttributesService attributesService; + + @Autowired + DeviceStateService deviceStateService; + + @Autowired + AuditLogService auditLogService; + + @Autowired + PartitionService partitionService; + + + @Override + public ListenableFuture provisionDevice(ProvisionRequest provisionRequest) { + DeviceProfile targetProfile = deviceProfileDao.findProfileByTenantIdAndProfileDataProvisionConfigurationPair( + TenantId.SYS_TENANT_ID, + provisionRequest.getCredentials().getProvisionDeviceKey(), + provisionRequest.getCredentials().getProvisionDeviceSecret()); + + if (targetProfile.getProfileData().getConfiguration().getType() != DeviceProfileType.PROVISION) { + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); + } + + ProvisionDeviceProfileConfiguration currentProfileConfiguration = (ProvisionDeviceProfileConfiguration) targetProfile.getProfileData().getConfiguration(); + if (!new ProvisionDeviceProfileConfiguration(provisionRequest.getCredentials().getProvisionDeviceKey(), provisionRequest.getCredentials().getProvisionDeviceSecret()).equals(currentProfileConfiguration)) { + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); + } + + Device device = deviceService.findDeviceByTenantIdAndName(targetProfile.getTenantId(), provisionRequest.getDeviceName()); + switch (currentProfileConfiguration.getStrategy()) { + case CHECK_NEW_DEVICE: + if (device == null) { + return createDevice(provisionRequest, targetProfile); + } else { + log.warn("[{}] The device is present and could not be provisioned once more!", device.getName()); + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); + } + case CHECK_PRE_PROVISIONED_DEVICE: + if (device == null) { + log.warn("[{}] Failed to find pre provisioned device!", provisionRequest.getDeviceName()); + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); + } else { + return processProvision(device, provisionRequest); + } + default: + throw new RuntimeException("Strategy is not supported - " + currentProfileConfiguration.getStrategy().name()); + } + } + + private ListenableFuture processProvision(Device device, ProvisionRequest provisionRequest) { + ListenableFuture> provisionStateFuture = attributesService.find(device.getTenantId(), device.getId(), + DataConstants.SERVER_SCOPE, DEVICE_PROVISION_STATE); + ListenableFuture provisionedFuture = Futures.transformAsync(provisionStateFuture, optionalAtr -> { + if (optionalAtr.isPresent()) { + String state = optionalAtr.get().getValueAsString(); + if (state.equals(PROVISIONED_STATE)) { + return Futures.immediateFuture(true); + } else { + log.error("[{}][{}] Unknown provision state: {}!", device.getName(), DEVICE_PROVISION_STATE, state); + return Futures.immediateCancelledFuture(); + } + } + return Futures.transform(saveProvisionStateAttribute(device), input -> false, MoreExecutors.directExecutor()); + }, MoreExecutors.directExecutor()); + if (provisionedFuture.isCancelled()) { + throw new RuntimeException("Unknown provision state!"); + } + return Futures.transform(provisionedFuture, provisioned -> { + if (provisioned) { + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); + return new ProvisionResponse(null, ProvisionResponseStatus.FAILURE); + } + notify(device, provisionRequest, DataConstants.PROVISION_SUCCESS, true); + return new ProvisionResponse(deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()), ProvisionResponseStatus.SUCCESS); + }, MoreExecutors.directExecutor()); + } + + private ListenableFuture createDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { + deviceCreationLock.lock(); + try { + return processCreateDevice(provisionRequest, profile); + } finally { + deviceCreationLock.unlock(); + } + } + + private ListenableFuture processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { + Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName()); + if (device == null) { + Device savedDevice = saveDevice(provisionRequest, profile); + + deviceStateService.onDeviceAdded(savedDevice); + pushDeviceCreatedEventToRuleEngine(savedDevice); + notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true); + + return Futures.transform(saveProvisionStateAttribute(savedDevice), input -> + new ProvisionResponse( + getDeviceCredentials(savedDevice, provisionRequest.getX509CertPubKey()), + ProvisionResponseStatus.SUCCESS), MoreExecutors.directExecutor()); + } + log.warn("[{}] The device is already provisioned!", device.getName()); + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); + } + + private ListenableFuture> saveProvisionStateAttribute(Device device) { + return attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, + Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(DEVICE_PROVISION_STATE, PROVISIONED_STATE), + System.currentTimeMillis()))); + } + + private Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { + Device device = new Device(); + device.setName(provisionRequest.getDeviceName()); + device.setType(provisionRequest.getDeviceType()); + device.setTenantId(profile.getTenantId()); + return deviceService.saveDevice(device); + } + + private DeviceCredentials getDeviceCredentials(Device device, String x509CertPubKey) { + DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); + if (!StringUtils.isEmpty(x509CertPubKey)) { + credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); + credentials.setCredentialsValue(x509CertPubKey); + return deviceCredentialsService.updateDeviceCredentials(device.getTenantId(), credentials); + } + return credentials; + } + + private void notify(Device device, ProvisionRequest provisionRequest, String type, boolean success) { + pushProvisionEventToRuleEngine(provisionRequest, device, type); + logAction(device.getTenantId(), device, success, provisionRequest); + } + + private void pushProvisionEventToRuleEngine(ProvisionRequest request, Device device, String type) { + try { + ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(request); + TbMsg msg = new TbMsg(Uuids.timeBased(), type, device.getId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode), null, null, 0L); + sendToRuleEngine(device.getTenantId(), msg, null); + } catch (JsonProcessingException | IllegalArgumentException e) { + log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), type, e); + } + } + + private void pushDeviceCreatedEventToRuleEngine(Device device) { + try { + ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device); + TbMsg msg = new TbMsg(Uuids.timeBased(), DataConstants.ENTITY_CREATED, device.getId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode), null, null, 0L); + sendToRuleEngine(device.getTenantId(), msg, null); + } catch (JsonProcessingException | IllegalArgumentException e) { + log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), DataConstants.ENTITY_CREATED, e); + } + } + + protected void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) { + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tbMsg.getOriginator()); + TransportProtos.ToRuleEngineMsg msg = TransportProtos.ToRuleEngineMsg.newBuilder().setTbMsg(TbMsg.toByteString(tbMsg)) + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()).build(); + ruleEngineMsgProducer.send(tpi, new TbProtoQueueMsg<>(tbMsg.getId(), msg), callback); + } + + private TbMsgMetaData createTbMsgMetaData(Device device) { + TbMsgMetaData metaData = new TbMsgMetaData(); + metaData.putValue("tenantId", device.getTenantId().toString()); + return metaData; + } + + private void logAction(TenantId tenantId, Device device, boolean success, ProvisionRequest provisionRequest) { + ActionType actionType = success ? ActionType.PROVISION_SUCCESS : ActionType.PROVISION_FAILURE; + auditLogService.logEntityAction(tenantId, null, null, device.getName(), device.getId(), device, actionType, null, provisionRequest); + } +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java new file mode 100644 index 0000000000..28456fb4fe --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.device; + +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.dao.device.provision.ProvisionRequest; +import org.thingsboard.server.dao.device.provision.ProvisionResponse; + +public interface DeviceProvisionService { + ListenableFuture provisionDevice(ProvisionRequest provisionRequest); +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java new file mode 100644 index 0000000000..45eb7f5998 --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.device.provision; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileConfiguration; + +@Data +@AllArgsConstructor +public class ProvisionRequest { + private String deviceName; + private String deviceType; + private String x509CertPubKey; + private ProvisionDeviceProfileConfiguration credentials; +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponse.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponse.java new file mode 100644 index 0000000000..509823eeb2 --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponse.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.device.provision; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.thingsboard.server.common.data.security.DeviceCredentials; + +@Data +@AllArgsConstructor +public class ProvisionResponse { + private DeviceCredentials deviceCredentials; + private ProvisionResponseStatus responseStatus; +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java new file mode 100644 index 0000000000..3c993b0e14 --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.device.provision; + +public enum ProvisionResponseStatus { + + SUCCESS, + NOT_FOUND, + FAILURE + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index 8dc492093c..bd9f4d26b0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -63,6 +63,8 @@ public class DataConstants { public static final String ALARM_CLEAR = "ALARM_CLEAR"; public static final String ENTITY_ASSIGNED_FROM_TENANT = "ENTITY_ASSIGNED_FROM_TENANT"; public static final String ENTITY_ASSIGNED_TO_TENANT = "ENTITY_ASSIGNED_TO_TENANT"; + public static final String PROVISION_SUCCESS = "PROVISION_SUCCESS"; + public static final String PROVISION_FAILURE = "PROVISION_FAILURE"; public static final String RPC_CALL_FROM_SERVER_TO_DEVICE = "RPC_CALL_FROM_SERVER_TO_DEVICE"; @@ -70,4 +72,11 @@ public class DataConstants { public static final String SECRET_KEY_FIELD_NAME = "secretKey"; public static final String DURATION_MS_FIELD_NAME = "durationMs"; + public static final String DEVICE_NAME = "deviceName"; + public static final String DEVICE_TYPE = "deviceType"; + public static final String CERT_PUB_KEY = "x509CertPubKey"; + + public static final String PROVISION_KEY = "provisionDeviceKey"; + public static final String PROVISION_SECRET = "provisionDeviceSecret"; + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileType.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileType.java index 93ca102082..218a5aa5b4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileType.java @@ -16,5 +16,6 @@ package org.thingsboard.server.common.data; public enum DeviceProfileType { - DEFAULT + DEFAULT, + PROVISION } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java index 14a38c810c..e30e20090d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java @@ -42,7 +42,9 @@ public enum ActionType { LOGOUT(false), LOCKOUT(false), ASSIGNED_FROM_TENANT(false), - ASSIGNED_TO_TENANT(false); + ASSIGNED_TO_TENANT(false), + PROVISION_SUCCESS(false), + PROVISION_FAILURE(false); private final boolean isRead; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileConfiguration.java index 3bb3d29c34..aca6d391da 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileConfiguration.java @@ -27,7 +27,8 @@ import org.thingsboard.server.common.data.DeviceProfileType; include = JsonTypeInfo.As.PROPERTY, property = "type") @JsonSubTypes({ - @JsonSubTypes.Type(value = DefaultDeviceProfileConfiguration.class, name = "DEFAULT")}) + @JsonSubTypes.Type(value = DefaultDeviceProfileConfiguration.class, name = "DEFAULT"), + @JsonSubTypes.Type(value = ProvisionDeviceProfileConfiguration.class, name = "PROVISION")}) public interface DeviceProfileConfiguration { @JsonIgnore diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java index 7aa8ddf63c..10e73af309 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java @@ -31,6 +31,8 @@ public class MqttTopics { public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + "/telemetry"; public static final String DEVICE_CLAIM_TOPIC = BASE_DEVICE_API_TOPIC + "/claim"; public static final String DEVICE_ATTRIBUTES_TOPIC = BASE_DEVICE_API_TOPIC + "/attributes"; + public static final String DEVICE_PROVISION_REQUEST_TOPIC = BASE_DEVICE_API_TOPIC + "/provision"; + public static final String DEVICE_PROVISION_RESPONSE_TOPIC = BASE_DEVICE_API_TOPIC + "/provision/response"; public static final String BASE_GATEWAY_API_TOPIC = "v1/gateway"; public static final String GATEWAY_CONNECT_TOPIC = BASE_GATEWAY_API_TOPIC + "/connect"; @@ -41,6 +43,8 @@ public class MqttTopics { public static final String GATEWAY_RPC_TOPIC = BASE_GATEWAY_API_TOPIC + "/rpc"; public static final String GATEWAY_ATTRIBUTES_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + "/attributes/request"; public static final String GATEWAY_ATTRIBUTES_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + "/attributes/response"; + public static final String GATEWAY_PROVISION_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + "/provision"; + public static final String GATEWAY_PROVISION_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + "/provision/response"; private MqttTopics() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileConfiguration.java new file mode 100644 index 0000000000..ee20a51628 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileConfiguration.java @@ -0,0 +1,57 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import org.thingsboard.server.common.data.DeviceProfileType; + +import java.util.Objects; + +@Data +public class ProvisionDeviceProfileConfiguration implements DeviceProfileConfiguration { + + private String provisionDeviceKey; + private String provisionDeviceSecret; + + private ProvisionRequestValidationStrategyType strategy; + + @Override + public DeviceProfileType getType() { + return DeviceProfileType.PROVISION; + } + + @JsonCreator + public ProvisionDeviceProfileConfiguration(@JsonProperty("provisionDeviceKey") String provisionProfileKey, @JsonProperty("provisionDeviceSecret") String provisionProfileSecret) { + this.provisionDeviceKey = provisionProfileKey; + this.provisionDeviceSecret = provisionProfileSecret; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ProvisionDeviceProfileConfiguration that = (ProvisionDeviceProfileConfiguration) o; + return provisionDeviceKey.equals(that.provisionDeviceKey) && + provisionDeviceSecret.equals(that.provisionDeviceSecret); + } + + @Override + public int hashCode() { + return Objects.hash(provisionDeviceKey, provisionDeviceSecret); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategy.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategy.java new file mode 100644 index 0000000000..759940b763 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategy.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile; + +import lombok.Data; + +@Data +public class ProvisionRequestValidationStrategy { + private final ProvisionRequestValidationStrategyType validationStrategyType; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategyType.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategyType.java new file mode 100644 index 0000000000..9a4005612a --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategyType.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile; + +public enum ProvisionRequestValidationStrategyType { + CHECK_NEW_DEVICE, CHECK_PRE_PROVISIONED_DEVICE +} diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 331c17b9d7..4caf51a7b4 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -73,6 +73,11 @@ enum KeyValueType { JSON_V = 4; } +enum CredentialsType { + ACCESS_TOKEN = 0; + X509_CERTIFICATE = 1; +} + message KeyValueProto { string key = 1; KeyValueType type = 2; @@ -241,6 +246,36 @@ message ClaimDeviceMsg { int64 durationMs = 4; } +message DeviceCredentialsProto { + int64 deviceIdMSB = 1; + int64 deviceIdLSB = 2; + CredentialsType credentialsType = 3; + string credentialsId = 4; + string credentialsValue = 5; +} + +message ProvisionDeviceRequestMsg { + string deviceName = 1; + string deviceType = 2; + string x509CertPubKey = 3; + ProvisionDeviceCredentialsMsg provisionDeviceCredentialsMsg = 4; +} + +message ProvisionDeviceCredentialsMsg { + string provisionDeviceKey = 1; + string provisionDeviceSecret = 2; +} + +message ProvisionDeviceResponseMsg { + DeviceCredentialsProto deviceCredentials = 1; + ProvisionResponseStatus provisionResponseStatus = 2; +} + +enum ProvisionResponseStatus { + SUCCESS = 0; + NOT_FOUND = 1; + FAILURE = 2; +} //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level. message SubscriptionInfoProto { int64 lastActivityTime = 1; @@ -266,6 +301,7 @@ message TransportToDeviceActorMsg { ToDeviceRpcResponseMsg toDeviceRPCCallResponse = 6; SubscriptionInfoProto subscriptionInfo = 7; ClaimDeviceMsg claimDevice = 8; + ProvisionDeviceRequestMsg provisionDevice = 9; } message TransportToRuleEngineMsg { @@ -441,6 +477,7 @@ message TransportApiRequestMsg { GetTenantRoutingInfoRequestMsg getTenantRoutingInfoRequestMsg = 4; GetDeviceProfileRequestMsg getDeviceProfileRequestMsg = 5; ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6; +// ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7; } /* Response from ThingsBoard Core Service to Transport Service */ @@ -449,6 +486,7 @@ message TransportApiResponseMsg { GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2; GetTenantRoutingInfoResponseMsg getTenantRoutingInfoResponseMsg = 4; GetDeviceProfileResponseMsg getDeviceProfileResponseMsg = 5; +// ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 6; } /* Messages that are handled by ThingsBoard Core Service */ @@ -491,4 +529,5 @@ message ToTransportMsg { ToServerRpcResponseMsg toServerResponse = 7; DeviceProfileUpdateMsg deviceProfileUpdateMsg = 8; DeviceProfileDeleteMsg deviceProfileDeleteMsg = 9; + ProvisionDeviceResponseMsg provisionResponse = 10; } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 866a4f7627..be913992e8 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -38,7 +38,6 @@ import io.netty.util.ReferenceCountUtil; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.device.profile.MqttTopics; @@ -52,9 +51,8 @@ import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.service.DefaultTransportService; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; -import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; @@ -66,7 +64,6 @@ import javax.net.ssl.SSLPeerUnverifiedException; import javax.security.cert.X509Certificate; import java.io.IOException; import java.net.InetSocketAddress; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -74,9 +71,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED; -import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD; import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED; import static io.netty.handler.codec.mqtt.MqttMessageType.CONNACK; +import static io.netty.handler.codec.mqtt.MqttMessageType.CONNECT; import static io.netty.handler.codec.mqtt.MqttMessageType.PINGRESP; import static io.netty.handler.codec.mqtt.MqttMessageType.PUBACK; import static io.netty.handler.codec.mqtt.MqttMessageType.SUBACK; @@ -136,10 +133,45 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement return; } deviceSessionCtx.setChannel(ctx); + if (CONNECT.equals(msg.fixedHeader().messageType())) { + processConnect(ctx, (MqttConnectMessage) msg); + } else if (deviceSessionCtx.isProvisionOnly()) { + processProvisionSessionMsg(ctx, msg); + } else { + processRegularSessionMsg(ctx, msg); + } + } + + private void processProvisionSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) { switch (msg.fixedHeader().messageType()) { - case CONNECT: - processConnect(ctx, (MqttConnectMessage) msg); + case PUBLISH: + MqttPublishMessage mqttMsg = (MqttPublishMessage) msg; + String topicName = mqttMsg.variableHeader().topicName(); + int msgId = mqttMsg.variableHeader().packetId(); + try { + if (topicName.equals(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC)) { + TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = adaptor.convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg); + transportService.process(deviceSessionCtx.getSessionInfo(), provisionRequestMsg, (TransportServiceCallback) new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg)); + log.trace("[{}][{}] Processing publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId); + } else { + throw new RuntimeException("Unsupported topic for provisioning requests!"); + } + } catch (RuntimeException | AdaptorException e) { + log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); + ctx.close(); + } break; + case PINGREQ: + ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0))); + break; + case DISCONNECT: + ctx.close(); + break; + } + } + + private void processRegularSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) { + switch (msg.fixedHeader().messageType()) { case PUBLISH: processPublish(ctx, (MqttPublishMessage) msg); break; @@ -261,6 +293,37 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement }; } + private class DeviceProvisionCallback implements TransportServiceCallback { + private final ChannelHandlerContext ctx; + private final int msgId; + private final TransportProtos.ProvisionDeviceRequestMsg msg; + + DeviceProvisionCallback(ChannelHandlerContext ctx, int msgId, TransportProtos.ProvisionDeviceRequestMsg msg) { + this.ctx = ctx; + this.msgId = msgId; + this.msg = msg; + } + + @Override + public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg provisionResponseMsg) { + log.trace("[{}] Published msg: {}", sessionId, msg); + if (msgId > 0) { + ctx.writeAndFlush(createMqttPubAckMsg(msgId)); + } + try { + adaptor.convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); + } catch (Exception e) { + log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); + } + } + + @Override + public void onError(Throwable e) { + log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e); + processDisconnect(ctx); + } + } + private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) { if (!checkConnected(ctx, mqttMsg)) { return; @@ -290,6 +353,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement case MqttTopics.GATEWAY_RPC_TOPIC: case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC: case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC: + case MqttTopics.GATEWAY_PROVISION_RESPONSE_TOPIC: + case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC: registerSubQoS(topic, grantedQoSList, reqQoS); break; default: diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java index 7ba6fbeea6..50e36a1c4c 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java @@ -184,6 +184,28 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_RESPONSE_TOPIC + rpcResponse.getRequestId(), JsonConverter.toJson(rpcResponse))); } + @Override + public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ProvisionDeviceResponseMsg provisionResponse) { + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, JsonConverter.toJson(provisionResponse))); + } + + @Override + public TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { + String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false); + try { + return JsonConverter.convertToProvisionRequestMsg(payload); + } catch (IllegalStateException | JsonSyntaxException ex) { + throw new AdaptorException(ex); + } + } + + @Override + public Optional convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ProvisionDeviceResponseMsg responseMsg, int requestId) { + return Optional.of(createMqttPublishMsg(ctx, + MqttTopics.GATEWAY_PROVISION_REQUEST_TOPIC, + JsonConverter.toJson(responseMsg, requestId))); + } + private MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, JsonElement json) { MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0); diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java index d4d36320f3..84a22ca198 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java @@ -24,6 +24,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestM import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; @@ -63,4 +65,10 @@ public interface MqttTransportAdaptor { Optional convertToPublish(MqttDeviceAwareSessionContext ctx, ToServerRpcResponseMsg rpcResponse) throws AdaptorException; + ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException; + + Optional convertToPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException; + + Optional convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse, int requestId) throws AdaptorException; + } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index 3f5e0dc7ad..3faad734d0 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.mqtt.session; import io.netty.channel.ChannelHandlerContext; import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; @@ -40,6 +41,10 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { private ChannelHandlerContext channel; private final AtomicInteger msgIdSeq = new AtomicInteger(0); + @Getter + @Setter + private boolean provisionOnly; + private volatile MqttTopicFilter telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); private volatile MqttTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 3fc8ed96d1..04da376ff3 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -27,6 +27,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfo import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg; @@ -83,6 +84,8 @@ public interface TransportService { void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback callback); + void process(SessionInfoProto sessionInfo, ProvisionDeviceRequestMsg msg, TransportServiceCallback deviceProvisionCallback); + void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener); void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout); @@ -90,5 +93,4 @@ public interface TransportService { void reportActivity(SessionInfoProto sessionInfo); void deregisterSession(SessionInfoProto sessionInfo); - } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 428dfb0912..c63c4d7bb8 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -42,6 +42,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto; import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; @@ -53,6 +54,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; +import java.util.UUID; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -397,6 +399,34 @@ public class JsonConverter { } } + public static JsonObject toJson(ProvisionDeviceResponseMsg payload) { + return toJson(payload, false, 0); + } + + public static JsonObject toJson(ProvisionDeviceResponseMsg payload, int requestId) { + return toJson(payload, true, requestId); + } + + private static JsonObject toJson(ProvisionDeviceResponseMsg payload, boolean toGateway, int requestId) { + JsonObject result = new JsonObject(); + if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.NOT_FOUND) { + result.addProperty("errorMsg", "Provision data was not found!"); + } else if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.FAILURE) { + result.addProperty("errorMsg", "Failed to provision device!"); + } else { + if (toGateway) { + result.addProperty("id", requestId); + } + result.addProperty("deviceId", new DeviceId( + new UUID(payload.getDeviceCredentials().getDeviceIdMSB(), payload.getDeviceCredentials().getDeviceIdLSB())).toString()); + result.addProperty("credentialsType", payload.getDeviceCredentials().getCredentialsType().name()); + result.addProperty("credentialsId", payload.getDeviceCredentials().getCredentialsId()); + result.addProperty("credentialsValue", + StringUtils.isEmpty(payload.getDeviceCredentials().getCredentialsValue()) ? null : payload.getDeviceCredentials().getCredentialsValue()); + } + return result; + } + public static JsonElement toErrorJson(String errorMsg) { JsonObject error = new JsonObject(); error.addProperty("error", errorMsg); @@ -498,4 +528,44 @@ public class JsonConverter { maxStringValueLength = length; } + public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(String json) { + JsonElement jsonElement = new JsonParser().parse(json); + if (jsonElement.isJsonObject()) { + return buildProvisionRequestMsg(jsonElement.getAsJsonObject()); + } else { + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + jsonElement); + } + } + + public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(JsonObject jo) { + return buildProvisionRequestMsg(jo); + } + + private static TransportProtos.ProvisionDeviceRequestMsg buildProvisionRequestMsg(JsonObject jo) { + return TransportProtos.ProvisionDeviceRequestMsg.newBuilder() + .setDeviceName(getStrValue(jo, DataConstants.DEVICE_NAME, true)) + .setDeviceType(getStrValue(jo, DataConstants.DEVICE_TYPE, true)) + .setX509CertPubKey(getStrValue(jo, DataConstants.CERT_PUB_KEY, false)) + .setProvisionDeviceCredentialsMsg(buildProvisionDeviceCredentialsMsg( + getStrValue(jo, DataConstants.PROVISION_KEY, true), + getStrValue(jo, DataConstants.PROVISION_SECRET, true))) + .build(); + } + + private static TransportProtos.ProvisionDeviceCredentialsMsg buildProvisionDeviceCredentialsMsg(String provisionKey, String provisionSecret) { + return TransportProtos.ProvisionDeviceCredentialsMsg.newBuilder() + .setProvisionDeviceKey(provisionKey) + .setProvisionDeviceSecret(provisionSecret) + .build(); + } + private static String getStrValue(JsonObject jo, String field, boolean requiredField) { + if (jo.has(field)) { + return jo.get(field).getAsString(); + } else { + if (requiredField) { + throw new RuntimeException("Failed to find the field " + field + " in JSON body " + jo + "!"); + } + return ""; + } + } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 72727e39de..35bfd00cb0 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -484,6 +484,15 @@ public class DefaultTransportService implements TransportService { } } + @Override + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ProvisionDeviceRequestMsg msg, TransportServiceCallback callback) { + if (checkLimits(sessionInfo, msg, callback)) { + reportActivityInternal(sessionInfo); + sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) + .setProvisionDevice(msg).build(), callback); + } + } + @Override public void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { reportActivityInternal(sessionInfo); diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java index 467e957f74..6b3f1650a2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java @@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.dao.audit.sink.AuditLogSink; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.service.DataValidator; import java.io.PrintWriter; @@ -257,6 +258,13 @@ public class AuditLogServiceImpl implements AuditLogService { actionData.put("os", os); actionData.put("device", device); break; + case PROVISION_SUCCESS: + case PROVISION_FAILURE: + ProvisionRequest request = extractParameter(ProvisionRequest.class, additionalInfo); + if (request != null) { + actionData.set("provisionRequest", objectMapper.valueToTree(request)); + } + break; } return actionData; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java index 267aff358e..5b118180d8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java @@ -38,5 +38,9 @@ public interface DeviceProfileDao extends Dao { DeviceProfileInfo findDefaultDeviceProfileInfo(TenantId tenantId); + DeviceProfileInfo findProfileInfoByTenantIdAndProfileDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret); + + DeviceProfile findProfileByTenantIdAndProfileDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret); + DeviceProfile findByName(TenantId tenantId, String profileName); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionServiceImpl.java new file mode 100644 index 0000000000..e9e1fcd182 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionServiceImpl.java @@ -0,0 +1,19 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.device; + +public class DeviceProvisionServiceImpl { +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java index 8116b711d5..97736cdb46 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java @@ -22,6 +22,7 @@ import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileInfo; +import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; import java.util.UUID; @@ -57,4 +58,23 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository>{'configuration', 'provisionDeviceKey'} = :provisionDeviceKey " + + "AND d.profileData::jsonb->>{'configuration', 'provisionDeviceSecret' = :provisionDeviceSecret}", + nativeQuery = true) + DeviceProfileEntity findProfileByTenantIdAndProfileDataProvisionConfigurationPair(@Param("tenantId") UUID tenantId, + @Param("provisionDeviceKey") String provisionDeviceKey, + @Param("provisionDeviceSecret") String provisionDeviceSecret); + + @Query(value = "SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.type, d.transportType) " + + " FROM DeviceProfileEntity d " + + "WHERE d.tenantId = :tenantId " + + "AND d.profileData::jsonb->>{'configuration', 'provisionDeviceKey'} = :provisionDeviceKey " + + "AND d.profileData::jsonb->>{'configuration', 'provisionDeviceSecret' = :provisionDeviceSecret}", + nativeQuery = true) + DeviceProfileInfo findProfileInfoByTenantIdAndProfileDataProvisionConfigurationPair(@Param("tenantId") UUID tenantId, + @Param("provisionDeviceKey") String provisionDeviceKey, + @Param("provisionDeviceSecret") String provisionDeviceSecret); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java index 9399d27304..b21f8a0ee6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java @@ -80,6 +80,16 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao Date: Mon, 21 Sep 2020 10:53:42 +0300 Subject: [PATCH 096/463] UI updated OAuth2 for new endpoint --- ui-ngx/src/app/core/http/admin.service.ts | 20 +- .../admin/oauth2-settings.component.html | 43 ++-- .../pages/admin/oauth2-settings.component.ts | 220 ++++++++++++------ .../app/shared/models/entity-type.models.ts | 3 +- .../src/app/shared/models/settings.models.ts | 10 +- .../assets/locale/locale.constant-en_US.json | 4 +- 6 files changed, 191 insertions(+), 109 deletions(-) diff --git a/ui-ngx/src/app/core/http/admin.service.ts b/ui-ngx/src/app/core/http/admin.service.ts index 38a7c6e5e9..d11a8f373e 100644 --- a/ui-ngx/src/app/core/http/admin.service.ts +++ b/ui-ngx/src/app/core/http/admin.service.ts @@ -60,17 +60,27 @@ export class AdminService { defaultHttpOptionsFromConfig(config)); } - public getOAuth2Settings(config?: RequestConfig): Observable { - return this.http.get(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config)); + public getOAuth2Settings(config?: RequestConfig): Observable> { + return this.http.get>(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config)); } public getOAuth2Template(config?: RequestConfig): Observable> { return this.http.get>(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config)); } - public saveOAuth2Settings(OAuth2Setting: OAuth2Settings, - config?: RequestConfig): Observable { - return this.http.post('/api/oauth2/config', OAuth2Setting, + public saveOAuth2Settings(OAuth2Setting: OAuth2Settings[], + config?: RequestConfig): Observable> { + return this.http.post>('/api/oauth2/config', OAuth2Setting, + defaultHttpOptionsFromConfig(config)); + } + + public deleteOAuth2Domain(OAuth2Domain: string, config?: RequestConfig) { + return this.http.delete(`/api/oauth2/config/domain/${OAuth2Domain}`, + defaultHttpOptionsFromConfig(config)); + } + + public deleteOAuthCclientRegistrationId(clientRegistrationId: string, config?: RequestConfig) { + return this.http.delete(`/api/oauth2/config/${clientRegistrationId}`, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html index fce4f0339f..4e45e02945 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -30,10 +30,10 @@
- +
- + @@ -75,7 +75,7 @@ class="registration-card mat-elevation-z0"> - {{ registration.get('providerName').value }} + {{ getProviderName(registration) }} @@ -152,7 +155,7 @@ @@ -171,7 +174,7 @@ @@ -185,7 +188,7 @@ @@ -207,7 +210,7 @@ -
+
admin.oauth2.login-button-label @@ -304,7 +307,7 @@ - + admin.oauth2.tenant-name-pattern { if (domain === 'CUSTOM') { - basicGroup.get('tenantNamePattern').setValidators(Validators.required); + basicGroup.get('tenantNamePattern').enable(); } else { - basicGroup.get('tenantNamePattern').clearValidators(); + basicGroup.get('tenantNamePattern').disable(); } })); return basicGroup; } - get formCustomGroup(): FormGroup { + private formCustomGroup(mapperConfigCustom?: MapperConfigCustom): FormGroup { return this.fb.group({ - url: [null, [Validators.required, Validators.pattern(this.URL_REGEXP)]], - username: [null], - password: [null] + 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] }); } private buildOAuth2SettingsForm(): void { this.oauth2SettingsForm = this.fb.group({ - clientsDomainsParams: this.fb.array([], Validators.required) + clientDomains: this.fb.array([]) }); } - private initOAuth2Settings(oauth2Settings: OAuth2Settings): void { - if (oauth2Settings.clientsDomainsParams) { - oauth2Settings.clientsDomainsParams.forEach((domaindomain) => { - this.clientsDomainsParams.push(this.buildSettingsDomain(domaindomain)); + private initOAuth2Settings(oauth2Settings: OAuth2Settings[]): void { + if (oauth2Settings) { + oauth2Settings.forEach((domain) => { + this.clientDomains.push(this.buildSettingsDomain(domain)); }); } } private uniqueDomainValidator(control: AbstractControl): { [key: string]: boolean } | null { - if (control.value !== null && control?.root) { + if (control.root.value.clientDomains?.length > 1) { const listDomainName = []; - control.root.value.clientsDomainsParams.forEach((domain) => { + control.root.value.clientDomains.forEach((domain) => { listDomainName.push(domain.domainName); }); if (listDomainName.indexOf(control.value) > -1) { @@ -183,7 +191,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha return null; } - private buildSettingsDomain(domainParams?: DomainParams): FormGroup { + private buildSettingsDomain(domainParams?: OAuth2Settings): FormGroup { let url = this.window.location.protocol + '//' + this.window.location.hostname; const port = this.window.location.port; if (port !== '80' && port !== '443') { @@ -191,8 +199,13 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } url += '/login/oauth2/code/'; const formDomain = this.fb.group({ - domainName: [null, [Validators.required, Validators.pattern('((?![:/]).)*$'), this.uniqueDomainValidator]], - redirectUriTemplate: [url, [Validators.required, Validators.pattern(this.URL_REGEXP)]], + domainName: [domainParams?.domainName ? domainParams.domainName : this.window.location.hostname, [ + Validators.required, + Validators.pattern('((?![:/]).)*$'), + this.uniqueDomainValidator]], + redirectUriTemplate: [domainParams?.redirectUriTemplate ? domainParams.redirectUriTemplate : url, [ + Validators.required, + Validators.pattern(this.URL_REGEXP)]], clientRegistrations: this.fb.array([], Validators.required) }); @@ -201,7 +214,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha domain = this.window.location.hostname; } const uri = this.window.location.protocol + `//${domain}/login/oauth2/code/`; - formDomain.get('redirectUriTemplate').patchValue(uri); + formDomain.get('redirectUriTemplate').patchValue(uri, {emitEvent: false}); })); if (domainParams) { @@ -216,40 +229,59 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } private buildSettingsRegistration(registrationData?: ClientRegistration): FormGroup { + let additionalInfo = null; + if (registrationData?.additionalInfo) { + additionalInfo = JSON.parse(registrationData.additionalInfo); + if (this.templateProvider.indexOf(additionalInfo.providerName) === -1) { + additionalInfo.providerName = 'Custom'; + } + } const clientRegistration = this.fb.group({ - providerName: ['Custom', [Validators.required]], - loginButtonLabel: [null, [Validators.required]], - loginButtonIcon: [null], - clientId: ['', [Validators.required]], - clientSecret: ['', [Validators.required]], - accessTokenUri: ['', [Validators.required, Validators.pattern(this.URL_REGEXP)]], - authorizationUri: ['', [Validators.required, Validators.pattern(this.URL_REGEXP)]], - scope: this.fb.array([], [Validators.required]), - jwkSetUri: ['', [Validators.pattern(this.URL_REGEXP)]], - userInfoUri: ['', [Validators.required, Validators.pattern(this.URL_REGEXP)]], - clientAuthenticationMethod: ['POST', [Validators.required]], - userNameAttributeName: ['email', [Validators.required]], + id: this.fb.group({ + id: [registrationData?.id?.id ? registrationData.id.id : null], + entityType: [registrationData?.id?.entityType ? registrationData.id.entityType : null] + }), + additionalInfo: this.fb.group({ + providerName: [additionalInfo?.providerName ? additionalInfo?.providerName : 'Custom', Validators.required] + }), + loginButtonLabel: [registrationData?.loginButtonLabel ? registrationData.loginButtonLabel : null, Validators.required], + loginButtonIcon: [registrationData?.loginButtonIcon ? registrationData.loginButtonIcon : null], + clientId: [registrationData?.clientId ? registrationData.clientId : '', Validators.required], + clientSecret: [registrationData?.clientSecret ? registrationData.clientSecret : '', Validators.required], + accessTokenUri: [registrationData?.accessTokenUri ? registrationData.accessTokenUri : '', [ + Validators.required, + Validators.pattern(this.URL_REGEXP)]], + authorizationUri: [registrationData?.authorizationUri ? registrationData.authorizationUri : '', [ + Validators.required, + Validators.pattern(this.URL_REGEXP)]], + scope: this.fb.array(registrationData?.scope ? registrationData.scope : []), + jwkSetUri: [registrationData?.jwkSetUri ? registrationData.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)], + userInfoUri: [registrationData?.userInfoUri ? registrationData.userInfoUri : '', [ + Validators.required, + Validators.pattern(this.URL_REGEXP)]], + clientAuthenticationMethod: [ + registrationData?.clientAuthenticationMethod ? registrationData.clientAuthenticationMethod : 'POST', Validators.required], + userNameAttributeName: [ + registrationData?.userNameAttributeName ? registrationData.userNameAttributeName : 'email', Validators.required], mapperConfig: this.fb.group({ - allowUserCreation: [true], - activateUser: [false], - type: ['BASIC', [Validators.required]], - basic: this.formBasicGroup + allowUserCreation: [registrationData?.mapperConfig?.allowUserCreation ? registrationData.mapperConfig.allowUserCreation : true], + activateUser: [registrationData?.mapperConfig?.activateUser ? registrationData.mapperConfig.activateUser : false], + type: [registrationData?.mapperConfig?.type ? registrationData.mapperConfig.type : 'BASIC', Validators.required] } ) }); + if (registrationData) { + this.changeMapperConfigType(clientRegistration, registrationData.mapperConfig.type, registrationData.mapperConfig); + } else { + this.changeMapperConfigType(clientRegistration, 'BASIC'); + } + this.subscriptions.push(clientRegistration.get('mapperConfig.type').valueChanges.subscribe((value) => { - const mapperConfig = clientRegistration.get('mapperConfig') as FormGroup; - if (value === 'BASIC') { - mapperConfig.removeControl('custom'); - mapperConfig.addControl('basic', this.formBasicGroup); - } else { - mapperConfig.removeControl('basic'); - mapperConfig.addControl('custom', this.formCustomGroup); - } + this.changeMapperConfigType(clientRegistration, value); })); - this.subscriptions.push(clientRegistration.get('providerName').valueChanges.subscribe((provider) => { + this.subscriptions.push(clientRegistration.get('additionalInfo.providerName').valueChanges.subscribe((provider) => { (clientRegistration.get('scope') as FormArray).clear(); if (provider === 'Custom') { clientRegistration.reset(this.defaultProvider, {emitEvent: false}); @@ -259,6 +291,8 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha clientRegistration.get('userInfoUri').enable(); } else { const template = this.templates.get(provider); + delete template.id; + delete template.additionalInfo; template.scope.forEach(() => { (clientRegistration.get('scope') as FormArray).push(this.fb.control('')); }); @@ -270,20 +304,23 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } })); - if (registrationData) { - registrationData.scope.forEach(() => { - (clientRegistration.get('scope') as FormArray).push(this.fb.control('')); - }); - if (registrationData.mapperConfig.type !== 'BASIC') { - clientRegistration.get('mapperConfig.type').patchValue('CUSTOM'); - } - } - return clientRegistration; } + private changeMapperConfigType(control: AbstractControl, type: MapperConfigType, predefinedValue?: MapperConfig) { + const mapperConfig = control.get('mapperConfig') as FormGroup; + if (type === 'BASIC') { + mapperConfig.removeControl('custom'); + mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic)); + } else { + mapperConfig.removeControl('basic'); + mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom)); + } + } + save(): void { - this.adminService.saveOAuth2Settings(this.oauth2SettingsForm.value).subscribe( + const setting = this.prepareFormValue(this.oauth2SettingsForm.getRawValue().clientDomains); + this.adminService.saveOAuth2Settings(setting).subscribe( (oauth2Settings) => { this.oauth2Settings = oauth2Settings; this.oauth2SettingsForm.markAsPristine(); @@ -292,6 +329,18 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha ); } + private prepareFormValue(formValue: OAuth2Settings[]): OAuth2Settings[]{ + formValue.forEach((setting) => { + setting.clientRegistrations.forEach((registration) => { + registration.additionalInfo = JSON.stringify(registration.additionalInfo); + if (registration.id.id === null) { + delete registration.id; + } + }); + }); + return formValue; + } + confirmForm(): FormGroup { return this.oauth2SettingsForm; } @@ -315,7 +364,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } addDomain(): void { - this.clientsDomainsParams.push(this.buildSettingsDomain()); + this.clientDomains.push(this.buildSettingsDomain()); } deleteDomain($event: Event, index: number): void { @@ -324,14 +373,21 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha $event.preventDefault(); } - const domainName = this.clientsDomainsParams.at(index).get('domainName').value; + const domainName = this.clientDomains.at(index).get('domainName').value; this.dialogService.confirm( this.translate.instant('admin.oauth2.delete-domain-title', {domainName: domainName || ''}), this.translate.instant('admin.oauth2.delete-domain-text'), null, this.translate.instant('action.delete') ).subscribe((data) => { if (data) { - this.clientsDomainsParams.removeAt(index); + if (index < this.oauth2Settings.length) { + this.adminService.deleteOAuth2Domain(this.oauth2Settings[index].domainName).subscribe(() => { + this.oauth2Settings.splice(index, 1); + this.clientDomains.removeAt(index); + }); + } else { + this.clientDomains.removeAt(index); + } } }); } @@ -344,20 +400,27 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha this.clientDomainRegistrations(control).push(this.buildSettingsRegistration()); } - deleteRegistration($event: Event, controler: AbstractControl, index: number): void { + deleteRegistration($event: Event, control: AbstractControl, index: number): void { if ($event) { $event.stopPropagation(); $event.preventDefault(); } - const providerName = this.clientDomainRegistrations(controler).at(index).get('providerName').value; + const providerName = this.clientDomainRegistrations(control).at(index).get('additionalInfo.providerName').value; this.dialogService.confirm( this.translate.instant('admin.oauth2.delete-registration-title', {name: providerName || ''}), this.translate.instant('admin.oauth2.delete-registration-text'), null, this.translate.instant('action.delete') ).subscribe((data) => { if (data) { - this.clientDomainRegistrations(controler).removeAt(index); + const registrationId = this.clientDomainRegistrations(control).at(index).get('id.id').value; + if (registrationId) { + this.adminService.deleteOAuthCclientRegistrationId(registrationId).subscribe(() => { + this.clientDomainRegistrations(control).removeAt(index); + }); + } else { + this.clientDomainRegistrations(control).removeAt(index); + } } }); } @@ -385,7 +448,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha overlayRef.dispose(); }); - const redirectURI = this.clientsDomainsParams.at(index).get('redirectUriTemplate').value; + const redirectURI = this.clientDomains.at(index).get('redirectUriTemplate').value; const injectionTokens = new WeakMap([ [EDIT_REDIRECT_URI_PANEL_DATA, { @@ -399,7 +462,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha componentRef.onDestroy(() => { if (componentRef.instance.result !== null) { const attributeValue = componentRef.instance.result; - this.clientsDomainsParams.at(index).get('redirectUriTemplate').patchValue(attributeValue, {emitEvent: true}); + this.clientDomains.at(index).get('redirectUriTemplate').patchValue(attributeValue, {emitEvent: true}); } }); } @@ -407,4 +470,13 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha toggleEditMode(control: AbstractControl, path: string) { control.get(path).disabled ? control.get(path).enable() : control.get(path).disable(); } + + getProviderName(controller: AbstractControl): string { + return controller.get('additionalInfo.providerName').value; + } + + getHelpLink(controller: AbstractControl): string { + const provider = controller.get('additionalInfo.providerName').value; + return this.templates.get(provider).helpLink; + } } diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 6841a911b7..f3888fe4be 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -45,7 +45,8 @@ export enum EntityType { RULE_NODE = 'RULE_NODE', ENTITY_VIEW = 'ENTITY_VIEW', WIDGETS_BUNDLE = 'WIDGETS_BUNDLE', - WIDGET_TYPE = 'WIDGET_TYPE' + WIDGET_TYPE = 'WIDGET_TYPE', + OAUTH2_CLIENT_REGISTRATION = 'OAUTH2_CLIENT_REGISTRATION' } export enum AliasEntityType { diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index 8fe83aee09..f27d80a260 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -69,21 +69,15 @@ export interface UpdateMessage { } export interface OAuth2Settings { - clientsDomainsParams: DomainParams[]; -} - -export interface DomainParams { domainName: string; redirectUriTemplate: string; clientRegistrations: ClientRegistration[]; } export interface ClientProviderTemplated extends ClientRegistration{ - additionalInfo: string; comment: string; createdTime: number; helpLink: string; - id: EntityId; name: string; providerId: string; tenantId: TenantId; @@ -97,11 +91,13 @@ export interface ClientRegistration { accessTokenUri: string; authorizationUri: string; scope: string[]; - jwkSetUri: string; + jwkSetUri?: string; userInfoUri: string; clientAuthenticationMethod: ClientAuthenticationMethod; userNameAttributeName: string; mapperConfig: MapperConfig; + id?: EntityId; + additionalInfo: string; } export interface MapperConfig { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 802575c0ff..50b74e393a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -166,8 +166,8 @@ "url": "URL", "url-required": "URL is required.", "url-pattern": "Invalid URL format.", - "login-button-label": "Login button label", - "login-button-label-required": "Login button label is required.", + "login-button-label": "Label (Login with ${label})", + "login-button-label-required": "Label is required.", "login-button-icon": "Login button icon", "delete-domain-title": "Are you sure you want to delete the domain '{{domainName}}'?", "delete-domain-text": "Be careful, after the confirmation a domain and all registration data will be unavailable.", From 86aacc07ae70206c55083c3d2293f350d60de792 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Mon, 21 Sep 2020 13:40:24 +0300 Subject: [PATCH 097/463] Set default value to 'false' for 'isOAuth2ConfigAllowed' --- .../org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java | 2 +- .../thingsboard/server/dao/service/BaseOAuth2ServiceTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 70b0f1edfd..788c1293da 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -129,7 +129,7 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se if (tenant == null) return false; JsonNode allowOAuth2ConfigurationJsonNode = tenant.getAdditionalInfo() != null ? tenant.getAdditionalInfo().get(ALLOW_OAUTH2_CONFIGURATION) : null; if (allowOAuth2ConfigurationJsonNode == null) { - return true; + return false; } else { return allowOAuth2ConfigurationJsonNode.asBoolean(); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java index 975b6f318b..2231bc6dfe 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -66,7 +66,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testIsOAuth2Allowed_null() throws IOException { updateTenantAllowOAuth2Setting(null); - Assert.assertTrue(oAuth2Service.isOAuth2ClientRegistrationAllowed(tenantId)); + Assert.assertFalse(oAuth2Service.isOAuth2ClientRegistrationAllowed(tenantId)); } @Test From 4c07e721175ed51f86b2aa578dc97ad5db5c201d Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 21 Sep 2020 14:00:13 +0300 Subject: [PATCH 098/463] Refactoring --- ...ard.ts => redirect-guard-settings.service.ts} | 4 ++-- .../home/pages/admin/admin-routing.module.ts | 4 ++-- .../pages/admin/oauth2-settings.component.ts | 16 +++++++++++++--- 3 files changed, 17 insertions(+), 7 deletions(-) rename ui-ngx/src/app/core/guards/{redirect.guard.ts => redirect-guard-settings.service.ts} (93%) diff --git a/ui-ngx/src/app/core/guards/redirect.guard.ts b/ui-ngx/src/app/core/guards/redirect-guard-settings.service.ts similarity index 93% rename from ui-ngx/src/app/core/guards/redirect.guard.ts rename to ui-ngx/src/app/core/guards/redirect-guard-settings.service.ts index 652f1ff026..4c69f21aae 100644 --- a/ui-ngx/src/app/core/guards/redirect.guard.ts +++ b/ui-ngx/src/app/core/guards/redirect-guard-settings.service.ts @@ -26,7 +26,7 @@ import { Authority } from '@shared/models/authority.enum'; @Injectable({ providedIn: 'root' }) -export class RedirectGuard implements CanActivate { +export class RedirectGuardSettings implements CanActivate { constructor(private store: Store, private router: Router) { } @@ -41,7 +41,7 @@ export class RedirectGuard implements CanActivate { ); if (auth?.userDetails?.authority === Authority.TENANT_ADMIN) { - this.router.navigateByUrl('/settings/oauth2-settings'); + this.router.navigateByUrl('/settings/oauth2'); return false; } this.router.navigateByUrl('/settings/general'); diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts index 16635ef53f..c4a38fdf30 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts @@ -23,7 +23,7 @@ import { Authority } from '@shared/models/authority.enum'; import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component'; import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component'; import { OAuth2SettingsComponent } from '@home/pages/admin/oauth2-settings.component'; -import { RedirectGuard } from '@core/guards/redirect.guard'; +import { RedirectGuardSettings } from '../../../../core/guards/redirect-guard-settings.service'; const routes: Routes = [ { @@ -38,7 +38,7 @@ const routes: Routes = [ children: [ { path: '', - canActivate: [RedirectGuard], + canActivate: [RedirectGuardSettings], pathMatch: 'full' }, { diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index 5d4c921700..1f627fd2de 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -57,7 +57,9 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha private subscriptions: Subscription[] = []; private templates = new Map(); private defaultProvider = { - providerName: 'Custom', + additionalInfo: { + providerName: 'Custom' + }, clientAuthenticationMethod: 'Post', userNameAttributeName: 'email', mapperConfig: { @@ -105,7 +107,6 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha this.initTemplates(templates); this.oauth2Settings = oauth2Settings; this.initOAuth2Settings(this.oauth2Settings); - // this.oauth2SettingsForm.get('clientDomains').updateValueAndValidity(); } ); } @@ -300,7 +301,13 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha clientRegistration.get('authorizationUri').disable(); clientRegistration.get('jwkSetUri').disable(); clientRegistration.get('userInfoUri').disable(); - clientRegistration.patchValue(this.templates.get(provider), {emitEvent: false}); + clientRegistration.patchValue({ + ...template, ...{ + clientId: '', + clientSecret: '', + id: {id: null, entityType: null} + } + }, {emitEvent: false}); } })); @@ -477,6 +484,9 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha getHelpLink(controller: AbstractControl): string { const provider = controller.get('additionalInfo.providerName').value; + if (provider === null || provider === 'Custom') { + return ''; + } return this.templates.get(provider).helpLink; } } From 556cb5aa5df8ddd7eafe39e5a9336d63d5d88226 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 21 Sep 2020 16:10:00 +0300 Subject: [PATCH 099/463] Refactoring OAuth2 --- ui-ngx/src/app/core/auth/auth.models.ts | 2 - ui-ngx/src/app/core/auth/auth.reducer.ts | 1 - ui-ngx/src/app/core/auth/auth.service.ts | 14 +---- .../guards/redirect-guard-settings.service.ts | 51 ------------------- ui-ngx/src/app/core/services/menu.service.ts | 24 +-------- .../home/pages/admin/admin-routing.module.ts | 5 +- .../pages/admin/oauth2-settings.component.ts | 36 +++++++------ .../home/pages/tenant/tenant.component.html | 3 -- .../home/pages/tenant/tenant.component.ts | 14 ++--- .../assets/locale/locale.constant-en_US.json | 4 +- 10 files changed, 26 insertions(+), 128 deletions(-) delete mode 100644 ui-ngx/src/app/core/guards/redirect-guard-settings.service.ts diff --git a/ui-ngx/src/app/core/auth/auth.models.ts b/ui-ngx/src/app/core/auth/auth.models.ts index 573b692a29..c7210da6ed 100644 --- a/ui-ngx/src/app/core/auth/auth.models.ts +++ b/ui-ngx/src/app/core/auth/auth.models.ts @@ -22,7 +22,6 @@ export interface AuthPayload { userTokenAccessEnabled: boolean; allowedDashboardIds: string[]; forceFullscreen: boolean; - allowOAuth2Configuration: boolean; } export interface AuthState { @@ -34,5 +33,4 @@ export interface AuthState { allowedDashboardIds: string[]; forceFullscreen: boolean; lastPublicDashboardId: string; - allowOAuth2Configuration: boolean; } diff --git a/ui-ngx/src/app/core/auth/auth.reducer.ts b/ui-ngx/src/app/core/auth/auth.reducer.ts index 0e7ca095af..6e02d03191 100644 --- a/ui-ngx/src/app/core/auth/auth.reducer.ts +++ b/ui-ngx/src/app/core/auth/auth.reducer.ts @@ -22,7 +22,6 @@ const emptyUserAuthState: AuthPayload = { userDetails: null, userTokenAccessEnabled: false, forceFullscreen: false, - allowOAuth2Configuration: false, allowedDashboardIds: [] }; diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts index f0b9c09a1e..32733ac139 100644 --- a/ui-ngx/src/app/core/auth/auth.service.ts +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -18,7 +18,7 @@ import { Injectable, NgZone } from '@angular/core'; import { JwtHelperService } from '@auth0/angular-jwt'; import { HttpClient } from '@angular/common/http'; -import { forkJoin, Observable, of, throwError, ReplaySubject } from 'rxjs'; +import { forkJoin, Observable, of, ReplaySubject, throwError } from 'rxjs'; import { catchError, map, mergeMap, tap } from 'rxjs/operators'; import { LoginRequest, LoginResponse, OAuth2Client, PublicLoginRequest } from '@shared/models/login.models'; @@ -425,25 +425,15 @@ export class AuthService { } } - private loadIsOAuth2ConfigurationAllow(authUser: AuthUser): Observable { - if (authUser.authority === Authority.TENANT_ADMIN) { - return this.http.get('/api/oauth2/config/isAllowed', defaultHttpOptions()); - } else { - return of(false); - } - } - private loadSystemParams(authPayload: AuthPayload): Observable { const sources: Array> = [this.loadIsUserTokenAccessEnabled(authPayload.authUser), this.fetchAllowedDashboardIds(authPayload), - this.loadIsOAuth2ConfigurationAllow(authPayload.authUser), this.timeService.loadMaxDatapointsLimit()]; return forkJoin(sources) .pipe(map((data) => { const userTokenAccessEnabled: boolean = data[0]; const allowedDashboardIds: string[] = data[1]; - const allowOAuth2Configuration: boolean = data[2]; - return {userTokenAccessEnabled, allowedDashboardIds, allowOAuth2Configuration}; + return {userTokenAccessEnabled, allowedDashboardIds}; })); } diff --git a/ui-ngx/src/app/core/guards/redirect-guard-settings.service.ts b/ui-ngx/src/app/core/guards/redirect-guard-settings.service.ts deleted file mode 100644 index 4c69f21aae..0000000000 --- a/ui-ngx/src/app/core/guards/redirect-guard-settings.service.ts +++ /dev/null @@ -1,51 +0,0 @@ -/// -/// Copyright © 2016-2020 The Thingsboard Authors -/// -/// Licensed under the Apache License, Version 2.0 (the "License"); -/// you may not use this file except in compliance with the License. -/// You may obtain a copy of the License at -/// -/// http://www.apache.org/licenses/LICENSE-2.0 -/// -/// Unless required by applicable law or agreed to in writing, software -/// distributed under the License is distributed on an "AS IS" BASIS, -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -/// See the License for the specific language governing permissions and -/// limitations under the License. -/// - -import { Injectable } from '@angular/core'; -import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; -import { AuthState } from '@core/auth/auth.models'; -import { select, Store } from '@ngrx/store'; -import { selectAuth } from '@core/auth/auth.selectors'; -import { take } from 'rxjs/operators'; -import { AppState } from '@core/core.state'; -import { Authority } from '@shared/models/authority.enum'; - -@Injectable({ - providedIn: 'root' -}) -export class RedirectGuardSettings implements CanActivate { - constructor(private store: Store, - private router: Router) { } - - canActivate( - next: ActivatedRouteSnapshot, - state: RouterStateSnapshot) { - let auth: AuthState = null; - this.store.pipe(select(selectAuth), take(1)).subscribe( - (authState: AuthState) => { - auth = authState; - } - ); - - if (auth?.userDetails?.authority === Authority.TENANT_ADMIN) { - this.router.navigateByUrl('/settings/oauth2'); - return false; - } - this.router.navigateByUrl('/settings/general'); - return false; - } - -} diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index 7d3503a613..14f3a632db 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -18,13 +18,12 @@ import { Injectable } from '@angular/core'; import { AuthService } from '../auth/auth.service'; import { select, Store } from '@ngrx/store'; import { AppState } from '../core.state'; -import { getCurrentAuthState, selectAuthUser, selectIsAuthenticated } from '../auth/auth.selectors'; +import { selectAuthUser, selectIsAuthenticated } from '../auth/auth.selectors'; import { take } from 'rxjs/operators'; import { HomeSection, MenuSection } from '@core/services/menu.models'; import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { Authority } from '@shared/models/authority.enum'; import { AuthUser } from '@shared/models/user.model'; -import { AuthState } from '@core/auth/auth.models'; @Injectable({ providedIn: 'root' @@ -44,8 +43,6 @@ export class MenuService { ); } - authState: AuthState = getCurrentAuthState(this.store); - private buildMenu() { this.store.pipe(select(selectAuthUser), take(1)).subscribe( (authUser: AuthUser) => { @@ -236,25 +233,6 @@ export class MenuService { icon: 'track_changes' } ); - - if (this.authState.allowOAuth2Configuration) { - sections.push({ - name: 'admin.settings', - type: 'toggle', - path: '/settings', - height: '40px', - icon: 'settings', - pages: [ - { - name: 'admin.oauth2.oauth2', - type: 'link', - path: '/settings/oauth2', - icon: 'security' - } - ] - }); - } - return sections; } diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts index c4a38fdf30..40a8d3addd 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts @@ -23,7 +23,6 @@ import { Authority } from '@shared/models/authority.enum'; import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component'; import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component'; import { OAuth2SettingsComponent } from '@home/pages/admin/oauth2-settings.component'; -import { RedirectGuardSettings } from '../../../../core/guards/redirect-guard-settings.service'; const routes: Routes = [ { @@ -38,7 +37,7 @@ const routes: Routes = [ children: [ { path: '', - canActivate: [RedirectGuardSettings], + redirectTo: 'general', pathMatch: 'full' }, { @@ -85,7 +84,7 @@ const routes: Routes = [ component: OAuth2SettingsComponent, canDeactivate: [ConfirmOnExitGuard], data: { - auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], + auth: [Authority.SYS_ADMIN], title: 'admin.oauth2.oauth2', breadcrumb: { label: 'admin.oauth2.oauth2', diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index 1f627fd2de..7a0e130758 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -60,7 +60,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha additionalInfo: { providerName: 'Custom' }, - clientAuthenticationMethod: 'Post', + clientAuthenticationMethod: 'POST', userNameAttributeName: 'email', mapperConfig: { allowUserCreation: true, @@ -249,17 +249,17 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha loginButtonIcon: [registrationData?.loginButtonIcon ? registrationData.loginButtonIcon : null], clientId: [registrationData?.clientId ? registrationData.clientId : '', Validators.required], clientSecret: [registrationData?.clientSecret ? registrationData.clientSecret : '', Validators.required], - accessTokenUri: [registrationData?.accessTokenUri ? registrationData.accessTokenUri : '', [ - Validators.required, - Validators.pattern(this.URL_REGEXP)]], - authorizationUri: [registrationData?.authorizationUri ? registrationData.authorizationUri : '', [ - Validators.required, - Validators.pattern(this.URL_REGEXP)]], + accessTokenUri: [registrationData?.accessTokenUri ? registrationData.accessTokenUri : '', + [Validators.required, + Validators.pattern(this.URL_REGEXP)]], + authorizationUri: [registrationData?.authorizationUri ? registrationData.authorizationUri : '', + [Validators.required, + Validators.pattern(this.URL_REGEXP)]], scope: this.fb.array(registrationData?.scope ? registrationData.scope : []), jwkSetUri: [registrationData?.jwkSetUri ? registrationData.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)], - userInfoUri: [registrationData?.userInfoUri ? registrationData.userInfoUri : '', [ - Validators.required, - Validators.pattern(this.URL_REGEXP)]], + userInfoUri: [registrationData?.userInfoUri ? registrationData.userInfoUri : '', + [Validators.required, + Validators.pattern(this.URL_REGEXP)]], clientAuthenticationMethod: [ registrationData?.clientAuthenticationMethod ? registrationData.clientAuthenticationMethod : 'POST', Validators.required], userNameAttributeName: [ @@ -285,7 +285,8 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha this.subscriptions.push(clientRegistration.get('additionalInfo.providerName').valueChanges.subscribe((provider) => { (clientRegistration.get('scope') as FormArray).clear(); if (provider === 'Custom') { - clientRegistration.reset(this.defaultProvider, {emitEvent: false}); + const defaultSettings = {...this.defaultProvider, ...{id: clientRegistration.get('id').value}}; + clientRegistration.reset(defaultSettings, {emitEvent: false}); clientRegistration.get('accessTokenUri').enable(); clientRegistration.get('authorizationUri').enable(); clientRegistration.get('jwkSetUri').enable(); @@ -294,6 +295,8 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha const template = this.templates.get(provider); delete template.id; delete template.additionalInfo; + template.clientId = ''; + template.clientSecret = ''; template.scope.forEach(() => { (clientRegistration.get('scope') as FormArray).push(this.fb.control('')); }); @@ -301,13 +304,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha clientRegistration.get('authorizationUri').disable(); clientRegistration.get('jwkSetUri').disable(); clientRegistration.get('userInfoUri').disable(); - clientRegistration.patchValue({ - ...template, ...{ - clientId: '', - clientSecret: '', - id: {id: null, entityType: null} - } - }, {emitEvent: false}); + clientRegistration.patchValue(template, {emitEvent: false}); } })); @@ -469,7 +466,8 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha componentRef.onDestroy(() => { if (componentRef.instance.result !== null) { const attributeValue = componentRef.instance.result; - this.clientDomains.at(index).get('redirectUriTemplate').patchValue(attributeValue, {emitEvent: true}); + this.clientDomains.at(index).get('redirectUriTemplate').patchValue(attributeValue); + this.clientDomains.at(index).get('redirectUriTemplate').markAsDirty(); } }); } diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html index f13809ffa6..335a7d3c9a 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html @@ -54,9 +54,6 @@ tenant.description - - {{ 'tenant.allow-oauth2-configuration' | translate }} -
diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts index aa40d792b0..cbf55857da 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts @@ -23,7 +23,6 @@ import { ActionNotificationShow } from '@app/core/notification/notification.acti import { TranslateService } from '@ngx-translate/core'; import { ContactBasedComponent } from '../../components/entity/contact-based.component'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; -import { isDefined } from '@core/utils'; @Component({ selector: 'tb-tenant', @@ -54,11 +53,8 @@ export class TenantComponent extends ContactBasedComponent { title: [entity ? entity.title : '', [Validators.required]], isolatedTbCore: [entity ? entity.isolatedTbCore : false, []], isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []], - additionalInfo: this.fb.group( - { - description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], - allowOAuth2Configuration: [isDefined(entity?.additionalInfo?.allowOAuth2Configuration) ? - entity.additionalInfo.allowOAuth2Configuration : false] + additionalInfo: this.fb.group({ + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''] } ) } @@ -69,11 +65,7 @@ export class TenantComponent extends ContactBasedComponent { this.entityForm.patchValue({title: entity.title}); this.entityForm.patchValue({isolatedTbCore: entity.isolatedTbCore}); this.entityForm.patchValue({isolatedTbRuleEngine: entity.isolatedTbRuleEngine}); - this.entityForm.patchValue({additionalInfo: { - description: entity.additionalInfo ? entity.additionalInfo.description : '', - allowOAuth2Configuration: isDefined(entity?.additionalInfo?.allowOAuth2Configuration) ? - entity.additionalInfo.allowOAuth2Configuration : false - }}); + this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); } updateFormState() { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 50b74e393a..78086a7972 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -126,7 +126,6 @@ "add-domain": "Add domain", "new-domain": "New domain", "add-provider": "Add provider", - "settings": "Settings", "oauth2": { "oauth2": "OAuth2", "registration-id": "Registration ID", @@ -1715,8 +1714,7 @@ "isolated-tb-core": "Processing in isolated ThingsBoard Core container", "isolated-tb-rule-engine": "Processing in isolated ThingsBoard Rule Engine container", "isolated-tb-core-details": "Requires separate microservice(s) per isolated Tenant", - "isolated-tb-rule-engine-details": "Requires separate microservice(s) per isolated Tenant", - "allow-oauth2-configuration": "Allow OAuth2 configuration" + "isolated-tb-rule-engine-details": "Requires separate microservice(s) per isolated Tenant" }, "timeinterval": { "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", From e778b3155afc2abb60acbd4a4e3d820a5f89f8b3 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Mon, 21 Sep 2020 17:33:27 +0300 Subject: [PATCH 100/463] Remove OAuth2Config functionality from Tenant --- .../main/data/upgrade/3.1.1/schema_update.sql | 2 - .../server/controller/BaseController.java | 43 +--- .../OAuth2ConfigTemplateController.java | 13 +- .../server/controller/OAuth2Controller.java | 66 +----- .../service/install/InstallScripts.java | 1 - .../oauth2/AbstractOAuth2ClientMapper.java | 14 +- .../auth/oauth2/BasicOAuth2ClientMapper.java | 5 +- .../auth/oauth2/CustomOAuth2ClientMapper.java | 5 +- .../auth/oauth2/OAuth2ClientMapper.java | 3 +- .../Oauth2AuthenticationSuccessHandler.java | 2 +- .../permission/SysAdminPermissions.java | 21 +- .../permission/TenantAdminPermissions.java | 37 --- .../server/dao/oauth2/OAuth2Service.java | 12 +- .../data/oauth2/OAuth2ClientRegistration.java | 4 +- .../OAuth2ClientRegistrationTemplate.java | 4 +- .../sql/OAuth2ClientRegistrationEntity.java | 7 - ...Auth2ClientRegistrationTemplateEntity.java | 7 - .../oauth2/OAuth2ClientRegistrationDao.java | 6 +- .../OAuth2ConfigTemplateServiceImpl.java | 8 +- .../server/dao/oauth2/OAuth2ServiceImpl.java | 74 ++---- .../server/dao/oauth2/OAuth2Utils.java | 12 +- .../JpaOAuth2ClientRegistrationDao.java | 16 +- .../OAuth2ClientRegistrationRepository.java | 6 +- .../server/dao/tenant/TenantServiceImpl.java | 1 - .../resources/sql/schema-entities-hsql.sql | 2 - .../main/resources/sql/schema-entities.sql | 2 - .../BaseOAuth2ConfigTemplateServiceTest.java | 23 +- .../dao/service/BaseOAuth2ServiceTest.java | 220 +++++------------- 28 files changed, 118 insertions(+), 498 deletions(-) diff --git a/application/src/main/data/upgrade/3.1.1/schema_update.sql b/application/src/main/data/upgrade/3.1.1/schema_update.sql index 6064ec8833..f0dfbe1ae3 100644 --- a/application/src/main/data/upgrade/3.1.1/schema_update.sql +++ b/application/src/main/data/upgrade/3.1.1/schema_update.sql @@ -20,7 +20,6 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, created_time bigint NOT NULL, additional_info varchar, - tenant_id uuid, domain_name varchar(255), client_id varchar(255), client_secret varchar(255), @@ -57,7 +56,6 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY, created_time bigint NOT NULL, additional_info varchar, - tenant_id uuid, provider_id varchar(255), authorization_uri varchar(255), token_uri varchar(255), diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 371b423105..577e6c9e83 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -27,19 +27,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.ExceptionHandler; -import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.Dashboard; -import org.thingsboard.server.common.data.DashboardInfo; -import org.thingsboard.server.common.data.DataConstants; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceInfo; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.EntityViewInfo; -import org.thingsboard.server.common.data.HasName; -import org.thingsboard.server.common.data.HasTenantId; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.*; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.asset.Asset; @@ -50,8 +38,6 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.DataType; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; @@ -388,10 +374,7 @@ public abstract class BaseController { checkWidgetTypeId(new WidgetTypeId(entityId.getId()), operation); return; case OAUTH2_CLIENT_REGISTRATION: - checkOAuth2ClientRegistrationId(new OAuth2ClientRegistrationId(entityId.getId()), operation); - return; case OAUTH2_CLIENT_REGISTRATION_TEMPLATE: - checkOAuth2ClientRegistrationTemplateId(new OAuth2ClientRegistrationTemplateId(entityId.getId()), operation); return; default: throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType()); @@ -545,30 +528,6 @@ public abstract class BaseController { } } - OAuth2ClientRegistration checkOAuth2ClientRegistrationId(OAuth2ClientRegistrationId clientRegistrationId, Operation operation) throws ThingsboardException { - try { - validateId(clientRegistrationId, "Incorrect oAuth2ClientRegistrationId " + clientRegistrationId); - OAuth2ClientRegistration clientRegistration = oAuth2Service.findClientRegistration(clientRegistrationId.getId()); - checkNotNull(clientRegistration); - accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION, operation, clientRegistrationId, clientRegistration); - return clientRegistration; - } catch (Exception e) { - throw handleException(e, false); - } - } - - OAuth2ClientRegistrationTemplate checkOAuth2ClientRegistrationTemplateId(OAuth2ClientRegistrationTemplateId clientRegistrationTemplateId, Operation operation) throws ThingsboardException { - try { - validateId(clientRegistrationTemplateId, "Incorrect oAuth2ClientRegistrationTemplateId " + clientRegistrationTemplateId); - OAuth2ClientRegistrationTemplate clientRegistrationTemplate = oAuth2ConfigTemplateService.findClientRegistrationTemplateById(clientRegistrationTemplateId); - checkNotNull(clientRegistrationTemplate); - accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, operation, clientRegistrationTemplateId, clientRegistrationTemplate); - return clientRegistrationTemplate; - } catch (Exception e) { - throw handleException(e, false); - } - } - ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException { try { log.debug("[{}] Lookup component descriptor", clazz); diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java index ed2c479e1f..1cb969e9e6 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java @@ -25,8 +25,6 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.security.permission.Operation; -import org.thingsboard.server.service.security.permission.Resource; import java.util.List; @@ -42,8 +40,6 @@ public class OAuth2ConfigTemplateController extends BaseController { @ResponseStatus(value = HttpStatus.OK) public OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(@RequestBody OAuth2ClientRegistrationTemplate clientRegistrationTemplate) throws ThingsboardException { try { - clientRegistrationTemplate.setTenantId(getCurrentUser().getTenantId()); - checkEntity(clientRegistrationTemplate.getId(), clientRegistrationTemplate, Resource.OAUTH2_CONFIGURATION_TEMPLATE); return oAuth2ConfigTemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate); } catch (Exception e) { throw handleException(e); @@ -57,10 +53,10 @@ public class OAuth2ConfigTemplateController extends BaseController { checkParameter(CLIENT_REGISTRATION_TEMPLATE_ID, strClientRegistrationTemplateId); try { OAuth2ClientRegistrationTemplateId clientRegistrationTemplateId = new OAuth2ClientRegistrationTemplateId(toUUID(strClientRegistrationTemplateId)); - OAuth2ClientRegistrationTemplate clientRegistrationTemplate = checkOAuth2ClientRegistrationTemplateId(clientRegistrationTemplateId, Operation.DELETE); oAuth2ConfigTemplateService.deleteClientRegistrationTemplateById(clientRegistrationTemplateId); - logEntityAction(clientRegistrationTemplateId, clientRegistrationTemplate, + logEntityAction(clientRegistrationTemplateId, + null, null, ActionType.DELETED, null, strClientRegistrationTemplateId); @@ -80,14 +76,9 @@ public class OAuth2ConfigTemplateController extends BaseController { @ResponseBody public List getClientRegistrationTemplates() throws ThingsboardException { try { - checkOAuth2ConfigTemplatePermissions(Operation.READ); return oAuth2ConfigTemplateService.findAllClientRegistrationTemplates(); } catch (Exception e) { throw handleException(e); } } - - private void checkOAuth2ConfigTemplatePermissions(Operation operation) throws ThingsboardException { - accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, operation); - } } diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index b4a8ead9db..3396c5537d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -23,16 +23,12 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.oauth2.*; -import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientsDomainParams; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.security.permission.Operation; -import org.thingsboard.server.service.security.permission.Resource; import javax.servlet.http.HttpServletRequest; import java.util.List; -import java.util.stream.Collectors; @RestController @TbCoreComponent @@ -52,62 +48,39 @@ public class OAuth2Controller extends BaseController { } } - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @RequestMapping(value = "/oauth2/config", method = RequestMethod.GET, produces = "application/json") @ResponseBody public List getCurrentClientsParams() throws ThingsboardException { try { - Authority authority = getCurrentUser().getAuthority(); - checkOAuth2ConfigPermissions(Operation.READ); - if (Authority.SYS_ADMIN.equals(authority)) { - return oAuth2Service.findDomainsParamsByTenantId(TenantId.SYS_TENANT_ID); - } else if (Authority.TENANT_ADMIN.equals(authority)) { - return oAuth2Service.findDomainsParamsByTenantId(getCurrentUser().getTenantId()); - } else { - throw new IllegalStateException("Authority " + authority + " cannot get client registrations."); - } + return oAuth2Service.findDomainsParams(); } catch (Exception e) { throw handleException(e); } } - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @RequestMapping(value = "/oauth2/config", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) public List saveClientParams(@RequestBody List domainsParams) throws ThingsboardException { try { - TenantId tenantId; - Authority authority = getCurrentUser().getAuthority(); - if (Authority.SYS_ADMIN.equals(authority)) { - tenantId = TenantId.SYS_TENANT_ID; - } else if (Authority.TENANT_ADMIN.equals(authority)) { - tenantId = getCurrentUser().getTenantId(); - } else { - throw new IllegalStateException("Authority " + authority + " cannot save client registrations."); - } - List clientRegistrationDtos = domainsParams.stream() - .flatMap(domainParams -> domainParams.getClientRegistrations().stream()) - .collect(Collectors.toList()); - for (ClientRegistrationDto clientRegistrationDto : clientRegistrationDtos) { - checkEntity(clientRegistrationDto.getId(), () -> tenantId, Resource.OAUTH2_CONFIGURATION); - } - return oAuth2Service.saveDomainsParams(tenantId, domainsParams); + return oAuth2Service.saveDomainsParams(domainsParams); } catch (Exception e) { throw handleException(e); } } - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @RequestMapping(value = "/oauth2/config/{clientRegistrationId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) public void deleteClientRegistration(@PathVariable(CLIENT_REGISTRATION_ID) String strClientRegistrationId) throws ThingsboardException { checkParameter(CLIENT_REGISTRATION_ID, strClientRegistrationId); try { OAuth2ClientRegistrationId clientRegistrationId = new OAuth2ClientRegistrationId(toUUID(strClientRegistrationId)); - OAuth2ClientRegistration clientRegistration = checkOAuth2ClientRegistrationId(clientRegistrationId, Operation.DELETE); - oAuth2Service.deleteClientRegistrationById(getCurrentUser().getTenantId(), clientRegistrationId); + oAuth2Service.deleteClientRegistrationById(clientRegistrationId); - logEntityAction(clientRegistrationId, clientRegistration, + logEntityAction(clientRegistrationId, + null, null, ActionType.DELETED, null, strClientRegistrationId); @@ -123,13 +96,13 @@ public class OAuth2Controller extends BaseController { } - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @RequestMapping(value = "/oauth2/config/domain/{domain}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) public void deleteClientRegistrationForDomain(@PathVariable(DOMAIN) String domain) throws ThingsboardException { checkParameter(DOMAIN, domain); try { - oAuth2Service.deleteClientRegistrationsByDomain(getCurrentUser().getTenantId(), domain); + oAuth2Service.deleteClientRegistrationsByDomain(domain); logEntityAction(emptyId(EntityType.OAUTH2_CLIENT_REGISTRATION), null, null, @@ -144,19 +117,4 @@ public class OAuth2Controller extends BaseController { throw handleException(e); } } - - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/oauth2/config/isAllowed", method = RequestMethod.GET) - @ResponseBody - public Boolean isOAuth2ConfigurationAllowed() throws ThingsboardException { - try { - return oAuth2Service.isOAuth2ClientRegistrationAllowed(getTenantId()); - } catch (Exception e) { - throw handleException(e); - } - } - - private void checkOAuth2ConfigPermissions(Operation operation) throws ThingsboardException { - accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION, operation); - } } diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index 1b5827b6f7..8a7870a1c2 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -224,7 +224,6 @@ public class InstallScripts { try { JsonNode oauth2ConfigTemplateJson = objectMapper.readTree(path.toFile()); OAuth2ClientRegistrationTemplate clientRegistrationTemplate = objectMapper.treeToValue(oauth2ConfigTemplateJson, OAuth2ClientRegistrationTemplate.class); - clientRegistrationTemplate.setTenantId(TenantId.SYS_TENANT_ID); oAuth2TemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate); } catch (Exception e) { log.error("Unable to load oauth2 config templates from json: [{}]", path.toString()); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java index 61d288cdff..651e234f0a 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java @@ -17,7 +17,6 @@ package org.thingsboard.server.service.security.auth.oauth2; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.common.base.Strings; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -34,7 +33,6 @@ import org.thingsboard.server.common.data.id.IdBased; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.dao.customer.CustomerService; @@ -49,7 +47,6 @@ import org.thingsboard.server.service.security.model.UserPrincipal; import java.io.IOException; import java.util.List; import java.util.Optional; -import java.util.concurrent.ExecutionException; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -79,7 +76,7 @@ public abstract class AbstractOAuth2ClientMapper { private final Lock userCreationLock = new ReentrantLock(); - protected SecurityUser getOrCreateSecurityUserFromOAuth2User(TenantId parentTenantId, OAuth2User oauth2User, boolean allowUserCreation, boolean activateUser) { + protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, boolean allowUserCreation, boolean activateUser) { UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, oauth2User.getEmail()); User user = userService.findUserByEmail(TenantId.SYS_TENANT_ID, oauth2User.getEmail()); @@ -99,13 +96,8 @@ public abstract class AbstractOAuth2ClientMapper { } else { user.setAuthority(Authority.CUSTOMER_USER); } - TenantId tenantId; - if (TenantId.SYS_TENANT_ID.equals(parentTenantId)) { - tenantId = oauth2User.getTenantId() != null ? - oauth2User.getTenantId() : getTenantId(oauth2User.getTenantName()); - } else { - tenantId = parentTenantId; - } + TenantId tenantId = oauth2User.getTenantId() != null ? + oauth2User.getTenantId() : getTenantId(oauth2User.getTenantName()); user.setTenantId(tenantId); CustomerId customerId = oauth2User.getCustomerId() != null ? oauth2User.getCustomerId() : getCustomerId(user.getTenantId(), oauth2User.getCustomerName()); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java index 5bee8e866e..7412f2199c 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java @@ -20,7 +20,6 @@ import org.apache.commons.lang3.text.StrSubstitutor; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.service.security.model.SecurityUser; @@ -35,7 +34,7 @@ public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implemen private static final String END_PLACEHOLDER_PREFIX = "}"; @Override - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, TenantId parentTenantId, OAuth2MapperConfig config) { + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) { OAuth2User oauth2User = new OAuth2User(); Map attributes = token.getPrincipal().getAttributes(); String email = getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); @@ -59,7 +58,7 @@ public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implemen oauth2User.setDefaultDashboardName(config.getBasic().getDefaultDashboardName()); } - return getOrCreateSecurityUserFromOAuth2User(parentTenantId, oauth2User, config.isAllowUserCreation(), config.isActivateUser()); + return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser()); } private String getTenantName(Map attributes, OAuth2MapperConfig config) { diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java index 21c60b943b..a85da830b0 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java @@ -23,7 +23,6 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.dao.oauth2.OAuth2User; @@ -39,9 +38,9 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); @Override - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, TenantId parentTenantId, OAuth2MapperConfig config) { + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) { OAuth2User oauth2User = getOAuth2User(token, providerAccessToken, config.getCustom()); - return getOrCreateSecurityUserFromOAuth2User(parentTenantId, oauth2User, config.isAllowUserCreation(), config.isActivateUser()); + return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser()); } private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2CustomMapperConfig custom) { diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java index cdd0313f51..27b24043a5 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java @@ -16,10 +16,9 @@ package org.thingsboard.server.service.security.auth.oauth2; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.service.security.model.SecurityUser; public interface OAuth2ClientMapper { - SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, TenantId parentTenantId, OAuth2MapperConfig config); + SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index 7c0097fbfb..ddd5de087d 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -74,7 +74,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS token.getPrincipal().getName()); OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(clientRegistration.getMapperConfig().getType()); SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(), - clientRegistration.getTenantId(), clientRegistration.getMapperConfig()); + clientRegistration.getMapperConfig()); JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java index ba295a064d..871de30e6e 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java @@ -19,14 +19,10 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.service.security.model.SecurityUser; -import java.util.HashMap; -import java.util.Optional; - @Component(value="sysAdminPermissions") public class SysAdminPermissions extends AbstractPermissions { @@ -39,7 +35,7 @@ public class SysAdminPermissions extends AbstractPermissions { put(Resource.USER, userPermissionChecker); put(Resource.WIDGETS_BUNDLE, systemEntityPermissionChecker); put(Resource.WIDGET_TYPE, systemEntityPermissionChecker); - put(Resource.OAUTH2_CONFIGURATION, sysAdminOAuth2ConfigPermissionChecker); + put(Resource.OAUTH2_CONFIGURATION, PermissionChecker.allowAllPermissionChecker); put(Resource.OAUTH2_CONFIGURATION_TEMPLATE, PermissionChecker.allowAllPermissionChecker); } @@ -67,19 +63,4 @@ public class SysAdminPermissions extends AbstractPermissions { }; - private final PermissionChecker sysAdminOAuth2ConfigPermissionChecker = new PermissionChecker() { - @Override - public boolean hasPermission(SecurityUser user, Operation operation) { - return true; - } - - @Override - public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { - if (entity.getTenantId() != null && !entity.getTenantId().isNullUid()) { - return false; - } - return true; - } - }; - } diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java index 0153affa23..c886bccdb8 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java @@ -15,25 +15,17 @@ */ package org.thingsboard.server.service.security.permission; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.service.security.model.SecurityUser; -import java.util.HashMap; - @Component(value="tenantAdminPermissions") public class TenantAdminPermissions extends AbstractPermissions { - @Autowired - private OAuth2Service oAuth2Service; - public TenantAdminPermissions() { super(); put(Resource.ALARM, tenantEntityPermissionChecker); @@ -47,8 +39,6 @@ public class TenantAdminPermissions extends AbstractPermissions { put(Resource.USER, userPermissionChecker); put(Resource.WIDGETS_BUNDLE, widgetsPermissionChecker); put(Resource.WIDGET_TYPE, widgetsPermissionChecker); - put(Resource.OAUTH2_CONFIGURATION, tenantOAuth2ConfigPermissionChecker); - put(Resource.OAUTH2_CONFIGURATION_TEMPLATE, tenantOAuth2ConfigTemplatePermissionChecker); } public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() { @@ -108,31 +98,4 @@ public class TenantAdminPermissions extends AbstractPermissions { } }; - - private final PermissionChecker tenantOAuth2ConfigPermissionChecker = new PermissionChecker() { - @Override - public boolean hasPermission(SecurityUser user, Operation operation) { - return oAuth2Service.isOAuth2ClientRegistrationAllowed(user.getTenantId()); - } - - @Override - public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { - if (!user.getTenantId().equals(entity.getTenantId())) { - return false; - } - return hasPermission(user, operation); - } - }; - - private static final PermissionChecker tenantOAuth2ConfigTemplatePermissionChecker = new PermissionChecker() { - @Override - public boolean hasPermission(SecurityUser user, Operation operation) { - return operation == Operation.READ; - } - - @Override - public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { - return operation == Operation.READ; - } - }; } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java index 2f7156756a..0f4cd1d0be 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java @@ -27,19 +27,15 @@ import java.util.UUID; public interface OAuth2Service { List getOAuth2Clients(String domainName); - List saveDomainsParams(TenantId tenantId, List domainsParams); + List saveDomainsParams(List domainsParams); - List findDomainsParamsByTenantId(TenantId tenantId); + List findDomainsParams(); OAuth2ClientRegistration findClientRegistration(UUID id); List findAllClientRegistrations(); - void deleteClientRegistrationsByTenantId(TenantId tenantId); + void deleteClientRegistrationById(OAuth2ClientRegistrationId id); - void deleteClientRegistrationById(TenantId tenantId, OAuth2ClientRegistrationId id); - - void deleteClientRegistrationsByDomain(TenantId tenantId, String domain); - - boolean isOAuth2ClientRegistrationAllowed(TenantId tenantId); + void deleteClientRegistrationsByDomain(String domain); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java index 64cfb59511..9b1fc12dd5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java @@ -32,9 +32,8 @@ import java.util.List; @Data @ToString(exclude = {"clientSecret"}) @NoArgsConstructor -public class OAuth2ClientRegistration extends SearchTextBasedWithAdditionalInfo implements HasTenantId, HasName { +public class OAuth2ClientRegistration extends SearchTextBasedWithAdditionalInfo implements HasName { - private TenantId tenantId; private String domainName; private String redirectUriTemplate; private OAuth2MapperConfig mapperConfig; @@ -52,7 +51,6 @@ public class OAuth2ClientRegistration extends SearchTextBasedWithAdditionalInfo< public OAuth2ClientRegistration(OAuth2ClientRegistration clientRegistration) { super(clientRegistration); - this.tenantId = clientRegistration.tenantId; this.domainName = clientRegistration.domainName; this.redirectUriTemplate = clientRegistration.redirectUriTemplate; this.mapperConfig = clientRegistration.mapperConfig; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java index e08b83fc37..5c2f70874a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java @@ -31,9 +31,8 @@ import java.util.List; @Data @ToString @NoArgsConstructor -public class OAuth2ClientRegistrationTemplate extends SearchTextBasedWithAdditionalInfo implements HasTenantId, HasName { +public class OAuth2ClientRegistrationTemplate extends SearchTextBasedWithAdditionalInfo implements HasName { - private TenantId tenantId; private String providerId; private OAuth2BasicMapperConfig basic; private String authorizationUri; @@ -50,7 +49,6 @@ public class OAuth2ClientRegistrationTemplate extends SearchTextBasedWithAdditio public OAuth2ClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { super(clientRegistrationTemplate); - this.tenantId = clientRegistrationTemplate.tenantId; this.providerId = clientRegistrationTemplate.providerId; this.basic = clientRegistrationTemplate.basic; this.authorizationUri = clientRegistrationTemplate.authorizationUri; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java index 4f2513f2ce..72e8abe4e6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java @@ -38,9 +38,6 @@ import java.util.UUID; @Table(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_COLUMN_FAMILY_NAME) public class OAuth2ClientRegistrationEntity extends BaseSqlEntity { - @Column(name = ModelConstants.OAUTH2_TENANT_ID_PROPERTY, columnDefinition = "uuid") - private UUID tenantId; - @Column(name = ModelConstants.OAUTH2_DOMAIN_NAME_PROPERTY) private String domainName; @Column(name = ModelConstants.OAUTH2_CLIENT_ID_PROPERTY) @@ -112,9 +109,6 @@ public class OAuth2ClientRegistrationEntity extends BaseSqlEntity { - @Column(name = ModelConstants.OAUTH2_TENANT_ID_PROPERTY, columnDefinition = "uuid") - private UUID tenantId; - @Column(name = ModelConstants.OAUTH2_TEMPLATE_PROVIDER_ID_PROPERTY) private String providerId; @Column(name = ModelConstants.OAUTH2_AUTHORIZATION_URI_PROPERTY) @@ -95,9 +92,6 @@ public class OAuth2ClientRegistrationTemplateEntity extends BaseSqlEntity { List findAll(); - List findByTenantId(UUID tenantId); - List findByDomainName(String domainName); - int removeByTenantIdAndDomainName(UUID tenantId, String domainName); - - int removeByTenantId(UUID tenantId); + int removeByDomainName(String domainName); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java index 4436be34b6..4eaeb914c9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java @@ -44,10 +44,10 @@ public class OAuth2ConfigTemplateServiceImpl extends AbstractEntityService imple @Override public OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { log.trace("Executing saveClientRegistrationTemplate [{}]", clientRegistrationTemplate); - clientRegistrationTemplateValidator.validate(clientRegistrationTemplate, OAuth2ClientRegistrationTemplate::getTenantId); + clientRegistrationTemplateValidator.validate(clientRegistrationTemplate, o -> TenantId.SYS_TENANT_ID); OAuth2ClientRegistrationTemplate savedClientRegistrationTemplate; try { - savedClientRegistrationTemplate = clientRegistrationTemplateDao.save(clientRegistrationTemplate.getTenantId(), clientRegistrationTemplate); + savedClientRegistrationTemplate = clientRegistrationTemplateDao.save(TenantId.SYS_TENANT_ID, clientRegistrationTemplate); } catch (Exception t) { ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("oauth2_template_provider_id_unq_key")) { @@ -98,10 +98,6 @@ public class OAuth2ConfigTemplateServiceImpl extends AbstractEntityService imple if (clientRegistrationTemplate.getBasic() == null) { throw new DataValidationException("Basic mapper config should be specified!"); } - if (clientRegistrationTemplate.getTenantId() == null - || !TenantId.SYS_TENANT_ID.equals(clientRegistrationTemplate.getTenantId())) { - throw new DataValidationException("Client registration template should be assigned to system admin!"); - } } }; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 788c1293da..26bad5566b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -15,30 +15,22 @@ */ package org.thingsboard.server.dao.oauth2; -import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; -import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; -import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.*; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.tenant.TenantService; import javax.transaction.Transactional; import java.util.*; -import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.stream.Collectors; -import static org.thingsboard.server.dao.oauth2.OAuth2Utils.ALLOW_OAUTH2_CONFIGURATION; import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateString; @@ -49,9 +41,6 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se public static final String INCORRECT_CLIENT_REGISTRATION_ID = "Incorrect clientRegistrationId "; public static final String INCORRECT_DOMAIN_NAME = "Incorrect domainName "; - @Autowired - private TenantService tenantService; - @Autowired private OAuth2ClientRegistrationDao clientRegistrationDao; @@ -66,21 +55,20 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se @Override @Transactional - public List saveDomainsParams(TenantId tenantId, List domainsParams) { - log.trace("Executing saveDomainsParams [{}] [{}]", tenantId, domainsParams); - clientParamsValidator.accept(tenantId, domainsParams); - List inputClientRegistrations = OAuth2Utils.toClientRegistrations(tenantId, domainsParams); + public List saveDomainsParams(List domainsParams) { + log.trace("Executing saveDomainsParams [{}]", domainsParams); + clientParamsValidator.accept(domainsParams); + List inputClientRegistrations = OAuth2Utils.toClientRegistrations(domainsParams); List savedClientRegistrations = inputClientRegistrations.stream() - .map(clientRegistration -> clientRegistrationDao.save(clientRegistration.getTenantId(), clientRegistration)) + .map(clientRegistration -> clientRegistrationDao.save(TenantId.SYS_TENANT_ID, clientRegistration)) .collect(Collectors.toList()); return OAuth2Utils.toDomainsParams(savedClientRegistrations); } @Override - public List findDomainsParamsByTenantId(TenantId tenantId) { - log.trace("Executing findDomainsParamsByTenantId [{}]", tenantId); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - return OAuth2Utils.toDomainsParams(clientRegistrationDao.findByTenantId(tenantId.getId())); + public List findDomainsParams() { + log.trace("Executing findDomainsParams"); + return OAuth2Utils.toDomainsParams(clientRegistrationDao.findAll()); } @Override @@ -97,45 +85,21 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se } @Override - @Transactional - public void deleteClientRegistrationsByTenantId(TenantId tenantId) { - log.trace("Executing deleteClientRegistrationsByTenantId [{}]", tenantId); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - clientRegistrationDao.removeByTenantId(tenantId.getId()); - } - - @Override - public void deleteClientRegistrationById(TenantId tenantId, OAuth2ClientRegistrationId id) { - log.trace("Executing deleteClientRegistrationById [{}], [{}]", tenantId, id); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + public void deleteClientRegistrationById(OAuth2ClientRegistrationId id) { + log.trace("Executing deleteClientRegistrationById [{}]", id); validateId(id, INCORRECT_CLIENT_REGISTRATION_ID + id); - clientRegistrationDao.removeById(tenantId, id.getId()); + clientRegistrationDao.removeById(TenantId.SYS_TENANT_ID, id.getId()); } @Override @Transactional - public void deleteClientRegistrationsByDomain(TenantId tenantId, String domain) { - log.trace("Executing deleteClientRegistrationsByDomain [{}], [{}]", tenantId, domain); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + public void deleteClientRegistrationsByDomain(String domain) { + log.trace("Executing deleteClientRegistrationsByDomain [{}]", domain); validateString(domain, INCORRECT_DOMAIN_NAME + domain); - clientRegistrationDao.removeByTenantIdAndDomainName(tenantId.getId(), domain); + clientRegistrationDao.removeByDomainName(domain); } - @Override - public boolean isOAuth2ClientRegistrationAllowed(TenantId tenantId) { - log.trace("Executing isOAuth2ClientRegistrationAllowed [{}]", tenantId); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - Tenant tenant = tenantService.findTenantById(tenantId); - if (tenant == null) return false; - JsonNode allowOAuth2ConfigurationJsonNode = tenant.getAdditionalInfo() != null ? tenant.getAdditionalInfo().get(ALLOW_OAUTH2_CONFIGURATION) : null; - if (allowOAuth2ConfigurationJsonNode == null) { - return false; - } else { - return allowOAuth2ConfigurationJsonNode.asBoolean(); - } - } - - private final BiConsumer> clientParamsValidator = (tenantId, domainsParams) -> { + private final Consumer> clientParamsValidator = domainsParams -> { if (domainsParams == null || domainsParams.isEmpty()) { throw new DataValidationException("Domain params should be specified!"); } @@ -211,13 +175,5 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se } } } - if (tenantId == null) { - throw new DataValidationException("Client registration should be assigned to tenant!"); - } else if (!TenantId.SYS_TENANT_ID.equals(tenantId)) { - Tenant tenant = tenantService.findTenantById(tenantId); - if (tenant == null) { - throw new DataValidationException("Client registration is referencing to non-existent tenant!"); - } - } }; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java index ec2bc59ff7..7af0b80ba5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java @@ -15,8 +15,6 @@ */ package org.thingsboard.server.dao.oauth2; -import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.*; import java.util.ArrayList; @@ -24,7 +22,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import java.util.stream.Stream; public class OAuth2Utils { public static final String ALLOW_OAUTH2_CONFIGURATION = "allowOAuth2Configuration"; @@ -38,10 +35,10 @@ public class OAuth2Utils { return client; } - public static List toClientRegistrations(TenantId tenantId, List domainsParams) { + public static List toClientRegistrations(List domainsParams) { return domainsParams.stream() .flatMap(domainParams -> domainParams.getClientRegistrations().stream() - .map(clientRegistrationDto -> OAuth2Utils.toClientRegistration(tenantId, domainParams.getDomainName(), + .map(clientRegistrationDto -> OAuth2Utils.toClientRegistration(domainParams.getDomainName(), domainParams.getRedirectUriTemplate(), clientRegistrationDto) )) .collect(Collectors.toList()); @@ -80,11 +77,10 @@ public class OAuth2Utils { .build(); } - public static OAuth2ClientRegistration toClientRegistration(TenantId tenantId, String domainName, String redirectUriTemplate, - ClientRegistrationDto clientRegistrationDto) { + public static OAuth2ClientRegistration toClientRegistration(String domainName, String redirectUriTemplate, + ClientRegistrationDto clientRegistrationDto) { OAuth2ClientRegistration clientRegistration = new OAuth2ClientRegistration(); clientRegistration.setId(clientRegistrationDto.getId()); - clientRegistration.setTenantId(tenantId); clientRegistration.setCreatedTime(clientRegistrationDto.getCreatedTime()); clientRegistration.setDomainName(domainName); clientRegistration.setRedirectUriTemplate(redirectUriTemplate); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java index d5e17ac635..c9ba51b813 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java @@ -26,7 +26,6 @@ import org.thingsboard.server.dao.sql.JpaAbstractDao; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; @@ -55,12 +54,6 @@ public class JpaOAuth2ClientRegistrationDao extends JpaAbstractDao findByTenantId(UUID tenantId) { - List entities = repository.findAllByTenantId(tenantId); - return entities.stream().map(DaoUtil::getData).collect(Collectors.toList()); - } - @Override public List findByDomainName(String domainName) { List entities = repository.findAllByDomainName(domainName); @@ -68,12 +61,7 @@ public class JpaOAuth2ClientRegistrationDao extends JpaAbstractDao { - List findAllByTenantId(UUID tenantId); - List findAllByDomainName(String domainName); - int deleteByTenantIdAndDomainName(UUID tenantId, String domainName); - - int deleteByTenantId(UUID tenantId); + int deleteByDomainName(String domainName); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index df0dfb666a..234781e90c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -105,7 +105,6 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe public void deleteTenant(TenantId tenantId) { log.trace("Executing deleteTenant [{}]", tenantId); Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - oAuth2Service.deleteClientRegistrationsByTenantId(tenantId); customerService.deleteCustomersByTenantId(tenantId); widgetsBundleService.deleteWidgetsBundlesByTenantId(tenantId); dashboardService.deleteDashboardsByTenantId(tenantId); diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index 0b7f1c848b..ca92bfcf4f 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -295,7 +295,6 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, created_time bigint NOT NULL, additional_info varchar, - tenant_id uuid, domain_name varchar(255), client_id varchar(255), client_secret varchar(255), @@ -330,7 +329,6 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY, created_time bigint NOT NULL, additional_info varchar, - tenant_id uuid, provider_id varchar(255), authorization_uri varchar(255), token_uri varchar(255), diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index a8f014ab22..d441e0cfd0 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -320,7 +320,6 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, created_time bigint NOT NULL, additional_info varchar, - tenant_id uuid, domain_name varchar(255), client_id varchar(255), client_secret varchar(255), @@ -355,7 +354,6 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY, created_time bigint NOT NULL, additional_info varchar, - tenant_id uuid, provider_id varchar(255), authorization_uri varchar(255), token_uri varchar(255), diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java index f2bf502fab..3d828d1ac5 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java @@ -34,8 +34,6 @@ public class BaseOAuth2ConfigTemplateServiceTest extends AbstractServiceTest { @Autowired protected OAuth2ConfigTemplateService oAuth2ConfigTemplateService; - private TenantId tenantId; - @Before public void beforeRun() throws Exception { Assert.assertTrue(oAuth2ConfigTemplateService.findAllClientRegistrationTemplates().isEmpty()); @@ -53,15 +51,15 @@ public class BaseOAuth2ConfigTemplateServiceTest extends AbstractServiceTest { @Test(expected = DataValidationException.class) public void testSaveDuplicateProviderId() { - OAuth2ClientRegistrationTemplate first = validClientRegistrationTemplate(TenantId.SYS_TENANT_ID, "providerId"); - OAuth2ClientRegistrationTemplate second = validClientRegistrationTemplate(TenantId.SYS_TENANT_ID, "providerId"); + OAuth2ClientRegistrationTemplate first = validClientRegistrationTemplate("providerId"); + OAuth2ClientRegistrationTemplate second = validClientRegistrationTemplate("providerId"); oAuth2ConfigTemplateService.saveClientRegistrationTemplate(first); oAuth2ConfigTemplateService.saveClientRegistrationTemplate(second); } @Test public void testCreateNewTemplate() { - OAuth2ClientRegistrationTemplate clientRegistrationTemplate = validClientRegistrationTemplate(TenantId.SYS_TENANT_ID, UUID.randomUUID().toString()); + OAuth2ClientRegistrationTemplate clientRegistrationTemplate = validClientRegistrationTemplate(UUID.randomUUID().toString()); OAuth2ClientRegistrationTemplate savedClientRegistrationTemplate = oAuth2ConfigTemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate); Assert.assertNotNull(savedClientRegistrationTemplate); @@ -73,7 +71,7 @@ public class BaseOAuth2ConfigTemplateServiceTest extends AbstractServiceTest { @Test public void testFindTemplate() { - OAuth2ClientRegistrationTemplate clientRegistrationTemplate = validClientRegistrationTemplate(TenantId.SYS_TENANT_ID, UUID.randomUUID().toString()); + OAuth2ClientRegistrationTemplate clientRegistrationTemplate = validClientRegistrationTemplate(UUID.randomUUID().toString()); OAuth2ClientRegistrationTemplate savedClientRegistrationTemplate = oAuth2ConfigTemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate); OAuth2ClientRegistrationTemplate foundClientRegistrationTemplate = oAuth2ConfigTemplateService.findClientRegistrationTemplateById(savedClientRegistrationTemplate.getId()); @@ -82,17 +80,17 @@ public class BaseOAuth2ConfigTemplateServiceTest extends AbstractServiceTest { @Test public void testFindAll() { - oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(TenantId.SYS_TENANT_ID, UUID.randomUUID().toString())); - oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(TenantId.SYS_TENANT_ID, UUID.randomUUID().toString())); + oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(UUID.randomUUID().toString())); + oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(UUID.randomUUID().toString())); Assert.assertEquals(2, oAuth2ConfigTemplateService.findAllClientRegistrationTemplates().size()); } @Test public void testDeleteTemplate() { - oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(TenantId.SYS_TENANT_ID, UUID.randomUUID().toString())); - oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(TenantId.SYS_TENANT_ID, UUID.randomUUID().toString())); - OAuth2ClientRegistrationTemplate saved = oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(TenantId.SYS_TENANT_ID, UUID.randomUUID().toString())); + oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(UUID.randomUUID().toString())); + oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(UUID.randomUUID().toString())); + OAuth2ClientRegistrationTemplate saved = oAuth2ConfigTemplateService.saveClientRegistrationTemplate(validClientRegistrationTemplate(UUID.randomUUID().toString())); Assert.assertEquals(3, oAuth2ConfigTemplateService.findAllClientRegistrationTemplates().size()); Assert.assertNotNull(oAuth2ConfigTemplateService.findClientRegistrationTemplateById(saved.getId())); @@ -103,10 +101,9 @@ public class BaseOAuth2ConfigTemplateServiceTest extends AbstractServiceTest { Assert.assertNull(oAuth2ConfigTemplateService.findClientRegistrationTemplateById(saved.getId())); } - private OAuth2ClientRegistrationTemplate validClientRegistrationTemplate(TenantId tenantId, String providerId) { + private OAuth2ClientRegistrationTemplate validClientRegistrationTemplate(String providerId) { OAuth2ClientRegistrationTemplate clientRegistrationTemplate = new OAuth2ClientRegistrationTemplate(); clientRegistrationTemplate.setProviderId(providerId); - clientRegistrationTemplate.setTenantId(tenantId); clientRegistrationTemplate.setAdditionalInfo(mapper.createObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString())); clientRegistrationTemplate.setBasic( OAuth2BasicMapperConfig.builder() diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java index 2231bc6dfe..4ff0716266 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -20,18 +20,14 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.*; -import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.dao.oauth2.OAuth2Utils; -import java.io.IOException; import java.util.*; import java.util.stream.Collectors; -import static org.thingsboard.server.dao.oauth2.OAuth2Utils.ALLOW_OAUTH2_CONFIGURATION; import static org.thingsboard.server.dao.oauth2.OAuth2Utils.toClientRegistrations; public class BaseOAuth2ServiceTest extends AbstractServiceTest { @@ -39,56 +35,26 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Autowired protected OAuth2Service oAuth2Service; - @Autowired - protected AttributesService attributesService; - - private TenantId tenantId; - @Before - public void beforeRun() throws Exception { - Tenant tenant = new Tenant(); - tenant.setTitle("My tenant"); - Tenant savedTenant = tenantService.saveTenant(tenant); - Assert.assertNotNull(savedTenant); - tenantId = savedTenant.getId(); - + public void beforeRun() { Assert.assertTrue(oAuth2Service.findAllClientRegistrations().isEmpty()); } @After - public void after() throws Exception { - tenantService.deleteTenant(tenantId); - oAuth2Service.deleteClientRegistrationsByTenantId(TenantId.SYS_TENANT_ID); - + public void after() { + oAuth2Service.findAllClientRegistrations().forEach(clientRegistration -> { + oAuth2Service.deleteClientRegistrationById(clientRegistration.getId()); + }); Assert.assertTrue(oAuth2Service.findAllClientRegistrations().isEmpty()); } @Test - public void testIsOAuth2Allowed_null() throws IOException { - updateTenantAllowOAuth2Setting(null); - Assert.assertFalse(oAuth2Service.isOAuth2ClientRegistrationAllowed(tenantId)); - } - - @Test - public void testIsOAuth2Allowed_false() throws IOException { - updateTenantAllowOAuth2Setting(false); - Assert.assertFalse(oAuth2Service.isOAuth2ClientRegistrationAllowed(tenantId)); - } - - @Test - public void testIsOAuth2Allowed_true() throws IOException { - updateTenantAllowOAuth2Setting(true); - Assert.assertTrue(oAuth2Service.isOAuth2ClientRegistrationAllowed(tenantId)); - } - - - @Test - public void testCreateNewSystemParams() { - OAuth2ClientRegistration clientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID); - List savedDomainsParams = oAuth2Service.saveDomainsParams(TenantId.SYS_TENANT_ID, OAuth2Utils.toDomainsParams(Collections.singletonList(clientRegistration))); + public void testCreateNewParams() { + OAuth2ClientRegistration clientRegistration = validClientRegistration("domain-name"); + List savedDomainsParams = oAuth2Service.saveDomainsParams(OAuth2Utils.toDomainsParams(Collections.singletonList(clientRegistration))); Assert.assertNotNull(savedDomainsParams); - List savedClientRegistrations = OAuth2Utils.toClientRegistrations(TenantId.SYS_TENANT_ID, savedDomainsParams); + List savedClientRegistrations = OAuth2Utils.toClientRegistrations(savedDomainsParams); Assert.assertEquals(1, savedClientRegistrations.size()); OAuth2ClientRegistration savedClientRegistration = savedClientRegistrations.get(0); @@ -96,95 +62,40 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { clientRegistration.setId(savedClientRegistration.getId()); clientRegistration.setCreatedTime(savedClientRegistration.getCreatedTime()); Assert.assertEquals(clientRegistration, savedClientRegistration); - } - - @Test - public void testFindSystemParamsByTenant() { - OAuth2ClientRegistration clientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID); - oAuth2Service.saveDomainsParams(TenantId.SYS_TENANT_ID, OAuth2Utils.toDomainsParams(Collections.singletonList(clientRegistration))); - - List foundDomainsParams = oAuth2Service.findDomainsParamsByTenantId(TenantId.SYS_TENANT_ID); - Assert.assertEquals(1, foundDomainsParams.size()); - Assert.assertEquals(1, oAuth2Service.findAllClientRegistrations().size()); - List foundClientRegistrations = OAuth2Utils.toClientRegistrations(TenantId.SYS_TENANT_ID, foundDomainsParams); - OAuth2ClientRegistration foundClientRegistration = foundClientRegistrations.get(0); - Assert.assertNotNull(foundClientRegistration); - clientRegistration.setId(foundClientRegistration.getId()); - clientRegistration.setCreatedTime(foundClientRegistration.getCreatedTime()); - Assert.assertEquals(clientRegistration, foundClientRegistration); + oAuth2Service.deleteClientRegistrationsByDomain("domain-name"); } @Test - public void testCreateNewTenantParams() { - OAuth2ClientRegistration clientRegistration = validClientRegistration(tenantId); - List savedDomainsParams = oAuth2Service.saveDomainsParams(tenantId, OAuth2Utils.toDomainsParams(Collections.singletonList(clientRegistration))); - Assert.assertNotNull(savedDomainsParams); + public void testFindDomainParams() { + OAuth2ClientRegistration clientRegistration = validClientRegistration(); + oAuth2Service.saveDomainsParams(OAuth2Utils.toDomainsParams(Collections.singletonList(clientRegistration))); - List savedClientRegistrations = OAuth2Utils.toClientRegistrations(tenantId, savedDomainsParams); - Assert.assertEquals(1, savedClientRegistrations.size()); - - OAuth2ClientRegistration savedClientRegistration = savedClientRegistrations.get(0); - - Assert.assertNotNull(savedClientRegistration); - Assert.assertNotNull(savedClientRegistration.getId()); - clientRegistration.setId(savedClientRegistration.getId()); - clientRegistration.setCreatedTime(savedClientRegistration.getCreatedTime()); - Assert.assertEquals(clientRegistration, savedClientRegistration); - } - - @Test - public void testFindTenantParams() { - OAuth2ClientRegistration clientRegistration = validClientRegistration(tenantId); - oAuth2Service.saveDomainsParams(tenantId, OAuth2Utils.toDomainsParams(Collections.singletonList(clientRegistration))); - - List foundDomainsParams = oAuth2Service.findDomainsParamsByTenantId(tenantId); + List foundDomainsParams = oAuth2Service.findDomainsParams(); Assert.assertEquals(1, foundDomainsParams.size()); Assert.assertEquals(1, oAuth2Service.findAllClientRegistrations().size()); - List foundClientRegistrations = OAuth2Utils.toClientRegistrations(tenantId, foundDomainsParams); + List foundClientRegistrations = OAuth2Utils.toClientRegistrations(foundDomainsParams); OAuth2ClientRegistration foundClientRegistration = foundClientRegistrations.get(0); - Assert.assertNotNull(foundClientRegistration); clientRegistration.setId(foundClientRegistration.getId()); clientRegistration.setCreatedTime(foundClientRegistration.getCreatedTime()); Assert.assertEquals(clientRegistration, foundClientRegistration); } - @Test - public void testGetClientRegistrationWithTenant() { - OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(tenantId); - OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID); - - List savedTenantDomainsParams = oAuth2Service.saveDomainsParams(tenantId, - OAuth2Utils.toDomainsParams(Collections.singletonList(tenantClientRegistration))); - List savedSysAdminDomainsParams = oAuth2Service.saveDomainsParams(TenantId.SYS_TENANT_ID, - OAuth2Utils.toDomainsParams(Collections.singletonList(sysAdminClientRegistration))); - - Assert.assertEquals(2, oAuth2Service.findAllClientRegistrations().size()); - - Assert.assertEquals(savedTenantDomainsParams, oAuth2Service.findDomainsParamsByTenantId(tenantId)); - Assert.assertEquals(savedSysAdminDomainsParams, oAuth2Service.findDomainsParamsByTenantId(TenantId.SYS_TENANT_ID)); - - OAuth2ClientRegistration savedTenantClientRegistration = toClientRegistrations(tenantId, savedTenantDomainsParams).get(0); - Assert.assertEquals(savedTenantClientRegistration, oAuth2Service.findClientRegistration(savedTenantClientRegistration.getUuidId())); - OAuth2ClientRegistration savedSysAdminClientRegistration = toClientRegistrations(TenantId.SYS_TENANT_ID, savedSysAdminDomainsParams).get(0); - Assert.assertEquals(savedSysAdminClientRegistration, oAuth2Service.findClientRegistration(savedSysAdminClientRegistration.getUuidId())); - } - @Test public void testGetOAuth2Clients() { String testDomainName = "test_domain"; - OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(tenantId, testDomainName); - OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID, testDomainName); + OAuth2ClientRegistration first = validClientRegistration(testDomainName); + OAuth2ClientRegistration second = validClientRegistration(testDomainName); - oAuth2Service.saveDomainsParams(tenantId, OAuth2Utils.toDomainsParams(Collections.singletonList(tenantClientRegistration))); - oAuth2Service.saveDomainsParams(TenantId.SYS_TENANT_ID, OAuth2Utils.toDomainsParams(Collections.singletonList(sysAdminClientRegistration))); + oAuth2Service.saveDomainsParams(OAuth2Utils.toDomainsParams(Collections.singletonList(first))); + oAuth2Service.saveDomainsParams(OAuth2Utils.toDomainsParams(Collections.singletonList(second))); List oAuth2Clients = oAuth2Service.getOAuth2Clients(testDomainName); - Set actualLabels = new HashSet<>(Arrays.asList(tenantClientRegistration.getLoginButtonLabel(), - sysAdminClientRegistration.getLoginButtonLabel())); + Set actualLabels = new HashSet<>(Arrays.asList(first.getLoginButtonLabel(), + second.getLoginButtonLabel())); Set foundLabels = oAuth2Clients.stream().map(OAuth2ClientInfo::getName).collect(Collectors.toSet()); Assert.assertEquals(actualLabels, foundLabels); @@ -193,88 +104,61 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testGetEmptyOAuth2Clients() { String testDomainName = "test_domain"; - OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(tenantId, testDomainName); - OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID, testDomainName); - oAuth2Service.saveDomainsParams(tenantId, OAuth2Utils.toDomainsParams(Collections.singletonList(tenantClientRegistration))); - oAuth2Service.saveDomainsParams(TenantId.SYS_TENANT_ID, OAuth2Utils.toDomainsParams(Collections.singletonList(sysAdminClientRegistration))); + OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(testDomainName); + OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(testDomainName); + oAuth2Service.saveDomainsParams(OAuth2Utils.toDomainsParams(Collections.singletonList(tenantClientRegistration))); + oAuth2Service.saveDomainsParams(OAuth2Utils.toDomainsParams(Collections.singletonList(sysAdminClientRegistration))); List oAuth2Clients = oAuth2Service.getOAuth2Clients("random-domain"); Assert.assertTrue(oAuth2Clients.isEmpty()); } @Test public void testDeleteOAuth2ClientRegistration() { - OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(tenantId); - OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(TenantId.SYS_TENANT_ID); + OAuth2ClientRegistration first = validClientRegistration(); + OAuth2ClientRegistration second = validClientRegistration(); - List savedTenantDomainsParams = oAuth2Service.saveDomainsParams(tenantId, - OAuth2Utils.toDomainsParams(Collections.singletonList(tenantClientRegistration))); - List savedSysAdminDomainsParams = oAuth2Service.saveDomainsParams(TenantId.SYS_TENANT_ID, - OAuth2Utils.toDomainsParams(Collections.singletonList(sysAdminClientRegistration))); + List savedFirstDomainsParams = oAuth2Service.saveDomainsParams( + OAuth2Utils.toDomainsParams(Collections.singletonList(first))); + List savedSecondDomainsParams = oAuth2Service.saveDomainsParams( + OAuth2Utils.toDomainsParams(Collections.singletonList(second))); - OAuth2ClientRegistration savedTenantRegistration = toClientRegistrations(tenantId, savedTenantDomainsParams).get(0); - OAuth2ClientRegistration savedSysAdminRegistration = toClientRegistrations(TenantId.SYS_TENANT_ID, savedSysAdminDomainsParams).get(0); + OAuth2ClientRegistration savedFirstRegistration = toClientRegistrations(savedFirstDomainsParams).get(0); + OAuth2ClientRegistration savedSecondRegistration = toClientRegistrations(savedSecondDomainsParams).get(0); - oAuth2Service.deleteClientRegistrationById(tenantId, savedTenantRegistration.getId()); + oAuth2Service.deleteClientRegistrationById(savedFirstRegistration.getId()); List foundRegistrations = oAuth2Service.findAllClientRegistrations(); Assert.assertEquals(1, foundRegistrations.size()); - Assert.assertEquals(savedSysAdminRegistration, foundRegistrations.get(0)); + Assert.assertEquals(savedSecondRegistration, foundRegistrations.get(0)); } @Test - public void testDeleteTenantOAuth2ClientRegistrations() { - oAuth2Service.saveDomainsParams(tenantId, OAuth2Utils.toDomainsParams(Arrays.asList( - validClientRegistration(tenantId, "domain"), - validClientRegistration(tenantId, "domain"), - validClientRegistration(tenantId, "domain") + public void testDeleteDomainOAuth2ClientRegistrations() { + oAuth2Service.saveDomainsParams(OAuth2Utils.toDomainsParams(Arrays.asList( + validClientRegistration("domain1"), + validClientRegistration("domain1"), + validClientRegistration("domain2") ))); - Assert.assertEquals(3, oAuth2Service.findAllClientRegistrations().size()); - Assert.assertEquals(1, oAuth2Service.findDomainsParamsByTenantId(tenantId).size()); - - oAuth2Service.deleteClientRegistrationsByTenantId(tenantId); - Assert.assertEquals(0, oAuth2Service.findAllClientRegistrations().size()); - Assert.assertEquals(0, oAuth2Service.findDomainsParamsByTenantId(tenantId).size()); - } - - @Test - public void testDeleteTenantDomainOAuth2ClientRegistrations() { - oAuth2Service.saveDomainsParams(tenantId, OAuth2Utils.toDomainsParams(Arrays.asList( - validClientRegistration(tenantId, "domain1"), - validClientRegistration(tenantId, "domain1"), - validClientRegistration(tenantId, "domain2") - ))); - oAuth2Service.saveDomainsParams(TenantId.SYS_TENANT_ID, OAuth2Utils.toDomainsParams(Arrays.asList( - validClientRegistration(TenantId.SYS_TENANT_ID, "domain2") + oAuth2Service.saveDomainsParams(OAuth2Utils.toDomainsParams(Arrays.asList( + validClientRegistration("domain2") ))); Assert.assertEquals(4, oAuth2Service.findAllClientRegistrations().size()); - List tenantDomainsParams = oAuth2Service.findDomainsParamsByTenantId(tenantId); - List tenantClientRegistrations = toClientRegistrations(tenantId, tenantDomainsParams); - Assert.assertEquals(2, tenantDomainsParams.size()); - Assert.assertEquals(3, tenantClientRegistrations.size()); + List domainsParams = oAuth2Service.findDomainsParams(); + List clientRegistrations = toClientRegistrations(domainsParams); + Assert.assertEquals(2, domainsParams.size()); + Assert.assertEquals(4, clientRegistrations.size()); - oAuth2Service.deleteClientRegistrationsByDomain(tenantId, "domain1"); + oAuth2Service.deleteClientRegistrationsByDomain("domain1"); Assert.assertEquals(2, oAuth2Service.findAllClientRegistrations().size()); - Assert.assertEquals(1, oAuth2Service.findDomainsParamsByTenantId(tenantId).size()); - Assert.assertEquals(1, toClientRegistrations(tenantId, oAuth2Service.findDomainsParamsByTenantId(tenantId)).size()); - } - - private void updateTenantAllowOAuth2Setting(Boolean allowOAuth2) throws IOException { - Tenant tenant = tenantService.findTenantById(tenantId); - if (allowOAuth2 == null) { - tenant.setAdditionalInfo(mapper.readTree("{}")); - } else { - String additionalInfo = "{\"" + ALLOW_OAUTH2_CONFIGURATION + "\":" + allowOAuth2 + "}"; - tenant.setAdditionalInfo(mapper.readTree(additionalInfo)); - tenantService.saveTenant(tenant); - } + Assert.assertEquals(1, oAuth2Service.findDomainsParams().size()); + Assert.assertEquals(2, toClientRegistrations(oAuth2Service.findDomainsParams()).size()); } - private OAuth2ClientRegistration validClientRegistration(TenantId tenantId) { - return validClientRegistration(tenantId, "domainName"); + private OAuth2ClientRegistration validClientRegistration() { + return validClientRegistration("domainName"); } - private OAuth2ClientRegistration validClientRegistration(TenantId tenantId, String domainName) { + private OAuth2ClientRegistration validClientRegistration(String domainName) { OAuth2ClientRegistration clientRegistration = new OAuth2ClientRegistration(); - clientRegistration.setTenantId(tenantId); clientRegistration.setDomainName(domainName); clientRegistration.setMapperConfig( OAuth2MapperConfig.builder() From 3816172b2ad59736e120ad75861b5ef464af24df Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 21 Sep 2020 18:52:54 +0300 Subject: [PATCH 101/463] Refactoring OAuth2 --- .../admin/oauth2-settings.component.html | 10 +-- .../pages/admin/oauth2-settings.component.ts | 20 +++-- .../assets/locale/locale.constant-en_US.json | 79 ++++++++++--------- 3 files changed, 57 insertions(+), 52 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html index 4e45e02945..9a0d9a1858 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -37,7 +37,7 @@ - {{ domain.get('domainName').value ? domain.get('domainName').value : ("admin.new-domain" | translate) }} + {{ domain.get('domainName').value ? domain.get('domainName').value : ("admin.oauth2.new-domain" | translate) }}
@@ -396,7 +396,7 @@ [disabled]="(isLoading$ | async)" (click)="addDomain()" type="button"> - {{'admin.add-domain' | translate}} + {{'admin.oauth2.add-domain' | translate}} + + + {{ 'device.client-id-or-user-name-necessary' | translate }} + + + diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts new file mode 100644 index 0000000000..298270d153 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts @@ -0,0 +1,229 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormControl, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + ValidatorFn, + Validators +} from '@angular/forms'; +import { + credentialTypeNames, + DeviceCredentialMQTTBasic, + DeviceCredentials, + DeviceCredentialsType +} from '@shared/models/device.models'; +import { Subscription } from 'rxjs'; +import { isDefinedAndNotNull } from '@core/utils'; +import { distinctUntilChanged } from 'rxjs/operators'; + +@Component({ + selector: 'tb-device-credentials', + templateUrl: './device-credentials.component.html', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DeviceCredentialsComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => DeviceCredentialsComponent), + multi: true, + }], + styleUrls: [] +}) +export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, Validator, OnDestroy { + + deviceCredentialsFormGroup: FormGroup; + + subscriptions: Subscription[] = []; + + @Input() + disabled: boolean; + + deviceCredentials: DeviceCredentials = null; + + submitted = false; + + deviceCredentialsType = DeviceCredentialsType; + + credentialsTypes = Object.keys(DeviceCredentialsType); + + credentialTypeNamesMap = credentialTypeNames; + + hidePassword = true; + + private propagateChange = (v: any) => {}; + + constructor(public fb: FormBuilder) { + this.deviceCredentialsFormGroup = this.fb.group({ + credentialsType: [DeviceCredentialsType.ACCESS_TOKEN], + credentialsId: [null], + credentialsValue: [null], + credentialsBasic: this.fb.group({ + clientId: [null, [Validators.pattern(/^[A-Za-z0-9]+$/)]], + userName: [null], + password: [null] + }, {validators: this.atLeastOne(Validators.required, ['clientId', 'userName'])}) + }); + this.deviceCredentialsFormGroup.get('credentialsBasic').disable(); + this.subscriptions.push( + this.deviceCredentialsFormGroup.valueChanges.pipe(distinctUntilChanged()).subscribe(() => { + this.updateView(); + }) + ); + this.subscriptions.push( + this.deviceCredentialsFormGroup.get('credentialsType').valueChanges.subscribe(() => { + this.credentialsTypeChanged(); + }) + ); + } + + ngOnInit(): void { + if (this.disabled) { + this.deviceCredentialsFormGroup.disable({emitEvent: false}); + } + } + + ngOnDestroy() { + this.subscriptions.forEach(s => s.unsubscribe()); + } + + writeValue(value: DeviceCredentials | null): void { + if (isDefinedAndNotNull(value)) { + this.deviceCredentials = value; + let credentialsBasic = {clientId: null, userName: null, password: null}; + let credentialsValue = null; + if (value.credentialsType === DeviceCredentialsType.MQTT_BASIC) { + credentialsBasic = JSON.parse(value.credentialsValue) as DeviceCredentialMQTTBasic; + } else { + credentialsValue = value.credentialsValue; + } + this.deviceCredentialsFormGroup.patchValue({ + credentialsType: value.credentialsType, + credentialsId: value.credentialsId, + credentialsValue, + credentialsBasic + }, {emitEvent: false}); + this.updateValidators(); + } + } + + updateView() { + const deviceCredentialsValue = this.deviceCredentialsFormGroup.value; + if (deviceCredentialsValue.credentialsType === DeviceCredentialsType.MQTT_BASIC) { + deviceCredentialsValue.credentialsValue = JSON.stringify(deviceCredentialsValue.credentialsBasic); + } + delete deviceCredentialsValue.credentialsBasic; + this.propagateChange(deviceCredentialsValue); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void {} + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.deviceCredentialsFormGroup.disable({emitEvent: false}); + } else { + this.deviceCredentialsFormGroup.enable({emitEvent: false}); + this.updateValidators(); + } + } + + public validate(c: FormControl) { + return this.deviceCredentialsFormGroup.valid ? null : { + deviceCredentials: { + valid: false, + }, + }; + } + + credentialsTypeChanged(): void { + this.deviceCredentialsFormGroup.patchValue({ + credentialsId: null, + credentialsValue: null, + credentialsBasic: {clientId: '', userName: '', password: ''} + }); + this.updateValidators(); + } + + updateValidators(): void { + this.hidePassword = true; + const crendetialsType = this.deviceCredentialsFormGroup.get('credentialsType').value as DeviceCredentialsType; + switch (crendetialsType) { + case DeviceCredentialsType.ACCESS_TOKEN: + this.deviceCredentialsFormGroup.get('credentialsId').setValidators([Validators.required, Validators.pattern(/^.{1,20}$/)]); + this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity({emitEvent: false}); + this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]); + this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false}); + this.deviceCredentialsFormGroup.get('credentialsBasic').disable({emitEvent: false}); + break; + case DeviceCredentialsType.X509_CERTIFICATE: + this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required]); + this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false}); + this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]); + this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity({emitEvent: false}); + this.deviceCredentialsFormGroup.get('credentialsBasic').disable({emitEvent: false}); + break; + case DeviceCredentialsType.MQTT_BASIC: + this.deviceCredentialsFormGroup.get('credentialsBasic').enable({emitEvent: false}); + this.deviceCredentialsFormGroup.get('credentialsBasic').updateValueAndValidity({emitEvent: false}); + this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]); + this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity({emitEvent: false}); + this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]); + this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false}); + } + } + + private atLeastOne(validator: ValidatorFn, controls: string[] = null) { + return (group: FormGroup): ValidationErrors | null => { + if (!controls) { + controls = Object.keys(group.controls); + } + const hasAtLeastOne = group?.controls && controls.some(k => !validator(group.controls[k])); + + return hasAtLeastOne ? null : {atLeastOne: true}; + }; + } + + passwordChanged() { + const value = this.deviceCredentialsFormGroup.get('credentialsBasic.password').value; + if (value !== '') { + this.deviceCredentialsFormGroup.get('credentialsBasic.userName').setValidators([Validators.required]); + if (this.deviceCredentialsFormGroup.get('credentialsBasic.userName').untouched) { + this.deviceCredentialsFormGroup.get('credentialsBasic.userName').markAsTouched({onlySelf: true}); + } + } else { + this.deviceCredentialsFormGroup.get('credentialsBasic.userName').setValidators([]); + } + this.deviceCredentialsFormGroup.get('credentialsBasic.userName').updateValueAndValidity({ + emitEvent: false, + onlySelf: true + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index d583369016..2bbf2cb01d 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -107,6 +107,7 @@ import { AlarmRuleKeyFiltersDialogComponent } from './profile/alarm/alarm-rule-k import { FilterTextComponent } from './filter/filter-text.component'; import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component'; import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component'; +import { DeviceCredentialsComponent } from './device/device-credentials.component'; @NgModule({ declarations: @@ -196,7 +197,8 @@ import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomp DeviceProfileComponent, DeviceProfileDialogComponent, AddDeviceProfileDialogComponent, - RuleChainAutocompleteComponent + RuleChainAutocompleteComponent, + DeviceCredentialsComponent ], imports: [ CommonModule, @@ -275,7 +277,8 @@ import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomp DeviceProfileComponent, DeviceProfileDialogComponent, AddDeviceProfileDialogComponent, - RuleChainAutocompleteComponent + RuleChainAutocompleteComponent, + DeviceCredentialsComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html index f1b5b442ce..0c99fd7040 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html @@ -30,65 +30,9 @@
- - device.credentials-type - - - {{ credentialTypeNamesMap.get(deviceCredentialsType[credentialsType]) }} - - - - - device.access-token - - - {{ 'device.access-token-required' | translate }} - - - {{ 'device.access-token-invalid' | translate }} - - - - device.rsa-key - - - {{ 'device.rsa-key-required' | translate }} - - -
- - device.client-id - - - {{ 'device.client-id-pattern' | translate }} - - - - device.user-name - - - {{ 'device.user-name-required' | translate }} - - - - device.password - - - - - {{ 'device.client-id-or-user-name-necessary' | translate }} - -
+ +
diff --git a/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts index 73e5ae040f..94083cc10b 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts @@ -19,23 +19,9 @@ import { ErrorStateMatcher } from '@angular/material/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { - FormBuilder, - FormControl, - FormGroup, - FormGroupDirective, - NgForm, - ValidationErrors, - ValidatorFn, - Validators -} from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm } from '@angular/forms'; import { DeviceService } from '@core/http/device.service'; -import { - credentialTypeNames, - DeviceCredentialMQTTBasic, - DeviceCredentials, - DeviceCredentialsType -} from '@shared/models/device.models'; +import { credentialTypeNames, DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models'; import { DialogComponent } from '@shared/components/dialog.component'; import { Router } from '@angular/router'; @@ -83,19 +69,10 @@ export class DeviceCredentialsDialogComponent extends ngOnInit(): void { this.deviceCredentialsFormGroup = this.fb.group({ - credentialsType: [DeviceCredentialsType.ACCESS_TOKEN], - credentialsId: [''], - credentialsValue: [''], - credentialsBasic: this.fb.group({ - clientId: ['', [Validators.pattern(/^[A-Za-z0-9]+$/)]], - userName: [''], - password: [''] - }, {validators: this.atLeastOne(Validators.required, ['clientId', 'userName'])}) + credential: [null] }); if (this.isReadOnly) { this.deviceCredentialsFormGroup.disable({emitEvent: false}); - } else { - this.registerDisableOnLoadFormControl(this.deviceCredentialsFormGroup.get('credentialsType')); } this.loadDeviceCredentials(); } @@ -110,82 +87,20 @@ export class DeviceCredentialsDialogComponent extends this.deviceService.getDeviceCredentials(this.data.deviceId).subscribe( (deviceCredentials) => { this.deviceCredentials = deviceCredentials; - let credentialsValue = deviceCredentials.credentialsValue; - let credentialsBasic = {clientId: null, userName: null, password: null}; - if (deviceCredentials.credentialsType === DeviceCredentialsType.MQTT_BASIC) { - credentialsValue = null; - credentialsBasic = JSON.parse(deviceCredentials.credentialsValue) as DeviceCredentialMQTTBasic; - } this.deviceCredentialsFormGroup.patchValue({ - credentialsType: deviceCredentials.credentialsType, - credentialsId: deviceCredentials.credentialsId, - credentialsValue, - credentialsBasic - }); - this.updateValidators(); + credential: deviceCredentials + }, {emitEvent: false}); } ); } - credentialsTypeChanged(): void { - this.deviceCredentialsFormGroup.patchValue({ - credentialsId: null, - credentialsValue: null, - credentialsBasic: {clientId: '', userName: '', password: ''} - }, {emitEvent: true}); - this.updateValidators(); - } - - updateValidators(): void { - this.hidePassword = true; - const crendetialsType = this.deviceCredentialsFormGroup.get('credentialsType').value as DeviceCredentialsType; - switch (crendetialsType) { - case DeviceCredentialsType.ACCESS_TOKEN: - this.deviceCredentialsFormGroup.get('credentialsId').setValidators([Validators.required, Validators.pattern(/^.{1,20}$/)]); - this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity(); - this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]); - this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity(); - this.deviceCredentialsFormGroup.get('credentialsBasic').disable(); - break; - case DeviceCredentialsType.X509_CERTIFICATE: - this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required]); - this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity(); - this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]); - this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity(); - this.deviceCredentialsFormGroup.get('credentialsBasic').disable(); - break; - case DeviceCredentialsType.MQTT_BASIC: - this.deviceCredentialsFormGroup.get('credentialsBasic').enable(); - this.deviceCredentialsFormGroup.get('credentialsBasic').updateValueAndValidity(); - this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]); - this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity(); - this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]); - this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity(); - } - } - - private atLeastOne(validator: ValidatorFn, controls: string[] = null) { - return (group: FormGroup): ValidationErrors | null => { - if (!controls) { - controls = Object.keys(group.controls); - } - const hasAtLeastOne = group?.controls && controls.some(k => !validator(group.controls[k])); - - return hasAtLeastOne ? null : {atLeastOne: true}; - }; - } - cancel(): void { this.dialogRef.close(null); } save(): void { this.submitted = true; - const deviceCredentialsValue = this.deviceCredentialsFormGroup.value; - if (deviceCredentialsValue.credentialsType === DeviceCredentialsType.MQTT_BASIC) { - deviceCredentialsValue.credentialsValue = JSON.stringify(deviceCredentialsValue.credentialsBasic); - } - delete deviceCredentialsValue.credentialsBasic; + const deviceCredentialsValue = this.deviceCredentialsFormGroup.value.credential; this.deviceCredentials = {...this.deviceCredentials, ...deviceCredentialsValue}; this.deviceService.saveDeviceCredentials(this.deviceCredentials).subscribe( (deviceCredentials) => { @@ -193,18 +108,4 @@ export class DeviceCredentialsDialogComponent extends } ); } - - passwordChanged() { - const value = this.deviceCredentialsFormGroup.get('credentialsBasic.password').value; - if (value !== '') { - this.deviceCredentialsFormGroup.get('credentialsBasic.userName').setValidators([Validators.required]); - if (this.deviceCredentialsFormGroup.get('credentialsBasic.userName').untouched) { - this.deviceCredentialsFormGroup.get('credentialsBasic.userName').markAsTouched({onlySelf: true}); - } - this.deviceCredentialsFormGroup.get('credentialsBasic.userName').updateValueAndValidity(); - } else { - this.deviceCredentialsFormGroup.get('credentialsBasic.userName').setValidators([]); - this.deviceCredentialsFormGroup.get('credentialsBasic.userName').updateValueAndValidity(); - } - } } From 950907d3980f9602f8b10a53cf036f5e5698118b Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 29 Sep 2020 11:30:32 +0300 Subject: [PATCH 113/463] Refactoring according to comments from @YevhenBondarenko --- .../server/dao/device/provision/ProvisionResponse.java | 6 ++---- .../server/transport/mqtt/MqttTransportHandler.java | 4 ---- .../server/common/transport/adaptor/JsonConverter.java | 3 +-- .../org/thingsboard/server/dao/device/DeviceProfileDao.java | 1 - .../server/dao/sql/device/JpaDeviceProfileDao.java | 1 - 5 files changed, 3 insertions(+), 12 deletions(-) diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponse.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponse.java index 509823eeb2..d38b53b134 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponse.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponse.java @@ -15,13 +15,11 @@ */ package org.thingsboard.server.dao.device.provision; -import lombok.AllArgsConstructor; import lombok.Data; import org.thingsboard.server.common.data.security.DeviceCredentials; @Data -@AllArgsConstructor public class ProvisionResponse { - private DeviceCredentials deviceCredentials; - private ProvisionResponseStatus responseStatus; + private final DeviceCredentials deviceCredentials; + private final ProvisionResponseStatus responseStatus; } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 37738b89a6..127bceb10a 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -167,10 +167,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement case PINGREQ: ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0))); break; -// case SUBSCRIBE: -// deviceSessionCtx.setDeviceInfo(TransportDeviceInfo); -// processSubscribe(ctx, (MqttSubscribeMessage) msg); -// break; case DISCONNECT: ctx.close(); break; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index c63c4d7bb8..13dda0a10a 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -417,8 +417,7 @@ public class JsonConverter { if (toGateway) { result.addProperty("id", requestId); } - result.addProperty("deviceId", new DeviceId( - new UUID(payload.getDeviceCredentials().getDeviceIdMSB(), payload.getDeviceCredentials().getDeviceIdLSB())).toString()); + result.addProperty("deviceId", new UUID(payload.getDeviceCredentials().getDeviceIdMSB(), payload.getDeviceCredentials().getDeviceIdLSB()).toString()); result.addProperty("credentialsType", payload.getDeviceCredentials().getCredentialsType().name()); result.addProperty("credentialsId", payload.getDeviceCredentials().getCredentialsId()); result.addProperty("credentialsValue", diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java index 3c5a9269a1..2d65448d02 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java @@ -22,7 +22,6 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; -import java.util.Optional; import java.util.UUID; public interface DeviceProfileDao extends Dao { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java index 6fa5768603..3a812e78da 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java @@ -29,7 +29,6 @@ import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import java.util.Objects; -import java.util.Optional; import java.util.UUID; @Component From f2af7d0831b9359cebf76452510901cb26ce40c4 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 29 Sep 2020 15:47:17 +0300 Subject: [PATCH 114/463] UI add device wizard --- .../app/core/http/device-profile.service.ts | 12 +- .../home/components/home-components.module.ts | 3 + .../add-device-profile-dialog.component.ts | 3 +- ...device-profile-autocomplete.component.html | 8 +- .../device-profile-autocomplete.component.ts | 35 ++- .../device-wizard-dialog.component.html | 155 ++++++++++ .../device-wizard-dialog.component.scss | 38 +++ .../wizard/device-wizard-dialog.component.ts | 286 ++++++++++++++++++ .../device-profiles-table-config.resolver.ts | 3 +- .../device/devices-table-config.resolver.ts | 36 ++- .../assets/locale/locale.constant-en_US.json | 12 +- 11 files changed, 562 insertions(+), 29 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts diff --git a/ui-ngx/src/app/core/http/device-profile.service.ts b/ui-ngx/src/app/core/http/device-profile.service.ts index 8cdc8c188b..c535878fd2 100644 --- a/ui-ngx/src/app/core/http/device-profile.service.ts +++ b/ui-ngx/src/app/core/http/device-profile.service.ts @@ -20,7 +20,8 @@ import { PageLink } from '@shared/models/page/page-link'; import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable } from 'rxjs'; import { PageData } from '@shared/models/page/page-data'; -import { DeviceProfile, DeviceProfileInfo } from '@shared/models/device.models'; +import { DeviceProfile, DeviceProfileInfo, DeviceTransportType } from '@shared/models/device.models'; +import { isDefinedAndNotNull } from '@core/utils'; @Injectable({ providedIn: 'root' @@ -59,8 +60,13 @@ export class DeviceProfileService { return this.http.get(`/api/deviceProfileInfo/${deviceProfileId}`, defaultHttpOptionsFromConfig(config)); } - public getDeviceProfileInfos(pageLink: PageLink, config?: RequestConfig): Observable> { - return this.http.get>(`/api/deviceProfileInfos${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); + public getDeviceProfileInfos(pageLink: PageLink, transportType?: DeviceTransportType, + config?: RequestConfig): Observable> { + let url = `/api/deviceProfileInfos${pageLink.toQuery()}`; + if (isDefinedAndNotNull(transportType)) { + url += `&transportType=${transportType}`; + } + return this.http.get>(url, defaultHttpOptionsFromConfig(config)); } } diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 2bbf2cb01d..f3d912e16d 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -107,6 +107,7 @@ import { AlarmRuleKeyFiltersDialogComponent } from './profile/alarm/alarm-rule-k import { FilterTextComponent } from './filter/filter-text.component'; import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component'; import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component'; +import { DeviceWizardDialogComponent } from './wizard/device-wizard-dialog.component'; import { DeviceCredentialsComponent } from './device/device-credentials.component'; @NgModule({ @@ -198,6 +199,7 @@ import { DeviceCredentialsComponent } from './device/device-credentials.componen DeviceProfileDialogComponent, AddDeviceProfileDialogComponent, RuleChainAutocompleteComponent, + DeviceWizardDialogComponent, DeviceCredentialsComponent ], imports: [ @@ -278,6 +280,7 @@ import { DeviceCredentialsComponent } from './device/device-credentials.componen DeviceProfileDialogComponent, AddDeviceProfileDialogComponent, RuleChainAutocompleteComponent, + DeviceWizardDialogComponent, DeviceCredentialsComponent ], providers: [ diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts index 476c69488f..a88b250c7e 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts @@ -46,6 +46,7 @@ import { RuleChainId } from '@shared/models/id/rule-chain-id'; export interface AddDeviceProfileDialogData { deviceProfileName: string; + transportType: DeviceTransportType; } @Component({ @@ -97,7 +98,7 @@ export class AddDeviceProfileDialogComponent extends ); this.transportConfigFormGroup = this.fb.group( { - transportType: [DeviceTransportType.DEFAULT, [Validators.required]], + transportType: [data.transportType ? data.transportType : DeviceTransportType.DEFAULT, [Validators.required]], transportConfiguration: [createDeviceProfileTransportConfiguration(DeviceTransportType.DEFAULT), [Validators.required]] } diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html index a8af9c2738..9941b1db94 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html @@ -48,7 +48,7 @@
-
+
device-profile.no-device-profiles-found
@@ -56,10 +56,10 @@ {{ translate.get('device-profile.no-device-profiles-matching', {entity: truncate.transform(searchText, true, 6, '...')}) | async }} + + device-profile.create-new-device-profile + - - device-profile.create-new-device-profile -
diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts index c419b7f11c..835c31a477 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts @@ -19,7 +19,8 @@ import { ElementRef, EventEmitter, forwardRef, - Input, NgZone, + Input, + NgZone, OnInit, Output, ViewChild @@ -38,14 +39,7 @@ import { TruncatePipe } from '@shared//pipe/truncate.pipe'; import { ENTER } from '@angular/cdk/keycodes'; import { MatDialog } from '@angular/material/dialog'; import { DeviceProfileId } from '@shared/models/id/device-profile-id'; -import { - createDeviceProfileConfiguration, - createDeviceProfileTransportConfiguration, - DeviceProfile, - DeviceProfileInfo, - DeviceProfileType, - DeviceTransportType -} from '@shared/models/device.models'; +import { DeviceProfile, DeviceProfileInfo, DeviceProfileType, DeviceTransportType } from '@shared/models/device.models'; import { DeviceProfileService } from '@core/http/device-profile.service'; import { DeviceProfileDialogComponent, DeviceProfileDialogData } from './device-profile-dialog.component'; import { MatAutocomplete } from '@angular/material/autocomplete'; @@ -76,6 +70,12 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, @Input() editProfileEnabled = true; + @Input() + addNewProfile = true; + + @Input() + transportType: DeviceTransportType = null; + private requiredValue: boolean; get required(): boolean { return this.requiredValue; @@ -183,6 +183,11 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; + if (this.disabled) { + this.selectDeviceProfileFormGroup.disable(); + } else { + this.selectDeviceProfileFormGroup.enable(); + } } writeValue(value: DeviceProfileId | null): void { @@ -244,7 +249,7 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, property: 'name', direction: Direction.ASC }); - return this.deviceProfileService.getDeviceProfileInfos(pageLink, {ignoreLoading: true}).pipe( + return this.deviceProfileService.getDeviceProfileInfos(pageLink, this.transportType, {ignoreLoading: true}).pipe( map(pageData => { let data = pageData.data; if (this.displayAllOnEmpty) { @@ -280,9 +285,12 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, createDeviceProfile($event: Event, profileName: string) { $event.preventDefault(); const deviceProfile: DeviceProfile = { - name: profileName + name: profileName, + transportType: this.transportType } as DeviceProfile; - this.openDeviceProfileDialog(deviceProfile, true); + if (this.addNewProfile) { + this.openDeviceProfileDialog(deviceProfile, true); + } } editDeviceProfile($event: Event) { @@ -312,7 +320,8 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { - deviceProfileName: deviceProfile.name + deviceProfileName: deviceProfile.name, + transportType: deviceProfile.transportType } }).afterClosed(); } diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html new file mode 100644 index 0000000000..c391bc848a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -0,0 +1,155 @@ + +
+ +

device.add-device-text

+ + +
+ + +
+
+ + + check + + + + {{ 'device.wizard.device-details' | translate}} +
+ + device.name + + + {{ 'device.name-required' | translate }} + + + + device.label + + + + device-profile.transport-type + + + {{deviceTransportTypeTranslations.get(type) | translate}} + + + + {{ 'device-profile.transport-type-required' | translate }} + + + + {{ 'device.is-gateway' | translate }} + + + device.description + + +
+ +
+ +
+ {{ 'device.wizard.profile-configuration' | translate}} + + +
+ device.wizard.existing-device-profile + + +
+
+ +
+ device.wizard.new-device-profile + + device-profile.device-profile + + + {{ 'device-profile.device-profile-required' | translate }} + + +
+
+
+
+
+ +
+ {{ 'device-profile.transport-configuration' | translate }} + + +
+
+ +
+ {{'device-profile.alarm-rules' | translate: + {count: alarmRulesFormGroup.get('alarms').value ? + alarmRulesFormGroup.get('alarms').value.length : 0} }} + + +
+
+ + {{ 'device.wizard.specific-configuration' | translate }} +
+ + + {{ 'device.wizard.add-credential' | translate }} + + +
+
+
+
+
+ + +
+ + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss new file mode 100644 index 0000000000..cafcce74b6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + .mat-dialog-content { + display: flex; + flex-direction: column; + overflow: hidden; + + .mat-stepper-horizontal { + display: flex; + flex-direction: column; + overflow: hidden; + } + } +} + +:host ::ng-deep { + .mat-dialog-content { + .mat-stepper-horizontal { + .mat-horizontal-content-container { + overflow: auto; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts new file mode 100644 index 0000000000..15efa868b2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts @@ -0,0 +1,286 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, OnDestroy, SkipSelf, ViewChild } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Router } from '@angular/router'; +import { + createDeviceProfileConfiguration, + createDeviceProfileTransportConfiguration, + DeviceProfile, + DeviceProfileType, + DeviceTransportType, + deviceTransportTypeTranslationMap +} from '@shared/models/device.models'; +import { MatHorizontalStepper } from '@angular/material/stepper'; +import { AddEntityDialogData } from '@home/models/entity/entity-component.models'; +import { BaseData, HasId } from '@shared/models/base-data'; +import { EntityType } from '@shared/models/entity-type.models'; +import { DeviceProfileService } from '@core/http/device-profile.service'; +import { EntityId } from '@shared/models/id/entity-id'; +import { BehaviorSubject, Observable, of, Subscription } from 'rxjs'; +import { map, mergeMap, tap } from 'rxjs/operators'; +import { DeviceService } from '@core/http/device.service'; +import { ErrorStateMatcher } from '@angular/material/core'; +import { StepperSelectionEvent } from '@angular/cdk/stepper'; + +@Component({ + selector: 'tb-device-wizard', + templateUrl: './device-wizard-dialog.component.html', + providers: [], + styleUrls: ['./device-wizard-dialog.component.scss'] +}) +export class DeviceWizardDialogComponent extends + DialogComponent implements OnDestroy, ErrorStateMatcher { + + @ViewChild('addDeviceWizardStepper', {static: true}) addDeviceWizardStepper: MatHorizontalStepper; + + selectedIndex = 0; + + nextStepButtonLabel$ = new BehaviorSubject('action.continue'); + + createdProfile = false; + + entityType = EntityType; + + deviceTransportTypes = Object.keys(DeviceTransportType); + + deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; + + deviceWizardFormGroup: FormGroup; + + profileConfigFormGroup: FormGroup; + + transportConfigFormGroup: FormGroup; + + alarmRulesFormGroup: FormGroup; + + specificConfigFormGroup: FormGroup; + + private subscriptions: Subscription[] = []; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: AddEntityDialogData>, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + private deviceProfileService: DeviceProfileService, + private deviceService: DeviceService, + private fb: FormBuilder) { + super(store, router, dialogRef); + this.deviceWizardFormGroup = this.fb.group({ + name: ['', Validators.required], + label: [''], + gateway: [false], + transportType: [DeviceTransportType.DEFAULT, Validators.required], + description: [''] + } + ); + + this.profileConfigFormGroup = this.fb.group({ + addProfileType: [0], + deviceProfileId: [null, Validators.required], + newDeviceProfileTitle: [{value: null, disabled: true}] + } + ); + + this.subscriptions.push(this.profileConfigFormGroup.get('addProfileType').valueChanges.subscribe( + (addProfileType: number) => { + if (addProfileType === 0) { + this.profileConfigFormGroup.get('deviceProfileId').setValidators([Validators.required]); + this.profileConfigFormGroup.get('deviceProfileId').enable(); + this.profileConfigFormGroup.get('newDeviceProfileTitle').setValidators(null); + this.profileConfigFormGroup.get('newDeviceProfileTitle').disable(); + this.profileConfigFormGroup.updateValueAndValidity(); + this.createdProfile = false; + } else { + this.profileConfigFormGroup.get('deviceProfileId').setValidators(null); + this.profileConfigFormGroup.get('deviceProfileId').disable(); + this.profileConfigFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]); + this.profileConfigFormGroup.get('newDeviceProfileTitle').enable(); + this.profileConfigFormGroup.updateValueAndValidity(); + this.createdProfile = true; + } + } + )); + + this.transportConfigFormGroup = this.fb.group( + { + transportConfiguration: [createDeviceProfileTransportConfiguration(DeviceTransportType.DEFAULT), Validators.required] + } + ); + this.subscriptions.push(this.deviceWizardFormGroup.get('transportType').valueChanges.subscribe((transportType) => { + this.deviceProfileTransportTypeChanged(transportType); + })); + + this.alarmRulesFormGroup = this.fb.group({ + alarms: [null] + } + ); + + this.specificConfigFormGroup = this.fb.group({ + customerId: [null], + setCredential: [false], + credential: [{value: null, disabled: true}] + } + ); + + this.subscriptions.push(this.specificConfigFormGroup.get('setCredential').valueChanges.subscribe((value) => { + if (value) { + this.specificConfigFormGroup.get('credential').enable(); + } else { + this.specificConfigFormGroup.get('credential').disable(); + } + })); + } + + ngOnDestroy() { + super.ngOnDestroy(); + this.subscriptions.forEach(s => s.unsubscribe()); + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.dialogRef.close(null); + } + + previousStep(): void { + this.addDeviceWizardStepper.previous(); + } + + nextStep(): void { + if (this.selectedIndex < this.maxStepperIndex) { + this.addDeviceWizardStepper.next(); + } else { + this.add(); + } + } + + get selectedForm(): FormGroup { + const index = !this.createdProfile && this.selectedIndex === this.maxStepperIndex ? 4 : this.selectedIndex; + switch (index) { + case 0: + return this.deviceWizardFormGroup; + case 1: + return this.profileConfigFormGroup; + case 2: + return this.transportConfigFormGroup; + case 3: + return this.alarmRulesFormGroup; + case 4: + return this.specificConfigFormGroup; + } + } + + get maxStepperIndex(): number { + return this.addDeviceWizardStepper?._steps?.length - 1; + } + + private deviceProfileTransportTypeChanged(deviceTransportType: DeviceTransportType): void { + this.transportConfigFormGroup.patchValue( + {transportConfiguration: createDeviceProfileTransportConfiguration(deviceTransportType)}); + } + + private add(): void { + this.creatProfile().pipe( + mergeMap(profileId => this.createdDevice(profileId)), + mergeMap(device => this.saveCredential(device)) + ).subscribe( + (created) => { + this.dialogRef.close(created); + } + ); + } + + private creatProfile(): Observable { + if (this.profileConfigFormGroup.get('addProfileType').value) { + const deviceProfile: DeviceProfile = { + name: this.profileConfigFormGroup.get('newDeviceProfileTitle').value, + type: DeviceProfileType.DEFAULT, + transportType: this.deviceWizardFormGroup.get('transportType').value, + profileData: { + configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT), + transportConfiguration: this.transportConfigFormGroup.get('transportConfiguration').value, + alarms: this.alarmRulesFormGroup.get('alarms').value + } + }; + return this.deviceProfileService.saveDeviceProfile(deviceProfile).pipe( + map(profile => profile.id), + tap((profileId) => { + this.profileConfigFormGroup.patchValue({ + deviceProfileId: profileId, + addProfileType: 0 + }); + this.addDeviceWizardStepper.selectedIndex = 2; + }) + ); + } else { + return of(null); + } + } + + private createdDevice(profileId: EntityId = this.profileConfigFormGroup.get('deviceProfileId').value): Observable> { + const device = { + name: this.deviceWizardFormGroup.get('name').value, + label: this.deviceWizardFormGroup.get('label').value, + deviceProfileId: profileId, + additionalInfo: { + gateway: this.deviceWizardFormGroup.get('gateway').value, + description: this.deviceWizardFormGroup.get('description').value + }, + customerId: null + }; + if (this.specificConfigFormGroup.get('customerId').value) { + device.customerId = { + entityType: EntityType.CUSTOMER, + id: this.specificConfigFormGroup.get('customerId').value + }; + } + return this.data.entitiesTableConfig.saveEntity(device); + } + + private saveCredential(device: BaseData): Observable { + if (this.specificConfigFormGroup.get('setCredential').value) { + return this.deviceService.getDeviceCredentials(device.id.id).pipe( + mergeMap( + (deviceCredentials) => { + const deviceCredentialsValue = {...deviceCredentials, ...this.specificConfigFormGroup.value.credential}; + return this.deviceService.saveDeviceCredentials(deviceCredentialsValue); + } + ), + map(() => true)); + } + return of(true); + } + + changeStep($event: StepperSelectionEvent): void { + this.selectedIndex = $event.selectedIndex; + if (this.selectedIndex === this.maxStepperIndex) { + this.nextStepButtonLabel$.next('action.add'); + } else { + this.nextStepButtonLabel$.next('action.continue'); + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts index c4927fbe99..d59c7225c5 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts @@ -114,7 +114,8 @@ export class DeviceProfilesTableConfigResolver implements Resolve> { @@ -221,7 +223,7 @@ export class DevicesTableConfigResolver implements Resolve true, + isEnabled: () => true, onAction: ($event, entity) => this.manageCredentials($event, entity) } ); @@ -243,7 +245,7 @@ export class DevicesTableConfigResolver implements Resolve true, + isEnabled: () => true, onAction: ($event, entity) => this.manageCredentials($event, entity) } ); @@ -253,7 +255,7 @@ export class DevicesTableConfigResolver implements Resolve true, + isEnabled: () => true, onAction: ($event, entity) => this.manageCredentials($event, entity) } ); @@ -301,7 +303,13 @@ export class DevicesTableConfigResolver implements Resolve true, onAction: ($event) => this.importDevices($event) - } + }, + { + name: this.translate.instant('device.wizard.device-wizard'), + icon: 'library_add', + isEnabled: () => true, + onAction: ($event) => this.deviceWizard($event) + }, ); } if (deviceScope === 'customer') { @@ -326,6 +334,23 @@ export class DevicesTableConfigResolver implements Resolve>, + boolean>(DeviceWizardDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + entitiesTableConfig: this.config.table.entitiesTableConfig + } + }).afterClosed().subscribe( + (res) => { + if (res) { + this.config.table.updateData(); + } + } + ); + } + addDevicesToCustomer($event: Event) { if ($event) { $event.stopPropagation(); @@ -480,5 +505,4 @@ export class DevicesTableConfigResolver implements Resolve Date: Thu, 1 Oct 2020 12:23:12 +0300 Subject: [PATCH 115/463] Moved provisionDeviceKey as regular column --- .../upgrade/3.1.2/schema_update_before.sql | 3 + .../device/DeviceProvisionServiceImpl.java | 32 ++++------- .../transport/DefaultTransportApiService.java | 5 +- .../server/controller/AbstractWebTest.java | 1 + .../BaseDeviceProfileControllerTest.java | 12 ++++ .../device/provision/ProvisionRequest.java | 5 +- .../server/common/data/DeviceProfile.java | 3 + ...e.java => DeviceProfileProvisionType.java} | 8 ++- .../server/common/data/DeviceProfileType.java | 3 +- .../data/device/data/DeviceConfiguration.java | 3 +- ...esDeviceProfileProvisionConfiguration.java | 31 ++++++++++ ...esDeviceProfileProvisionConfiguration.java | 31 ++++++++++ .../profile/DeviceProfileConfiguration.java | 3 +- .../device/profile/DeviceProfileData.java | 1 + .../DeviceProfileProvisionConfiguration.java | 39 +++++++++++++ ...edDeviceProfileProvisionConfiguration.java | 31 ++++++++++ .../ProvisionDeviceProfileConfiguration.java | 57 ------------------- ...=> ProvisionDeviceProfileCredentials.java} | 5 +- common/queue/src/main/proto/queue.proto | 5 +- .../transport/adaptor/JsonConverter.java | 1 - .../server/dao/device/DeviceProfileDao.java | 2 +- .../dao/device/DeviceProfileServiceImpl.java | 7 +++ .../server/dao/model/ModelConstants.java | 2 + .../dao/model/sql/DeviceProfileEntity.java | 12 ++++ .../sql/device/DeviceProfileRepository.java | 14 ++--- .../dao/sql/device/JpaDeviceProfileDao.java | 4 +- .../resources/sql/schema-entities-hsql.sql | 3 + .../main/resources/sql/schema-entities.sql | 3 + 28 files changed, 215 insertions(+), 111 deletions(-) rename common/data/src/main/java/org/thingsboard/server/common/data/{device/profile/ProvisionRequestValidationStrategyType.java => DeviceProfileProvisionType.java} (79%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreateNewDevicesDeviceProfileProvisionConfiguration.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DisabledDeviceProfileProvisionConfiguration.java delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileConfiguration.java rename common/data/src/main/java/org/thingsboard/server/common/data/device/profile/{ProvisionRequestValidationStrategy.java => ProvisionDeviceProfileCredentials.java} (83%) diff --git a/application/src/main/data/upgrade/3.1.2/schema_update_before.sql b/application/src/main/data/upgrade/3.1.2/schema_update_before.sql index c1591e7831..8f1d2f69a2 100644 --- a/application/src/main/data/upgrade/3.1.2/schema_update_before.sql +++ b/application/src/main/data/upgrade/3.1.2/schema_update_before.sql @@ -20,13 +20,16 @@ CREATE TABLE IF NOT EXISTS device_profile ( name varchar(255), type varchar(255), transport_type varchar(255), + provision_type varchar(255), profile_data jsonb, description varchar, search_text varchar(255), is_default boolean, tenant_id uuid, default_rule_chain_id uuid, + provision_device_key varchar, CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), + CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id) ); diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 80d4c25798..091e5099ce 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -27,10 +27,8 @@ import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.DeviceProfileType; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.audit.ActionType; -import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileConfiguration; -import org.thingsboard.server.common.data.device.profile.ProvisionRequestValidationStrategyType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; @@ -117,21 +115,18 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); } - DeviceProfile targetProfile = deviceProfileDao.findProfileByProfileNameAndProfileDataProvisionConfigurationPair( - provisionRequest.getDeviceType(), - provisionRequestKey, - provisionRequestSecret); + DeviceProfile targetProfile = deviceProfileDao.findByProvisionDeviceKeyAndProvisionDeviceSecret(provisionRequestKey, provisionRequestSecret); - if (targetProfile == null || targetProfile.getProfileData().getConfiguration().getType() != DeviceProfileType.PROVISION) { + if (targetProfile == null || + !(targetProfile.getProvisionType() != DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES || + targetProfile.getProvisionType() != DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES)) { return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); } - ProvisionRequestValidationStrategyType validationStrategy = getStrategy(targetProfile); - Device targetDevice = deviceDao.findDeviceByTenantIdAndName(targetProfile.getTenantId().getId(), provisionRequest.getDeviceName()).orElse(null); - switch(validationStrategy) { - case CHECK_NEW_DEVICE: + switch(targetProfile.getProvisionType()) { + case ALLOW_CREATE_NEW_DEVICES: if (targetDevice != null) { log.warn("[{}] The device is present and could not be provisioned once more!", targetDevice.getName()); notify(targetDevice, provisionRequest, DataConstants.PROVISION_FAILURE, false); @@ -139,15 +134,15 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } else { return createDevice(provisionRequest, targetProfile); } - case CHECK_PRE_PROVISIONED_DEVICE: - if (targetDevice != null){ + case CHECK_PRE_PROVISIONED_DEVICES: + if (targetDevice != null && targetDevice.getDeviceProfileId().equals(targetProfile.getId())){ return processProvision(targetDevice, provisionRequest); } else { log.warn("[{}] Failed to find pre provisioned device!", provisionRequest.getDeviceName()); return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); } default: - throw new RuntimeException("Strategy is not supported - " + validationStrategy.name()); + throw new RuntimeException("Strategy is not supported - " + targetProfile.getProvisionType().name()); } } @@ -193,11 +188,6 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { logAction(device.getTenantId(), device.getCustomerId(), device, success, provisionRequest); } - private ProvisionRequestValidationStrategyType getStrategy(DeviceProfile profile) { - return ((ProvisionDeviceProfileConfiguration) profile.getProfileData().getConfiguration()).getStrategy(); - - } - private ListenableFuture processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName()); if (device == null) { @@ -226,7 +216,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { Device device = new Device(); device.setName(provisionRequest.getDeviceName()); - device.setType(provisionRequest.getDeviceType()); + device.setType(profile.getName()); device.setTenantId(profile.getTenantId()); return deviceService.saveDevice(device); } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index e2dcbe5416..927afc3c8a 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; -import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileConfiguration; +import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -279,9 +279,8 @@ public class DefaultTransportApiService implements TransportApiService { provisionResponseFuture = deviceProvisionService.provisionDevice( new ProvisionRequest( requestMsg.getDeviceName(), - requestMsg.getDeviceType(), requestMsg.getX509CertPubKey(), - new ProvisionDeviceProfileConfiguration( + new ProvisionDeviceProfileCredentials( requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceKey(), requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret()))); return Futures.transform(provisionResponseFuture, provisionResponse -> { diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index a7b796cace..da71c1e2df 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -68,6 +68,7 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java index b2334d7c46..3376c16573 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; @@ -153,6 +154,17 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController .andExpect(statusReason(containsString("Device profile with such name already exists"))); } + @Test + public void testSaveDeviceProfileWithSameProvisionDeviceKey() throws Exception { + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + deviceProfile.setProvisionDeviceKey("testProvisionDeviceKey"); + doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk()); + DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile 2"); + deviceProfile2.setProvisionDeviceKey("testProvisionDeviceKey"); + doPost("/api/deviceProfile", deviceProfile2).andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Device profile with such provision device key already exists"))); + } + @Ignore @Test public void testChangeDeviceProfileTypeWithExistingDevices() throws Exception { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java index 45eb7f5998..1c743fd855 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java @@ -17,13 +17,12 @@ package org.thingsboard.server.dao.device.provision; import lombok.AllArgsConstructor; import lombok.Data; -import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileConfiguration; +import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; @Data @AllArgsConstructor public class ProvisionRequest { private String deviceName; - private String deviceType; private String x509CertPubKey; - private ProvisionDeviceProfileConfiguration credentials; + private ProvisionDeviceProfileCredentials credentials; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index 097d64b198..10990bc436 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -41,10 +41,12 @@ public class DeviceProfile extends SearchTextBased implements H private boolean isDefault; private DeviceProfileType type; private DeviceTransportType transportType; + private DeviceProfileProvisionType provisionType; private RuleChainId defaultRuleChainId; private transient DeviceProfileData profileData; @JsonIgnore private byte[] profileDataBytes; + private String provisionDeviceKey; public DeviceProfile() { super(); @@ -62,6 +64,7 @@ public class DeviceProfile extends SearchTextBased implements H this.isDefault = deviceProfile.isDefault(); this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId(); this.setProfileData(deviceProfile.getProfileData()); + this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey(); } @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategyType.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java similarity index 79% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategyType.java rename to common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java index 9a4005612a..f33c705d52 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategyType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java @@ -13,8 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.device.profile; +package org.thingsboard.server.common.data; -public enum ProvisionRequestValidationStrategyType { - CHECK_NEW_DEVICE, CHECK_PRE_PROVISIONED_DEVICE +public enum DeviceProfileProvisionType { + DISABLED, + ALLOW_CREATE_NEW_DEVICES, + CHECK_PRE_PROVISIONED_DEVICES } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileType.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileType.java index 218a5aa5b4..93ca102082 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileType.java @@ -16,6 +16,5 @@ package org.thingsboard.server.common.data; public enum DeviceProfileType { - DEFAULT, - PROVISION + DEFAULT } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java index 18d233caa0..1ea2ee4f97 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java @@ -27,8 +27,7 @@ import org.thingsboard.server.common.data.DeviceProfileType; include = JsonTypeInfo.As.PROPERTY, property = "type") @JsonSubTypes({ - @JsonSubTypes.Type(value = DefaultDeviceConfiguration.class, name = "DEFAULT"), - @JsonSubTypes.Type(value = DefaultDeviceConfiguration.class, name = "PROVISION")}) + @JsonSubTypes.Type(value = DefaultDeviceConfiguration.class, name = "DEFAULT")}) public interface DeviceConfiguration { @JsonIgnore diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreateNewDevicesDeviceProfileProvisionConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreateNewDevicesDeviceProfileProvisionConfiguration.java new file mode 100644 index 0000000000..8414caa400 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreateNewDevicesDeviceProfileProvisionConfiguration.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile; + +import lombok.Data; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; + +@Data +public class AllowCreateNewDevicesDeviceProfileProvisionConfiguration implements DeviceProfileProvisionConfiguration { + + private final String provisionDeviceSecret; + + @Override + public DeviceProfileProvisionType getType() { + return DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration.java new file mode 100644 index 0000000000..40af29ea51 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile; + +import lombok.Data; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; + +@Data +public class CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration implements DeviceProfileProvisionConfiguration { + + private final String provisionDeviceSecret; + + @Override + public DeviceProfileProvisionType getType() { + return DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileConfiguration.java index aca6d391da..3bb3d29c34 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileConfiguration.java @@ -27,8 +27,7 @@ import org.thingsboard.server.common.data.DeviceProfileType; include = JsonTypeInfo.As.PROPERTY, property = "type") @JsonSubTypes({ - @JsonSubTypes.Type(value = DefaultDeviceProfileConfiguration.class, name = "DEFAULT"), - @JsonSubTypes.Type(value = ProvisionDeviceProfileConfiguration.class, name = "PROVISION")}) + @JsonSubTypes.Type(value = DefaultDeviceProfileConfiguration.class, name = "DEFAULT")}) public interface DeviceProfileConfiguration { @JsonIgnore diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java index 275e6269d6..7cb3304934 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java @@ -24,6 +24,7 @@ public class DeviceProfileData { private DeviceProfileConfiguration configuration; private DeviceProfileTransportConfiguration transportConfiguration; + private DeviceProfileProvisionConfiguration provisionConfiguration; private List alarms; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java new file mode 100644 index 0000000000..140c7334ec --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; + + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = DisabledDeviceProfileProvisionConfiguration.class, name = "DISABLED"), + @JsonSubTypes.Type(value = AllowCreateNewDevicesDeviceProfileProvisionConfiguration.class, name = "ALLOW_CREATE_NEW_DEVICES"), + @JsonSubTypes.Type(value = CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration.class, name = "CHECK_PRE_PROVISIONED_DEVICES")}) +public interface DeviceProfileProvisionConfiguration { + + @JsonIgnore + DeviceProfileProvisionType getType(); + +} \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DisabledDeviceProfileProvisionConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DisabledDeviceProfileProvisionConfiguration.java new file mode 100644 index 0000000000..01876c810d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DisabledDeviceProfileProvisionConfiguration.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile; + +import lombok.Data; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; + +@Data +public class DisabledDeviceProfileProvisionConfiguration implements DeviceProfileProvisionConfiguration { + + private final String provisionDeviceSecret; + + @Override + public DeviceProfileProvisionType getType() { + return DeviceProfileProvisionType.DISABLED; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileConfiguration.java deleted file mode 100644 index ee20a51628..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileConfiguration.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.common.data.device.profile; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; -import org.thingsboard.server.common.data.DeviceProfileType; - -import java.util.Objects; - -@Data -public class ProvisionDeviceProfileConfiguration implements DeviceProfileConfiguration { - - private String provisionDeviceKey; - private String provisionDeviceSecret; - - private ProvisionRequestValidationStrategyType strategy; - - @Override - public DeviceProfileType getType() { - return DeviceProfileType.PROVISION; - } - - @JsonCreator - public ProvisionDeviceProfileConfiguration(@JsonProperty("provisionDeviceKey") String provisionProfileKey, @JsonProperty("provisionDeviceSecret") String provisionProfileSecret) { - this.provisionDeviceKey = provisionProfileKey; - this.provisionDeviceSecret = provisionProfileSecret; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ProvisionDeviceProfileConfiguration that = (ProvisionDeviceProfileConfiguration) o; - return provisionDeviceKey.equals(that.provisionDeviceKey) && - provisionDeviceSecret.equals(that.provisionDeviceSecret); - } - - @Override - public int hashCode() { - return Objects.hash(provisionDeviceKey, provisionDeviceSecret); - } -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategy.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileCredentials.java similarity index 83% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategy.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileCredentials.java index 759940b763..659da71488 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategy.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileCredentials.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.device.profile; import lombok.Data; @Data -public class ProvisionRequestValidationStrategy { - private final ProvisionRequestValidationStrategyType validationStrategyType; +public class ProvisionDeviceProfileCredentials { + private final String provisionDeviceKey; + private final String provisionDeviceSecret; } diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 007c17f3d3..bf308d922e 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -256,9 +256,8 @@ message DeviceCredentialsProto { message ProvisionDeviceRequestMsg { string deviceName = 1; - string deviceType = 2; - string x509CertPubKey = 3; - ProvisionDeviceCredentialsMsg provisionDeviceCredentialsMsg = 4; + string x509CertPubKey = 2; + ProvisionDeviceCredentialsMsg provisionDeviceCredentialsMsg = 3; } message ProvisionDeviceCredentialsMsg { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 13dda0a10a..f710efd44a 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -543,7 +543,6 @@ public class JsonConverter { private static TransportProtos.ProvisionDeviceRequestMsg buildProvisionRequestMsg(JsonObject jo) { return TransportProtos.ProvisionDeviceRequestMsg.newBuilder() .setDeviceName(getStrValue(jo, DataConstants.DEVICE_NAME, true)) - .setDeviceType(getStrValue(jo, DataConstants.DEVICE_TYPE, true)) .setX509CertPubKey(getStrValue(jo, DataConstants.CERT_PUB_KEY, false)) .setProvisionDeviceCredentialsMsg(buildProvisionDeviceCredentialsMsg( getStrValue(jo, DataConstants.PROVISION_KEY, true), diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java index 2d65448d02..74a251e3ff 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java @@ -38,7 +38,7 @@ public interface DeviceProfileDao extends Dao { DeviceProfileInfo findDefaultDeviceProfileInfo(TenantId tenantId); - DeviceProfile findProfileByProfileNameAndProfileDataProvisionConfigurationPair(String profileName, String provisionDeviceKey, String provisionDeviceSecret); + DeviceProfile findByProvisionDeviceKeyAndProvisionDeviceSecret(String provisionDeviceKey, String provisionDeviceSecret); DeviceProfile findByName(TenantId tenantId, String profileName); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index b40ad102eb..d83408aead 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -26,6 +26,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileInfo; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntitySubtype; @@ -33,6 +34,7 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -112,6 +114,8 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_profile_name_unq_key")) { throw new DataValidationException("Device profile with such name already exists!"); + } else if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_provision_key_unq_key")) { + throw new DataValidationException("Device profile with such provision device key already exists!"); } else { throw t; } @@ -210,12 +214,15 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D deviceProfile.setName(profileName); deviceProfile.setType(DeviceProfileType.DEFAULT); deviceProfile.setTransportType(DeviceTransportType.DEFAULT); + deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); deviceProfile.setDescription("Default device profile"); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); DefaultDeviceProfileTransportConfiguration transportConfiguration = new DefaultDeviceProfileTransportConfiguration(); + DisabledDeviceProfileProvisionConfiguration provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(null); deviceProfileData.setConfiguration(configuration); deviceProfileData.setTransportConfiguration(transportConfiguration); + deviceProfileData.setProvisionConfiguration(provisionConfiguration); deviceProfile.setProfileData(deviceProfileData); return saveDeviceProfile(deviceProfile); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index b62dac2d44..1f5a4b34d7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -169,10 +169,12 @@ public class ModelConstants { public static final String DEVICE_PROFILE_NAME_PROPERTY = "name"; public static final String DEVICE_PROFILE_TYPE_PROPERTY = "type"; public static final String DEVICE_PROFILE_TRANSPORT_TYPE_PROPERTY = "transport_type"; + public static final String DEVICE_PROFILE_PROVISION_TYPE_PROPERTY = "provision_type"; public static final String DEVICE_PROFILE_PROFILE_DATA_PROPERTY = "profile_data"; public static final String DEVICE_PROFILE_DESCRIPTION_PROPERTY = "description"; public static final String DEVICE_PROFILE_IS_DEFAULT_PROPERTY = "is_default"; public static final String DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY = "default_rule_chain_id"; + public static final String DEVICE_PROFILE_PROVISION_DEVICE_KEY = "provision_device_key"; /** * Cassandra entityView constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java index 27e77d4eca..264d0725ae 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java @@ -23,6 +23,7 @@ import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileType; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -62,6 +63,10 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl @Column(name = ModelConstants.DEVICE_PROFILE_TRANSPORT_TYPE_PROPERTY) private DeviceTransportType transportType; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.DEVICE_PROFILE_PROVISION_TYPE_PROPERTY) + private DeviceProfileProvisionType provisionType; + @Column(name = ModelConstants.DEVICE_PROFILE_DESCRIPTION_PROPERTY) private String description; @@ -78,6 +83,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl @Column(name = ModelConstants.DEVICE_PROFILE_PROFILE_DATA_PROPERTY, columnDefinition = "jsonb") private JsonNode profileData; + @Column(name=ModelConstants.DEVICE_PROFILE_PROVISION_DEVICE_KEY) + private String provisionDeviceKey; + public DeviceProfileEntity() { super(); } @@ -93,12 +101,14 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl this.name = deviceProfile.getName(); this.type = deviceProfile.getType(); this.transportType = deviceProfile.getTransportType(); + this.provisionType = deviceProfile.getProvisionType(); this.description = deviceProfile.getDescription(); this.isDefault = deviceProfile.isDefault(); this.profileData = JacksonUtil.convertValue(deviceProfile.getProfileData(), ObjectNode.class); if (deviceProfile.getDefaultRuleChainId() != null) { this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId().getId(); } + this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey(); } @Override @@ -125,12 +135,14 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl deviceProfile.setName(name); deviceProfile.setType(type); deviceProfile.setTransportType(transportType); + deviceProfile.setProvisionType(provisionType); deviceProfile.setDescription(description); deviceProfile.setDefault(isDefault); deviceProfile.setProfileData(JacksonUtil.convertValue(profileData, DeviceProfileData.class)); if (defaultRuleChainId != null) { deviceProfile.setDefaultRuleChainId(new RuleChainId(defaultRuleChainId)); } + deviceProfile.setProvisionDeviceKey(provisionDeviceKey); return deviceProfile; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java index d528650c10..43b6d68efe 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java @@ -56,14 +56,10 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository'configuration'->>'provisionDeviceKey' IS NOT NULL " + - "AND d.profile_data->'configuration'->>'provisionDeviceSecret' IS NOT NULL " + - "AND d.profile_data->'configuration'->>'provisionDeviceKey' = :provisionDeviceKey " + - "AND d.profile_data->'configuration'->>'provisionDeviceSecret' = :provisionDeviceSecret", + @Query(value = "SELECT d.* from device_profile d " + + "WHERE d.provision_device_key = :provisionDeviceKey " + + "AND d.profile_data->'provisionConfiguration'->>'provisionDeviceSecret' = :provisionDeviceSecret", nativeQuery = true) - DeviceProfileEntity findProfileByProfileNameAndProfileDataProvisionConfigurationPair(@Param("profileName") String profileName, - @Param("provisionDeviceKey") String provisionDeviceKey, - @Param("provisionDeviceSecret") String provisionDeviceSecret); + DeviceProfileEntity findByProvisionDeviceKeyAndProvisionDeviceSecret(@Param("provisionDeviceKey") String provisionDeviceKey, + @Param("provisionDeviceSecret") String provisionDeviceSecret); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java index 3a812e78da..2d7c011a87 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java @@ -81,8 +81,8 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao Date: Thu, 1 Oct 2020 17:22:17 +0300 Subject: [PATCH 116/463] Added support for multiple domains in OAuth2 config --- .../main/data/upgrade/3.1.1/schema_update.sql | 18 +- ...tomOAuth2AuthorizationRequestResolver.java | 257 ++++++++++ .../ThingsboardSecurityConfiguration.java | 6 + .../server/controller/BaseController.java | 2 +- .../server/controller/OAuth2Controller.java | 61 +-- .../Oauth2AuthenticationSuccessHandler.java | 4 +- .../service/security/permission/Resource.java | 2 +- .../permission/SysAdminPermissions.java | 2 +- .../thingsboard/server/utils/WebUtils.java | 70 +++ .../server/dao/oauth2/OAuth2Service.java | 15 +- .../server/common/data/EntityType.java | 2 +- .../common/data/id/EntityIdFactory.java | 4 +- .../id/OAuth2ClientRegistrationInfoId.java | 39 ++ .../data/oauth2/ClientRegistrationDto.java | 6 +- ...lientRegistration.java => DomainInfo.java} | 9 +- .../ExtendedOAuth2ClientRegistrationInfo.java | 39 ++ .../data/oauth2/OAuth2ClientRegistration.java | 56 +- .../oauth2/OAuth2ClientRegistrationInfo.java | 76 +++ .../oauth2/OAuth2ClientsDomainParams.java | 6 +- .../data/oauth2/OAuth2ClientsParams.java | 8 +- .../server/common/data/oauth2/SchemeType.java | 20 + .../server/dao/model/ModelConstants.java | 6 +- ...actOAuth2ClientRegistrationInfoEntity.java | 231 +++++++++ ...dedOAuth2ClientRegistrationInfoEntity.java | 48 ++ .../sql/OAuth2ClientRegistrationEntity.java | 162 +----- .../OAuth2ClientRegistrationInfoEntity.java | 51 ++ .../HybridClientRegistrationRepository.java | 12 +- .../oauth2/OAuth2ClientRegistrationDao.java | 9 +- .../OAuth2ClientRegistrationInfoDao.java | 34 ++ .../server/dao/oauth2/OAuth2ServiceImpl.java | 89 ++-- .../server/dao/oauth2/OAuth2Utils.java | 131 +++-- .../JpaOAuth2ClientRegistrationDao.java | 25 +- .../JpaOAuth2ClientRegistrationInfoDao.java | 76 +++ ...Auth2ClientRegistrationInfoRepository.java | 41 ++ .../OAuth2ClientRegistrationRepository.java | 4 - .../resources/sql/schema-entities-hsql.sql | 14 +- .../main/resources/sql/schema-entities.sql | 14 +- .../dao/service/BaseOAuth2ServiceTest.java | 484 +++++++++++++----- 38 files changed, 1562 insertions(+), 571 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java create mode 100644 application/src/main/java/org/thingsboard/server/utils/WebUtils.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationInfoId.java rename common/data/src/main/java/org/thingsboard/server/common/data/oauth2/{ExtendedOAuth2ClientRegistration.java => DomainInfo.java} (81%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistrationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/SchemeType.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractOAuth2ClientRegistrationInfoEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/ExtendedOAuth2ClientRegistrationInfoEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationInfoEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationInfoDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationInfoDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationInfoRepository.java diff --git a/application/src/main/data/upgrade/3.1.1/schema_update.sql b/application/src/main/data/upgrade/3.1.1/schema_update.sql index aa6b59617b..0b99e797b0 100644 --- a/application/src/main/data/upgrade/3.1.1/schema_update.sql +++ b/application/src/main/data/upgrade/3.1.1/schema_update.sql @@ -14,19 +14,17 @@ -- limitations under the License. -- -DROP TABLE IF EXISTS oauth2_client_registration; +DROP TABLE IF EXISTS oauth2_client_registration_info; -CREATE TABLE IF NOT EXISTS oauth2_client_registration ( - id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, +CREATE TABLE IF NOT EXISTS oauth2_client_registration_info ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_info_pkey PRIMARY KEY, enabled boolean, created_time bigint NOT NULL, additional_info varchar, - domain_name varchar(255), client_id varchar(255), client_secret varchar(255), authorization_uri varchar(255), token_uri varchar(255), - redirect_uri_template varchar(255), scope varchar(255), user_info_uri varchar(255), user_name_attribute_name varchar(255), @@ -51,6 +49,16 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( custom_send_token boolean ); +DROP TABLE IF EXISTS oauth2_client_registration; + +CREATE TABLE IF NOT EXISTS oauth2_client_registration ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + created_time bigint NOT NULL, + domain_name varchar(255), + domain_scheme varchar(31), + client_registration_info_id uuid +); + DROP TABLE IF EXISTS oauth2_client_registration_template; CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( diff --git a/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java b/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java new file mode 100644 index 0000000000..e8c59efde8 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java @@ -0,0 +1,257 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; +import org.springframework.security.crypto.keygen.StringKeyGenerator; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; +import org.springframework.security.oauth2.core.oidc.OidcScopes; +import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; +import org.springframework.security.web.util.UrlUtils; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; +import org.thingsboard.server.dao.oauth2.OAuth2Configuration; +import org.thingsboard.server.utils.WebUtils; + +import javax.servlet.http.HttpServletRequest; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +@Service +@Slf4j +public class CustomOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver { + public static final String DEFAULT_AUTHORIZATION_REQUEST_BASE_URI = "/oauth2/authorization"; + public static final String DEFAULT_LOGIN_PROCESSING_URI = "/login/oauth2/code/"; + private static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId"; + private static final char PATH_DELIMITER = '/'; + + private final AntPathRequestMatcher authorizationRequestMatcher = new AntPathRequestMatcher( + DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/{" + REGISTRATION_ID_URI_VARIABLE_NAME + "}"); + private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder()); + private final StringKeyGenerator secureKeyGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96); + + @Autowired + private ClientRegistrationRepository clientRegistrationRepository; + + @Autowired(required = false) + private OAuth2Configuration oauth2Configuration; + + + @Override + public OAuth2AuthorizationRequest resolve(HttpServletRequest request) { + String registrationId = this.resolveRegistrationId(request); + String redirectUriAction = getAction(request, "login"); + return resolve(request, registrationId, redirectUriAction); + } + + @Override + public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId) { + if (registrationId == null) { + return null; + } + String redirectUriAction = getAction(request, "authorize"); + return resolve(request, registrationId, redirectUriAction); + } + + private String getAction(HttpServletRequest request, String defaultAction) { + String action = request.getParameter("action"); + if (action == null) { + return defaultAction; + } + return action; + } + + private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction) { + if (registrationId == null) { + return null; + } + + ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId); + if (clientRegistration == null) { + throw new IllegalArgumentException("Invalid Client Registration with Id: " + registrationId); + } + + Map attributes = new HashMap<>(); + attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()); + + OAuth2AuthorizationRequest.Builder builder; + if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) { + builder = OAuth2AuthorizationRequest.authorizationCode(); + Map additionalParameters = new HashMap<>(); + if (!CollectionUtils.isEmpty(clientRegistration.getScopes()) && + clientRegistration.getScopes().contains(OidcScopes.OPENID)) { + // Section 3.1.2.1 Authentication Request - https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest + // scope + // REQUIRED. OpenID Connect requests MUST contain the "openid" scope value. + addNonceParameters(attributes, additionalParameters); + } + if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) { + addPkceParameters(attributes, additionalParameters); + } + builder.additionalParameters(additionalParameters); + } else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) { + builder = OAuth2AuthorizationRequest.implicit(); + } else { + throw new IllegalArgumentException("Invalid Authorization Grant Type (" + + clientRegistration.getAuthorizationGrantType().getValue() + + ") for Client Registration with Id: " + clientRegistration.getRegistrationId()); + } + + String redirectUriStr = expandRedirectUri(request, clientRegistration, redirectUriAction); + + return builder + .clientId(clientRegistration.getClientId()) + .authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri()) + .redirectUri(redirectUriStr) + .scopes(clientRegistration.getScopes()) + .state(this.stateGenerator.generateKey()) + .attributes(attributes) + .build(); + } + + private String resolveRegistrationId(HttpServletRequest request) { + if (this.authorizationRequestMatcher.matches(request)) { + return this.authorizationRequestMatcher + .matcher(request).getVariables().get(REGISTRATION_ID_URI_VARIABLE_NAME); + } + return null; + } + + /** + * Expands the {@link ClientRegistration#getRedirectUriTemplate()} with following provided variables:
+ * - baseUrl (e.g. https://localhost/app)
+ * - baseScheme (e.g. https)
+ * - baseHost (e.g. localhost)
+ * - basePort (e.g. :8080)
+ * - basePath (e.g. /app)
+ * - registrationId (e.g. google)
+ * - action (e.g. login)
+ *

+ * Null variables are provided as empty strings. + *

+ * Default redirectUriTemplate is: {@link org.springframework.security.config.oauth2.client}.CommonOAuth2Provider#DEFAULT_REDIRECT_URL + * + * @return expanded URI + */ + private String expandRedirectUri(HttpServletRequest request, ClientRegistration clientRegistration, String action) { + Map uriVariables = new HashMap<>(); + uriVariables.put("registrationId", clientRegistration.getRegistrationId()); + + UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request)) + .replacePath(request.getContextPath()) + .replaceQuery(null) + .fragment(null) + .build(); + String scheme = uriComponents.getScheme(); + uriVariables.put("baseScheme", scheme == null ? "" : scheme); + String host = uriComponents.getHost(); + uriVariables.put("baseHost", host == null ? "" : host); + // following logic is based on HierarchicalUriComponents#toUriString() + int port = uriComponents.getPort(); + uriVariables.put("basePort", port == -1 ? "" : ":" + port); + String path = uriComponents.getPath(); + if (StringUtils.hasLength(path)) { + if (path.charAt(0) != PATH_DELIMITER) { + path = PATH_DELIMITER + path; + } + } + uriVariables.put("basePath", path == null ? "" : path); + uriVariables.put("baseUrl", uriComponents.toUriString()); + + uriVariables.put("action", action == null ? "" : action); + + return UriComponentsBuilder.fromUriString(getRedirectUri(request)) + .buildAndExpand(uriVariables) + .toUriString(); + } + + private String getRedirectUri(HttpServletRequest request) { + String loginProcessingUri = oauth2Configuration != null ? oauth2Configuration.getLoginProcessingUrl() : DEFAULT_LOGIN_PROCESSING_URI; + + String scheme = WebUtils.getScheme(request); + String host = WebUtils.getHost(request); + String port = WebUtils.getPort(request); + log.trace("Scheme - {}, host - {}, port - {}.", scheme, host, port); + String requestHost = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort(); + return requestHost + loginProcessingUri; + } + + /** + * Creates nonce and its hash for use in OpenID Connect 1.0 Authentication Requests. + * + * @param attributes where the {@link OidcParameterNames#NONCE} is stored for the authentication request + * @param additionalParameters where the {@link OidcParameterNames#NONCE} hash is added for the authentication request + * + * @since 5.2 + * @see 3.1.2.1. Authentication Request + */ + private void addNonceParameters(Map attributes, Map additionalParameters) { + try { + String nonce = this.secureKeyGenerator.generateKey(); + String nonceHash = createHash(nonce); + attributes.put(OidcParameterNames.NONCE, nonce); + additionalParameters.put(OidcParameterNames.NONCE, nonceHash); + } catch (NoSuchAlgorithmException e) { } + } + + /** + * Creates and adds additional PKCE parameters for use in the OAuth 2.0 Authorization and Access Token Requests + * + * @param attributes where {@link PkceParameterNames#CODE_VERIFIER} is stored for the token request + * @param additionalParameters where {@link PkceParameterNames#CODE_CHALLENGE} and, usually, + * {@link PkceParameterNames#CODE_CHALLENGE_METHOD} are added to be used in the authorization request. + * + * @since 5.2 + * @see 1.1. Protocol Flow + * @see 4.1. Client Creates a Code Verifier + * @see 4.2. Client Creates the Code Challenge + */ + private void addPkceParameters(Map attributes, Map additionalParameters) { + String codeVerifier = this.secureKeyGenerator.generateKey(); + attributes.put(PkceParameterNames.CODE_VERIFIER, codeVerifier); + try { + String codeChallenge = createHash(codeVerifier); + additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeChallenge); + additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); + } catch (NoSuchAlgorithmException e) { + additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeVerifier); + } + } + + private static String createHash(String value) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] digest = md.digest(value.getBytes(StandardCharsets.US_ASCII)); + return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); + } +} diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java index fb71bcb994..4894ecd6ae 100644 --- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java @@ -32,6 +32,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @@ -175,6 +176,9 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt web.ignoring().antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**"); } + @Autowired + private OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver; + @Override protected void configure(HttpSecurity http) throws Exception { http.headers().cacheControl().and().frameOptions().disable() @@ -209,6 +213,8 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt .addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class); if (oauth2Configuration != null) { http.oauth2Login() + .authorizationEndpoint().authorizationRequestResolver(oAuth2AuthorizationRequestResolver) + .and() .loginPage("/oauth2Login") .loginProcessingUrl(oauth2Configuration.getLoginProcessingUrl()) .successHandler(oauth2AuthenticationSuccessHandler) diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 577e6c9e83..8363dd7205 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -373,7 +373,7 @@ public abstract class BaseController { case WIDGET_TYPE: checkWidgetTypeId(new WidgetTypeId(entityId.getId()), operation); return; - case OAUTH2_CLIENT_REGISTRATION: + case OAUTH2_CLIENT_REGISTRATION_INFO: case OAUTH2_CLIENT_REGISTRATION_TEMPLATE: return; default: diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index 6be426c245..d94a706440 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -19,13 +19,10 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientsDomainParams; import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; +import org.thingsboard.server.common.data.oauth2.SchemeType; import org.thingsboard.server.queue.util.TbCoreComponent; import javax.servlet.http.HttpServletRequest; @@ -36,14 +33,11 @@ import java.util.List; @RequestMapping("/api") @Slf4j public class OAuth2Controller extends BaseController { - private static final String CLIENT_REGISTRATION_ID = "clientRegistrationId"; - private static final String DOMAIN = "domain"; - @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) @ResponseBody public List getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { try { - return oAuth2Service.getOAuth2Clients(request.getServerName()); + return oAuth2Service.getOAuth2Clients(request.getScheme(), request.getServerName()); } catch (Exception e) { throw handleException(e); } @@ -65,56 +59,9 @@ public class OAuth2Controller extends BaseController { @ResponseStatus(value = HttpStatus.OK) public OAuth2ClientsParams saveOAuth2Params(@RequestBody OAuth2ClientsParams oauth2Params) throws ThingsboardException { try { - return oAuth2Service.saveOAuth2Params(oauth2Params); - } catch (Exception e) { - throw handleException(e); - } - } - - @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") - @RequestMapping(value = "/oauth2/config/{clientRegistrationId}", method = RequestMethod.DELETE) - @ResponseStatus(value = HttpStatus.OK) - public void deleteClientRegistration(@PathVariable(CLIENT_REGISTRATION_ID) String strClientRegistrationId) throws ThingsboardException { - checkParameter(CLIENT_REGISTRATION_ID, strClientRegistrationId); - try { - OAuth2ClientRegistrationId clientRegistrationId = new OAuth2ClientRegistrationId(toUUID(strClientRegistrationId)); - oAuth2Service.deleteClientRegistrationById(clientRegistrationId); - - logEntityAction(clientRegistrationId, - null, - null, - ActionType.DELETED, null, strClientRegistrationId); - - } catch (Exception e) { - - logEntityAction(emptyId(EntityType.OAUTH2_CLIENT_REGISTRATION), - null, - null, - ActionType.DELETED, e, strClientRegistrationId); - - throw handleException(e); - } - } - - - @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") - @RequestMapping(value = "/oauth2/config/domain/{domain}", method = RequestMethod.DELETE) - @ResponseStatus(value = HttpStatus.OK) - public void deleteClientRegistrationForDomain(@PathVariable(DOMAIN) String domain) throws ThingsboardException { - checkParameter(DOMAIN, domain); - try { - oAuth2Service.deleteClientRegistrationsByDomain(domain); - - logEntityAction(emptyId(EntityType.OAUTH2_CLIENT_REGISTRATION), null, - null, - ActionType.DELETED, null, domain); - + oAuth2Service.saveOAuth2Params(oauth2Params); + return oAuth2Service.findOAuth2Params(); } catch (Exception e) { - logEntityAction(emptyId(EntityType.OAUTH2_CLIENT_REGISTRATION), - null, - null, - ActionType.DELETED, e, domain); - throw handleException(e); } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index ddd5de087d..5bb02b9a63 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -22,7 +22,7 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; import org.thingsboard.server.service.security.model.SecurityUser; @@ -68,7 +68,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS try { OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; - OAuth2ClientRegistration clientRegistration = oAuth2Service.findClientRegistration(UUID.fromString(token.getAuthorizedClientRegistrationId())); + OAuth2ClientRegistrationInfo clientRegistration = oAuth2Service.findClientRegistrationInfo(UUID.fromString(token.getAuthorizedClientRegistrationId())); OAuth2AuthorizedClient oAuth2AuthorizedClient = oAuth2AuthorizedClientService.loadAuthorizedClient( token.getAuthorizedClientRegistrationId(), token.getPrincipal().getName()); diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java index 7bae743c22..3b5a115a61 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java @@ -32,7 +32,7 @@ public enum Resource { USER(EntityType.USER), WIDGETS_BUNDLE(EntityType.WIDGETS_BUNDLE), WIDGET_TYPE(EntityType.WIDGET_TYPE), - OAUTH2_CONFIGURATION(EntityType.OAUTH2_CLIENT_REGISTRATION), + OAUTH2_CONFIGURATION_INFO(EntityType.OAUTH2_CLIENT_REGISTRATION_INFO), OAUTH2_CONFIGURATION_TEMPLATE(EntityType.OAUTH2_CLIENT_REGISTRATION_TEMPLATE), ; diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java index 871de30e6e..49febbcd84 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java @@ -35,7 +35,7 @@ public class SysAdminPermissions extends AbstractPermissions { put(Resource.USER, userPermissionChecker); put(Resource.WIDGETS_BUNDLE, systemEntityPermissionChecker); put(Resource.WIDGET_TYPE, systemEntityPermissionChecker); - put(Resource.OAUTH2_CONFIGURATION, PermissionChecker.allowAllPermissionChecker); + put(Resource.OAUTH2_CONFIGURATION_INFO, PermissionChecker.allowAllPermissionChecker); put(Resource.OAUTH2_CONFIGURATION_TEMPLATE, PermissionChecker.allowAllPermissionChecker); } diff --git a/application/src/main/java/org/thingsboard/server/utils/WebUtils.java b/application/src/main/java/org/thingsboard/server/utils/WebUtils.java new file mode 100644 index 0000000000..31d61634af --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/utils/WebUtils.java @@ -0,0 +1,70 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.utils; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; + +import javax.servlet.http.HttpServletRequest; + +@Slf4j +public class WebUtils { + private static final String X_FORWARDED_HOST_HEADER_KEY = "x-forwarded-host"; + private static final String X_FORWARDED_PORT_HEADER_KEY = "x-forwarded-port"; + private static final String X_FORWARDED_PROTO_HEADER_KEY = "x-forwarded-proto"; + + public static String getHost(HttpServletRequest request) { + String forwardedHost = request.getHeader(X_FORWARDED_HOST_HEADER_KEY); + log.trace("Forwarded host - {}.", forwardedHost); + if (!StringUtils.isEmpty(forwardedHost)) { + if (forwardedHost.contains(":")) { + return forwardedHost.substring(0, forwardedHost.indexOf(":")); + } else { + return forwardedHost; + } + } else { + return request.getServerName(); + } + } + + public static String getScheme(HttpServletRequest request) { + String forwardedProto = request.getHeader(X_FORWARDED_PROTO_HEADER_KEY); + log.trace("Forwarded proto - {}.", forwardedProto); + if (!StringUtils.isEmpty(forwardedProto)) { + return forwardedProto; + } else { + return request.getServerName(); + } + } + + public static String getPort(HttpServletRequest request) { + String forwardedPort = request.getHeader(X_FORWARDED_PORT_HEADER_KEY); + log.trace("Forwarded port - {}.", forwardedPort); + if (!StringUtils.isEmpty(forwardedPort)) { + return forwardedPort; + } + String forwardedHost = request.getHeader(X_FORWARDED_HOST_HEADER_KEY); + if (!StringUtils.isEmpty(forwardedHost)) { + if (forwardedHost.contains(":")) { + return forwardedHost.substring(forwardedHost.indexOf(":")); + } else { + return "HTTP".equals(getScheme(request).toUpperCase()) ? + "80" : "443"; + } + } + return Integer.toString(request.getServerPort()); + } +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java index 76920561d6..073bab823b 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java @@ -15,26 +15,21 @@ */ package org.thingsboard.server.dao.oauth2; -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; import java.util.List; import java.util.UUID; public interface OAuth2Service { - List getOAuth2Clients(String domainName); + List getOAuth2Clients(String domainScheme, String domainName); - OAuth2ClientsParams saveOAuth2Params(OAuth2ClientsParams oauth2Params); + void saveOAuth2Params(OAuth2ClientsParams oauth2Params); OAuth2ClientsParams findOAuth2Params(); - OAuth2ClientRegistration findClientRegistration(UUID id); + OAuth2ClientRegistrationInfo findClientRegistrationInfo(UUID id); - List findAllClientRegistrations(); - - void deleteClientRegistrationById(OAuth2ClientRegistrationId id); - - void deleteClientRegistrationsByDomain(String domain); + List findAllClientRegistrationInfos(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index 7791597141..bb786ce196 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -19,5 +19,5 @@ package org.thingsboard.server.common.data; * @author Andrew Shvayka */ public enum EntityType { - TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, OAUTH2_CLIENT_REGISTRATION, OAUTH2_CLIENT_REGISTRATION_TEMPLATE + TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, OAUTH2_CLIENT_REGISTRATION, OAUTH2_CLIENT_REGISTRATION_INFO, OAUTH2_CLIENT_REGISTRATION_TEMPLATE } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java index 41d9a929f2..7e5bb22019 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java @@ -62,8 +62,8 @@ public class EntityIdFactory { return new WidgetsBundleId(uuid); case WIDGET_TYPE: return new WidgetTypeId(uuid); - case OAUTH2_CLIENT_REGISTRATION: - return new OAuth2ClientRegistrationId(uuid); + case OAUTH2_CLIENT_REGISTRATION_INFO: + return new OAuth2ClientRegistrationInfoId(uuid); case OAUTH2_CLIENT_REGISTRATION_TEMPLATE: return new OAuth2ClientRegistrationTemplateId(uuid); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationInfoId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationInfoId.java new file mode 100644 index 0000000000..999f0d5816 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationInfoId.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; + +import java.util.UUID; + +public class OAuth2ClientRegistrationInfoId extends UUIDBased implements EntityId { + + @JsonCreator + public OAuth2ClientRegistrationInfoId(@JsonProperty("id") UUID id) { + super(id); + } + + public static OAuth2ClientRegistrationInfoId fromString(String clientRegistrationInfoId) { + return new OAuth2ClientRegistrationInfoId(UUID.fromString(clientRegistrationInfoId)); + } + + @Override + public EntityType getEntityType() { + return EntityType.OAUTH2_CLIENT_REGISTRATION_INFO; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java index eef79bda6c..b3b1911523 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java @@ -15,11 +15,9 @@ */ package org.thingsboard.server.common.data.oauth2; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; import lombok.*; -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; -import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId; import java.util.List; @@ -30,8 +28,6 @@ import java.util.List; @AllArgsConstructor @Builder public class ClientRegistrationDto { - private OAuth2ClientRegistrationId id; - private long createdTime; private OAuth2MapperConfig mapperConfig; private String clientId; private String clientSecret; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/DomainInfo.java similarity index 81% rename from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistration.java rename to common/data/src/main/java/org/thingsboard/server/common/data/oauth2/DomainInfo.java index 18d5337132..7eb5b34661 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/DomainInfo.java @@ -17,12 +17,13 @@ package org.thingsboard.server.common.data.oauth2; import lombok.*; +@EqualsAndHashCode @Data @ToString -@Builder(toBuilder = true) @NoArgsConstructor @AllArgsConstructor -public class ExtendedOAuth2ClientRegistration { - private String redirectUriTemplate; - private OAuth2ClientRegistration clientRegistration; +@Builder +public class DomainInfo { + private SchemeType scheme; + private String name; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistrationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistrationInfo.java new file mode 100644 index 0000000000..9998b2bd35 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistrationInfo.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.oauth2; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class ExtendedOAuth2ClientRegistrationInfo extends OAuth2ClientRegistrationInfo { + + private String domainName; + private SchemeType domainScheme; + + public ExtendedOAuth2ClientRegistrationInfo() { + super(); + } + + public ExtendedOAuth2ClientRegistrationInfo(OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo, + SchemeType domainScheme, + String domainName) { + super(oAuth2ClientRegistrationInfo); + this.domainScheme = domainScheme; + this.domainName = domainName; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java index 1932a77bef..0ced05cfdb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java @@ -15,68 +15,28 @@ */ package org.thingsboard.server.common.data.oauth2; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; -import org.thingsboard.server.common.data.HasName; -import org.thingsboard.server.common.data.HasTenantId; -import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; +import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; -import org.thingsboard.server.common.data.id.TenantId; - -import java.util.List; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId; @EqualsAndHashCode(callSuper = true) @Data -@ToString(exclude = {"clientSecret"}) +@ToString @NoArgsConstructor -public class OAuth2ClientRegistration extends SearchTextBasedWithAdditionalInfo implements HasName { +public class OAuth2ClientRegistration extends BaseData { - private boolean enabled; + private OAuth2ClientRegistrationInfoId clientRegistrationId; private String domainName; - private String redirectUriTemplate; - private OAuth2MapperConfig mapperConfig; - private String clientId; - private String clientSecret; - private String authorizationUri; - private String accessTokenUri; - private List scope; - private String userInfoUri; - private String userNameAttributeName; - private String jwkSetUri; - private String clientAuthenticationMethod; - private String loginButtonLabel; - private String loginButtonIcon; + private SchemeType domainScheme; public OAuth2ClientRegistration(OAuth2ClientRegistration clientRegistration) { super(clientRegistration); - this.enabled = clientRegistration.enabled; + this.clientRegistrationId = clientRegistration.clientRegistrationId; this.domainName = clientRegistration.domainName; - this.redirectUriTemplate = clientRegistration.redirectUriTemplate; - this.mapperConfig = clientRegistration.mapperConfig; - this.clientId = clientRegistration.clientId; - this.clientSecret = clientRegistration.clientSecret; - this.authorizationUri = clientRegistration.authorizationUri; - this.accessTokenUri = clientRegistration.accessTokenUri; - this.scope = clientRegistration.scope; - this.userInfoUri = clientRegistration.userInfoUri; - this.userNameAttributeName = clientRegistration.userNameAttributeName; - this.jwkSetUri = clientRegistration.jwkSetUri; - this.clientAuthenticationMethod = clientRegistration.clientAuthenticationMethod; - this.loginButtonLabel = clientRegistration.loginButtonLabel; - this.loginButtonIcon = clientRegistration.loginButtonIcon; - } - - @Override - @JsonProperty(access = JsonProperty.Access.READ_ONLY) - public String getName() { - return loginButtonLabel; - } - - @Override - public String getSearchText() { - return getName(); + this.domainScheme = clientRegistration.domainScheme; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationInfo.java new file mode 100644 index 0000000000..177d677abf --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationInfo.java @@ -0,0 +1,76 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.oauth2; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId; + +import java.util.List; + +@EqualsAndHashCode(callSuper = true) +@Data +@ToString(exclude = {"clientSecret"}) +@NoArgsConstructor +public class OAuth2ClientRegistrationInfo extends SearchTextBasedWithAdditionalInfo implements HasName { + + private boolean enabled; + private OAuth2MapperConfig mapperConfig; + private String clientId; + private String clientSecret; + private String authorizationUri; + private String accessTokenUri; + private List scope; + private String userInfoUri; + private String userNameAttributeName; + private String jwkSetUri; + private String clientAuthenticationMethod; + private String loginButtonLabel; + private String loginButtonIcon; + + public OAuth2ClientRegistrationInfo(OAuth2ClientRegistrationInfo clientRegistration) { + super(clientRegistration); + this.enabled = clientRegistration.enabled; + this.mapperConfig = clientRegistration.mapperConfig; + this.clientId = clientRegistration.clientId; + this.clientSecret = clientRegistration.clientSecret; + this.authorizationUri = clientRegistration.authorizationUri; + this.accessTokenUri = clientRegistration.accessTokenUri; + this.scope = clientRegistration.scope; + this.userInfoUri = clientRegistration.userInfoUri; + this.userNameAttributeName = clientRegistration.userNameAttributeName; + this.jwkSetUri = clientRegistration.jwkSetUri; + this.clientAuthenticationMethod = clientRegistration.clientAuthenticationMethod; + this.loginButtonLabel = clientRegistration.loginButtonLabel; + this.loginButtonIcon = clientRegistration.loginButtonIcon; + } + + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getName() { + return loginButtonLabel; + } + + @Override + public String getSearchText() { + return getName(); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java index 33670c7b2b..d93401f5cc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.oauth2; import lombok.*; import java.util.List; +import java.util.Set; @EqualsAndHashCode @Data @@ -26,7 +27,6 @@ import java.util.List; @NoArgsConstructor @AllArgsConstructor public class OAuth2ClientsDomainParams { - private String domainName; - private String redirectUriTemplate; - private List clientRegistrations; + private Set domainInfos; + private Set clientRegistrations; } \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java index 7eb29f9068..3cb3b863fd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java @@ -16,11 +16,7 @@ package org.thingsboard.server.common.data.oauth2; import lombok.*; -import org.thingsboard.server.common.data.id.TenantId; - -import java.util.Collection; -import java.util.List; -import java.util.Objects; +import java.util.Set; @EqualsAndHashCode @Data @@ -30,5 +26,5 @@ import java.util.Objects; @AllArgsConstructor public class OAuth2ClientsParams { private boolean enabled; - private List oAuth2DomainDtos; + private Set oAuth2DomainDtos; } \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/SchemeType.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/SchemeType.java new file mode 100644 index 0000000000..80d0362b7d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/SchemeType.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.oauth2; + +public enum SchemeType { + HTTP, HTTPS, MIXED; +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index f6d599cefc..b3f0f5b954 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -358,11 +358,15 @@ public class ModelConstants { * OAuth2 client registration constants. */ public static final String OAUTH2_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; - public static final String OAUTH2_ENABLED_PROPERTY = "enabled"; + public static final String OAUTH2_CLIENT_REGISTRATION_INFO_COLUMN_FAMILY_NAME = "oauth2_client_registration_info"; public static final String OAUTH2_CLIENT_REGISTRATION_COLUMN_FAMILY_NAME = "oauth2_client_registration"; + public static final String OAUTH2_CLIENT_REGISTRATION_TO_DOMAIN_COLUMN_FAMILY_NAME = "oauth2_client_registration_to_domain"; public static final String OAUTH2_CLIENT_REGISTRATION_TEMPLATE_COLUMN_FAMILY_NAME = "oauth2_client_registration_template"; + public static final String OAUTH2_ENABLED_PROPERTY = "enabled"; public static final String OAUTH2_TEMPLATE_PROVIDER_ID_PROPERTY = "provider_id"; + public static final String OAUTH2_CLIENT_REGISTRATION_INFO_ID_PROPERTY = "client_registration_info_id"; public static final String OAUTH2_DOMAIN_NAME_PROPERTY = "domain_name"; + public static final String OAUTH2_DOMAIN_SCHEME_PROPERTY = "domain_scheme"; public static final String OAUTH2_CLIENT_ID_PROPERTY = "client_id"; public static final String OAUTH2_CLIENT_SECRET_PROPERTY = "client_secret"; public static final String OAUTH2_AUTHORIZATION_URI_PROPERTY = "authorization_uri"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractOAuth2ClientRegistrationInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractOAuth2ClientRegistrationInfoEntity.java new file mode 100644 index 0000000000..efa9a563ec --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractOAuth2ClientRegistrationInfoEntity.java @@ -0,0 +1,231 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId; +import org.thingsboard.server.common.data.oauth2.*; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.util.mapping.JsonStringType; + +import javax.persistence.*; +import java.util.Arrays; + +@Data +@EqualsAndHashCode(callSuper = true) +@TypeDef(name = "json", typeClass = JsonStringType.class) +@MappedSuperclass +public abstract class AbstractOAuth2ClientRegistrationInfoEntity extends BaseSqlEntity { + + @Column(name = ModelConstants.OAUTH2_ENABLED_PROPERTY) + private Boolean enabled; + @Column(name = ModelConstants.OAUTH2_CLIENT_ID_PROPERTY) + private String clientId; + @Column(name = ModelConstants.OAUTH2_CLIENT_SECRET_PROPERTY) + private String clientSecret; + @Column(name = ModelConstants.OAUTH2_AUTHORIZATION_URI_PROPERTY) + private String authorizationUri; + @Column(name = ModelConstants.OAUTH2_TOKEN_URI_PROPERTY) + private String tokenUri; + @Column(name = ModelConstants.OAUTH2_SCOPE_PROPERTY) + private String scope; + @Column(name = ModelConstants.OAUTH2_USER_INFO_URI_PROPERTY) + private String userInfoUri; + @Column(name = ModelConstants.OAUTH2_USER_NAME_ATTRIBUTE_NAME_PROPERTY) + private String userNameAttributeName; + @Column(name = ModelConstants.OAUTH2_JWK_SET_URI_PROPERTY) + private String jwkSetUri; + @Column(name = ModelConstants.OAUTH2_CLIENT_AUTHENTICATION_METHOD_PROPERTY) + private String clientAuthenticationMethod; + @Column(name = ModelConstants.OAUTH2_LOGIN_BUTTON_LABEL_PROPERTY) + private String loginButtonLabel; + @Column(name = ModelConstants.OAUTH2_LOGIN_BUTTON_ICON_PROPERTY) + private String loginButtonIcon; + @Column(name = ModelConstants.OAUTH2_ALLOW_USER_CREATION_PROPERTY) + private Boolean allowUserCreation; + @Column(name = ModelConstants.OAUTH2_ACTIVATE_USER_PROPERTY) + private Boolean activateUser; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.OAUTH2_MAPPER_TYPE_PROPERTY) + private MapperType type; + @Column(name = ModelConstants.OAUTH2_EMAIL_ATTRIBUTE_KEY_PROPERTY) + private String emailAttributeKey; + @Column(name = ModelConstants.OAUTH2_FIRST_NAME_ATTRIBUTE_KEY_PROPERTY) + private String firstNameAttributeKey; + @Column(name = ModelConstants.OAUTH2_LAST_NAME_ATTRIBUTE_KEY_PROPERTY) + private String lastNameAttributeKey; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.OAUTH2_TENANT_NAME_STRATEGY_PROPERTY) + private TenantNameStrategyType tenantNameStrategy; + @Column(name = ModelConstants.OAUTH2_TENANT_NAME_PATTERN_PROPERTY) + private String tenantNamePattern; + @Column(name = ModelConstants.OAUTH2_CUSTOMER_NAME_PATTERN_PROPERTY) + private String customerNamePattern; + @Column(name = ModelConstants.OAUTH2_DEFAULT_DASHBOARD_NAME_PROPERTY) + private String defaultDashboardName; + @Column(name = ModelConstants.OAUTH2_ALWAYS_FULL_SCREEN_PROPERTY) + private Boolean alwaysFullScreen; + @Column(name = ModelConstants.OAUTH2_MAPPER_URL_PROPERTY) + private String url; + @Column(name = ModelConstants.OAUTH2_MAPPER_USERNAME_PROPERTY) + private String username; + @Column(name = ModelConstants.OAUTH2_MAPPER_PASSWORD_PROPERTY) + private String password; + @Column(name = ModelConstants.OAUTH2_MAPPER_SEND_TOKEN_PROPERTY) + private Boolean sendToken; + + @Type(type = "json") + @Column(name = ModelConstants.OAUTH2_ADDITIONAL_INFO_PROPERTY) + private JsonNode additionalInfo; + + public AbstractOAuth2ClientRegistrationInfoEntity() { + super(); + } + + public AbstractOAuth2ClientRegistrationInfoEntity(OAuth2ClientRegistrationInfo clientRegistrationInfo) { + if (clientRegistrationInfo.getId() != null) { + this.setUuid(clientRegistrationInfo.getId().getId()); + } + this.createdTime = clientRegistrationInfo.getCreatedTime(); + this.enabled = clientRegistrationInfo.isEnabled(); + this.clientId = clientRegistrationInfo.getClientId(); + this.clientSecret = clientRegistrationInfo.getClientSecret(); + this.authorizationUri = clientRegistrationInfo.getAuthorizationUri(); + this.tokenUri = clientRegistrationInfo.getAccessTokenUri(); + this.scope = clientRegistrationInfo.getScope().stream().reduce((result, element) -> result + "," + element).orElse(""); + this.userInfoUri = clientRegistrationInfo.getUserInfoUri(); + this.userNameAttributeName = clientRegistrationInfo.getUserNameAttributeName(); + this.jwkSetUri = clientRegistrationInfo.getJwkSetUri(); + this.clientAuthenticationMethod = clientRegistrationInfo.getClientAuthenticationMethod(); + this.loginButtonLabel = clientRegistrationInfo.getLoginButtonLabel(); + this.loginButtonIcon = clientRegistrationInfo.getLoginButtonIcon(); + this.additionalInfo = clientRegistrationInfo.getAdditionalInfo(); + OAuth2MapperConfig mapperConfig = clientRegistrationInfo.getMapperConfig(); + if (mapperConfig != null) { + this.allowUserCreation = mapperConfig.isAllowUserCreation(); + this.activateUser = mapperConfig.isActivateUser(); + this.type = mapperConfig.getType(); + OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic(); + if (basicConfig != null) { + this.emailAttributeKey = basicConfig.getEmailAttributeKey(); + this.firstNameAttributeKey = basicConfig.getFirstNameAttributeKey(); + this.lastNameAttributeKey = basicConfig.getLastNameAttributeKey(); + this.tenantNameStrategy = basicConfig.getTenantNameStrategy(); + this.tenantNamePattern = basicConfig.getTenantNamePattern(); + this.customerNamePattern = basicConfig.getCustomerNamePattern(); + this.defaultDashboardName = basicConfig.getDefaultDashboardName(); + this.alwaysFullScreen = basicConfig.isAlwaysFullScreen(); + } + OAuth2CustomMapperConfig customConfig = mapperConfig.getCustom(); + if (customConfig != null) { + this.url = customConfig.getUrl(); + this.username = customConfig.getUsername(); + this.password = customConfig.getPassword(); + this.sendToken = customConfig.isSendToken(); + } + } + } + + public AbstractOAuth2ClientRegistrationInfoEntity(OAuth2ClientRegistrationInfoEntity oAuth2ClientRegistrationInfoEntity) { + this.setId(oAuth2ClientRegistrationInfoEntity.getId()); + this.setCreatedTime(oAuth2ClientRegistrationInfoEntity.getCreatedTime()); + this.enabled = oAuth2ClientRegistrationInfoEntity.getEnabled(); + this.clientId = oAuth2ClientRegistrationInfoEntity.getClientId(); + this.clientSecret = oAuth2ClientRegistrationInfoEntity.getClientSecret(); + this.authorizationUri = oAuth2ClientRegistrationInfoEntity.getAuthorizationUri(); + this.tokenUri = oAuth2ClientRegistrationInfoEntity.getTokenUri(); + this.scope = oAuth2ClientRegistrationInfoEntity.getScope(); + this.userInfoUri = oAuth2ClientRegistrationInfoEntity.getUserInfoUri(); + this.userNameAttributeName = oAuth2ClientRegistrationInfoEntity.getUserNameAttributeName(); + this.jwkSetUri = oAuth2ClientRegistrationInfoEntity.getJwkSetUri(); + this.clientAuthenticationMethod = oAuth2ClientRegistrationInfoEntity.getClientAuthenticationMethod(); + this.loginButtonLabel = oAuth2ClientRegistrationInfoEntity.getLoginButtonLabel(); + this.loginButtonIcon = oAuth2ClientRegistrationInfoEntity.getLoginButtonIcon(); + this.additionalInfo = oAuth2ClientRegistrationInfoEntity.getAdditionalInfo(); + this.allowUserCreation = oAuth2ClientRegistrationInfoEntity.getAllowUserCreation(); + this.activateUser = oAuth2ClientRegistrationInfoEntity.getActivateUser(); + this.type = oAuth2ClientRegistrationInfoEntity.getType(); + this.emailAttributeKey = oAuth2ClientRegistrationInfoEntity.getEmailAttributeKey(); + this.firstNameAttributeKey = oAuth2ClientRegistrationInfoEntity.getFirstNameAttributeKey(); + this.lastNameAttributeKey = oAuth2ClientRegistrationInfoEntity.getLastNameAttributeKey(); + this.tenantNameStrategy = oAuth2ClientRegistrationInfoEntity.getTenantNameStrategy(); + this.tenantNamePattern = oAuth2ClientRegistrationInfoEntity.getTenantNamePattern(); + this.customerNamePattern = oAuth2ClientRegistrationInfoEntity.getCustomerNamePattern(); + this.defaultDashboardName = oAuth2ClientRegistrationInfoEntity.getDefaultDashboardName(); + this.alwaysFullScreen = oAuth2ClientRegistrationInfoEntity.getAlwaysFullScreen(); + this.url = oAuth2ClientRegistrationInfoEntity.getUrl(); + this.username = oAuth2ClientRegistrationInfoEntity.getUsername(); + this.password = oAuth2ClientRegistrationInfoEntity.getPassword(); + this.sendToken = oAuth2ClientRegistrationInfoEntity.getSendToken(); + } + + + protected OAuth2ClientRegistrationInfo toOAuth2ClientRegistrationInfo() { + OAuth2ClientRegistrationInfo clientRegistrationInfo = new OAuth2ClientRegistrationInfo(); + clientRegistrationInfo.setId(new OAuth2ClientRegistrationInfoId(id)); + clientRegistrationInfo.setEnabled(enabled); + clientRegistrationInfo.setCreatedTime(createdTime); + clientRegistrationInfo.setAdditionalInfo(additionalInfo); + clientRegistrationInfo.setMapperConfig( + OAuth2MapperConfig.builder() + .allowUserCreation(allowUserCreation) + .activateUser(activateUser) + .type(type) + .basic( + type == MapperType.BASIC ? + OAuth2BasicMapperConfig.builder() + .emailAttributeKey(emailAttributeKey) + .firstNameAttributeKey(firstNameAttributeKey) + .lastNameAttributeKey(lastNameAttributeKey) + .tenantNameStrategy(tenantNameStrategy) + .tenantNamePattern(tenantNamePattern) + .customerNamePattern(customerNamePattern) + .defaultDashboardName(defaultDashboardName) + .alwaysFullScreen(alwaysFullScreen) + .build() + : null + ) + .custom( + type == MapperType.CUSTOM ? + OAuth2CustomMapperConfig.builder() + .url(url) + .username(username) + .password(password) + .sendToken(sendToken) + .build() + : null + ) + .build() + ); + clientRegistrationInfo.setClientId(clientId); + clientRegistrationInfo.setClientSecret(clientSecret); + clientRegistrationInfo.setAuthorizationUri(authorizationUri); + clientRegistrationInfo.setAccessTokenUri(tokenUri); + clientRegistrationInfo.setScope(Arrays.asList(scope.split(","))); + clientRegistrationInfo.setUserInfoUri(userInfoUri); + clientRegistrationInfo.setUserNameAttributeName(userNameAttributeName); + clientRegistrationInfo.setJwkSetUri(jwkSetUri); + clientRegistrationInfo.setClientAuthenticationMethod(clientAuthenticationMethod); + clientRegistrationInfo.setLoginButtonLabel(loginButtonLabel); + clientRegistrationInfo.setLoginButtonIcon(loginButtonIcon); + return clientRegistrationInfo; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ExtendedOAuth2ClientRegistrationInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ExtendedOAuth2ClientRegistrationInfoEntity.java new file mode 100644 index 0000000000..ddb6e264ac --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ExtendedOAuth2ClientRegistrationInfoEntity.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.oauth2.ExtendedOAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.SchemeType; + +@Data +@EqualsAndHashCode(callSuper = true) +public class ExtendedOAuth2ClientRegistrationInfoEntity extends AbstractOAuth2ClientRegistrationInfoEntity { + + private String domainName; + private SchemeType domainScheme; + + public ExtendedOAuth2ClientRegistrationInfoEntity() { + super(); + } + + public ExtendedOAuth2ClientRegistrationInfoEntity(OAuth2ClientRegistrationInfoEntity oAuth2ClientRegistrationInfoEntity, + String domainName, + SchemeType domainScheme) { + super(oAuth2ClientRegistrationInfoEntity); + this.domainName = domainName; + this.domainScheme = domainScheme; + } + + @Override + public ExtendedOAuth2ClientRegistrationInfo toData() { + return new ExtendedOAuth2ClientRegistrationInfo(super.toOAuth2ClientRegistrationInfo(), + domainScheme, + domainName); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java index c34e2a9793..466034e31c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java @@ -15,13 +15,11 @@ */ package org.thingsboard.server.dao.model.sql; -import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; -import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId; import org.thingsboard.server.common.data.oauth2.*; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; @@ -38,70 +36,15 @@ import java.util.UUID; @Table(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_COLUMN_FAMILY_NAME) public class OAuth2ClientRegistrationEntity extends BaseSqlEntity { - @Column(name = ModelConstants.OAUTH2_ENABLED_PROPERTY) - private Boolean enabled; + @Column(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_INFO_ID_PROPERTY, columnDefinition = "uuid") + private UUID clientRegistrationInfoId; + @Column(name = ModelConstants.OAUTH2_DOMAIN_NAME_PROPERTY) private String domainName; - @Column(name = ModelConstants.OAUTH2_CLIENT_ID_PROPERTY) - private String clientId; - @Column(name = ModelConstants.OAUTH2_CLIENT_SECRET_PROPERTY) - private String clientSecret; - @Column(name = ModelConstants.OAUTH2_AUTHORIZATION_URI_PROPERTY) - private String authorizationUri; - @Column(name = ModelConstants.OAUTH2_TOKEN_URI_PROPERTY) - private String tokenUri; - @Column(name = ModelConstants.OAUTH2_REDIRECT_URI_TEMPLATE_PROPERTY) - private String redirectUriTemplate; - @Column(name = ModelConstants.OAUTH2_SCOPE_PROPERTY) - private String scope; - @Column(name = ModelConstants.OAUTH2_USER_INFO_URI_PROPERTY) - private String userInfoUri; - @Column(name = ModelConstants.OAUTH2_USER_NAME_ATTRIBUTE_NAME_PROPERTY) - private String userNameAttributeName; - @Column(name = ModelConstants.OAUTH2_JWK_SET_URI_PROPERTY) - private String jwkSetUri; - @Column(name = ModelConstants.OAUTH2_CLIENT_AUTHENTICATION_METHOD_PROPERTY) - private String clientAuthenticationMethod; - @Column(name = ModelConstants.OAUTH2_LOGIN_BUTTON_LABEL_PROPERTY) - private String loginButtonLabel; - @Column(name = ModelConstants.OAUTH2_LOGIN_BUTTON_ICON_PROPERTY) - private String loginButtonIcon; - @Column(name = ModelConstants.OAUTH2_ALLOW_USER_CREATION_PROPERTY) - private Boolean allowUserCreation; - @Column(name = ModelConstants.OAUTH2_ACTIVATE_USER_PROPERTY) - private Boolean activateUser; - @Enumerated(EnumType.STRING) - @Column(name = ModelConstants.OAUTH2_MAPPER_TYPE_PROPERTY) - private MapperType type; - @Column(name = ModelConstants.OAUTH2_EMAIL_ATTRIBUTE_KEY_PROPERTY) - private String emailAttributeKey; - @Column(name = ModelConstants.OAUTH2_FIRST_NAME_ATTRIBUTE_KEY_PROPERTY) - private String firstNameAttributeKey; - @Column(name = ModelConstants.OAUTH2_LAST_NAME_ATTRIBUTE_KEY_PROPERTY) - private String lastNameAttributeKey; - @Enumerated(EnumType.STRING) - @Column(name = ModelConstants.OAUTH2_TENANT_NAME_STRATEGY_PROPERTY) - private TenantNameStrategyType tenantNameStrategy; - @Column(name = ModelConstants.OAUTH2_TENANT_NAME_PATTERN_PROPERTY) - private String tenantNamePattern; - @Column(name = ModelConstants.OAUTH2_CUSTOMER_NAME_PATTERN_PROPERTY) - private String customerNamePattern; - @Column(name = ModelConstants.OAUTH2_DEFAULT_DASHBOARD_NAME_PROPERTY) - private String defaultDashboardName; - @Column(name = ModelConstants.OAUTH2_ALWAYS_FULL_SCREEN_PROPERTY) - private Boolean alwaysFullScreen; - @Column(name = ModelConstants.OAUTH2_MAPPER_URL_PROPERTY) - private String url; - @Column(name = ModelConstants.OAUTH2_MAPPER_USERNAME_PROPERTY) - private String username; - @Column(name = ModelConstants.OAUTH2_MAPPER_PASSWORD_PROPERTY) - private String password; - @Column(name = ModelConstants.OAUTH2_MAPPER_SEND_TOKEN_PROPERTY) - private Boolean sendToken; - @Type(type = "json") - @Column(name = ModelConstants.OAUTH2_ADDITIONAL_INFO_PROPERTY) - private JsonNode additionalInfo; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.OAUTH2_DOMAIN_SCHEME_PROPERTY) + private SchemeType domainScheme; public OAuth2ClientRegistrationEntity() { super(); @@ -111,99 +54,22 @@ public class OAuth2ClientRegistrationEntity extends BaseSqlEntity result + "," + element).orElse(""); - this.userInfoUri = clientRegistration.getUserInfoUri(); - this.userNameAttributeName = clientRegistration.getUserNameAttributeName(); - this.jwkSetUri = clientRegistration.getJwkSetUri(); - this.clientAuthenticationMethod = clientRegistration.getClientAuthenticationMethod(); - this.loginButtonLabel = clientRegistration.getLoginButtonLabel(); - this.loginButtonIcon = clientRegistration.getLoginButtonIcon(); - this.additionalInfo = clientRegistration.getAdditionalInfo(); - OAuth2MapperConfig mapperConfig = clientRegistration.getMapperConfig(); - if (mapperConfig != null) { - this.allowUserCreation = mapperConfig.isAllowUserCreation(); - this.activateUser = mapperConfig.isActivateUser(); - this.type = mapperConfig.getType(); - OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic(); - if (basicConfig != null) { - this.emailAttributeKey = basicConfig.getEmailAttributeKey(); - this.firstNameAttributeKey = basicConfig.getFirstNameAttributeKey(); - this.lastNameAttributeKey = basicConfig.getLastNameAttributeKey(); - this.tenantNameStrategy = basicConfig.getTenantNameStrategy(); - this.tenantNamePattern = basicConfig.getTenantNamePattern(); - this.customerNamePattern = basicConfig.getCustomerNamePattern(); - this.defaultDashboardName = basicConfig.getDefaultDashboardName(); - this.alwaysFullScreen = basicConfig.isAlwaysFullScreen(); - } - OAuth2CustomMapperConfig customConfig = mapperConfig.getCustom(); - if (customConfig != null) { - this.url = customConfig.getUrl(); - this.username = customConfig.getUsername(); - this.password = customConfig.getPassword(); - this.sendToken = customConfig.isSendToken(); - } + if (clientRegistration.getClientRegistrationId() != null){ + this.clientRegistrationInfoId = clientRegistration.getClientRegistrationId().getId(); } + this.createdTime = clientRegistration.getCreatedTime(); + this.domainName = clientRegistration.getDomainName(); + this.domainScheme = clientRegistration.getDomainScheme(); } @Override public OAuth2ClientRegistration toData() { OAuth2ClientRegistration clientRegistration = new OAuth2ClientRegistration(); clientRegistration.setId(new OAuth2ClientRegistrationId(id)); - clientRegistration.setEnabled(enabled); + clientRegistration.setClientRegistrationId(new OAuth2ClientRegistrationInfoId(clientRegistrationInfoId)); clientRegistration.setCreatedTime(createdTime); clientRegistration.setDomainName(domainName); - clientRegistration.setAdditionalInfo(additionalInfo); - clientRegistration.setMapperConfig( - OAuth2MapperConfig.builder() - .allowUserCreation(allowUserCreation) - .activateUser(activateUser) - .type(type) - .basic( - type == MapperType.BASIC ? - OAuth2BasicMapperConfig.builder() - .emailAttributeKey(emailAttributeKey) - .firstNameAttributeKey(firstNameAttributeKey) - .lastNameAttributeKey(lastNameAttributeKey) - .tenantNameStrategy(tenantNameStrategy) - .tenantNamePattern(tenantNamePattern) - .customerNamePattern(customerNamePattern) - .defaultDashboardName(defaultDashboardName) - .alwaysFullScreen(alwaysFullScreen) - .build() - : null - ) - .custom( - type == MapperType.CUSTOM ? - OAuth2CustomMapperConfig.builder() - .url(url) - .username(username) - .password(password) - .sendToken(sendToken) - .build() - : null - ) - .build() - ); - clientRegistration.setClientId(clientId); - clientRegistration.setClientSecret(clientSecret); - clientRegistration.setAuthorizationUri(authorizationUri); - clientRegistration.setAccessTokenUri(tokenUri); - clientRegistration.setRedirectUriTemplate(redirectUriTemplate); - clientRegistration.setScope(Arrays.asList(scope.split(","))); - clientRegistration.setUserInfoUri(userInfoUri); - clientRegistration.setUserNameAttributeName(userNameAttributeName); - clientRegistration.setJwkSetUri(jwkSetUri); - clientRegistration.setClientAuthenticationMethod(clientAuthenticationMethod); - clientRegistration.setLoginButtonLabel(loginButtonLabel); - clientRegistration.setLoginButtonIcon(loginButtonIcon); + clientRegistration.setDomainScheme(domainScheme); return clientRegistration; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationInfoEntity.java new file mode 100644 index 0000000000..7fd68e2716 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationInfoEntity.java @@ -0,0 +1,51 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.util.mapping.JsonStringType; + +import javax.persistence.Entity; +import javax.persistence.Table; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@TypeDef(name = "json", typeClass = JsonStringType.class) +@Table(name = ModelConstants.OAUTH2_CLIENT_REGISTRATION_INFO_COLUMN_FAMILY_NAME) +public class OAuth2ClientRegistrationInfoEntity extends AbstractOAuth2ClientRegistrationInfoEntity { + + public OAuth2ClientRegistrationInfoEntity() { + super(); + } + + public OAuth2ClientRegistrationInfoEntity(OAuth2ClientRegistrationInfo clientRegistration) { + super(clientRegistration); + } + + public OAuth2ClientRegistrationInfoEntity(OAuth2ClientRegistrationInfoEntity oAuth2ClientRegistrationInfoEntity) { + super(oAuth2ClientRegistrationInfoEntity); + } + + @Override + public OAuth2ClientRegistrationInfo toData() { + return super.toOAuth2ClientRegistrationInfo(); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java index c3b3572da3..93c4c47df0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java @@ -21,8 +21,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.oauth2.ExtendedOAuth2ClientRegistration; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; import java.util.UUID; @@ -34,12 +33,12 @@ public class HybridClientRegistrationRepository implements ClientRegistrationRep @Override public ClientRegistration findByRegistrationId(String registrationId) { - OAuth2ClientRegistration oAuth2ClientRegistration = oAuth2Service.findClientRegistration(UUID.fromString(registrationId)); - return oAuth2ClientRegistration == null ? - null : toSpringClientRegistration(oAuth2ClientRegistration); + OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo = oAuth2Service.findClientRegistrationInfo(UUID.fromString(registrationId)); + return oAuth2ClientRegistrationInfo == null ? + null : toSpringClientRegistration(oAuth2ClientRegistrationInfo); } - private ClientRegistration toSpringClientRegistration(OAuth2ClientRegistration localClientRegistration){ + private ClientRegistration toSpringClientRegistration(OAuth2ClientRegistrationInfo localClientRegistration){ String registrationId = localClientRegistration.getUuidId().toString(); return ClientRegistration.withRegistrationId(registrationId) .clientName(localClientRegistration.getName()) @@ -47,7 +46,6 @@ public class HybridClientRegistrationRepository implements ClientRegistrationRep .authorizationUri(localClientRegistration.getAuthorizationUri()) .clientSecret(localClientRegistration.getClientSecret()) .tokenUri(localClientRegistration.getAccessTokenUri()) - .redirectUriTemplate(localClientRegistration.getRedirectUriTemplate()) .scope(localClientRegistration.getScope()) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .userInfoUri(localClientRegistration.getUserInfoUri()) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java index 6dc1c729cb..d56ff34354 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java @@ -18,13 +18,6 @@ package org.thingsboard.server.dao.oauth2; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; import org.thingsboard.server.dao.Dao; -import java.util.List; -import java.util.UUID; - public interface OAuth2ClientRegistrationDao extends Dao { - List findAll(); - - List findByDomainName(String domainName); - - int removeByDomainName(String domainName); + void deleteAll(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationInfoDao.java new file mode 100644 index 0000000000..a907aefe6b --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationInfoDao.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.oauth2; + +import org.thingsboard.server.common.data.oauth2.ExtendedOAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.SchemeType; +import org.thingsboard.server.dao.Dao; + +import java.util.List; +import java.util.Set; + +public interface OAuth2ClientRegistrationInfoDao extends Dao { + List findAll(); + + List findAllExtended(); + + List findByDomainSchemesAndDomainName(List domainSchemes, String domainName); + + void deleteAll(); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index c9fcb9e51b..fab4c559d3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -19,11 +19,11 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.*; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.exception.IncorrectParameterException; import javax.transaction.Transactional; import java.util.*; @@ -39,78 +39,89 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; public static final String INCORRECT_CLIENT_REGISTRATION_ID = "Incorrect clientRegistrationId "; public static final String INCORRECT_DOMAIN_NAME = "Incorrect domainName "; + public static final String INCORRECT_DOMAIN_SCHEME = "Incorrect domainScheme "; + @Autowired + private OAuth2ClientRegistrationInfoDao clientRegistrationInfoDao; @Autowired private OAuth2ClientRegistrationDao clientRegistrationDao; @Override - public List getOAuth2Clients(String domainName) { - log.trace("Executing getOAuth2Clients [{}]", domainName); + public List getOAuth2Clients(String domainSchemeStr, String domainName) { + log.trace("Executing getOAuth2Clients [{}://{}]", domainSchemeStr, domainName); + if (domainSchemeStr == null) { + throw new IncorrectParameterException(INCORRECT_DOMAIN_SCHEME); + } + SchemeType domainScheme; + try { + domainScheme = SchemeType.valueOf(domainSchemeStr.toUpperCase()); + } catch (IllegalArgumentException e){ + throw new IncorrectParameterException(INCORRECT_DOMAIN_SCHEME); + } validateString(domainName, INCORRECT_DOMAIN_NAME + domainName); - return clientRegistrationDao.findByDomainName(domainName).stream() - .filter(OAuth2ClientRegistration::isEnabled) + return clientRegistrationInfoDao.findByDomainSchemesAndDomainName(Arrays.asList(domainScheme, SchemeType.MIXED), domainName).stream() + .filter(OAuth2ClientRegistrationInfo::isEnabled) .map(OAuth2Utils::toClientInfo) .collect(Collectors.toList()); } @Override @Transactional - public OAuth2ClientsParams saveOAuth2Params(OAuth2ClientsParams oauth2Params) { + public void saveOAuth2Params(OAuth2ClientsParams oauth2Params) { log.trace("Executing saveOAuth2Params [{}]", oauth2Params); clientParamsValidator.accept(oauth2Params); - List inputClientRegistrations = OAuth2Utils.toClientRegistrations(oauth2Params); - List savedClientRegistrations = inputClientRegistrations.stream() - .map(clientRegistration -> clientRegistrationDao.save(TenantId.SYS_TENANT_ID, clientRegistration)) - .collect(Collectors.toList()); - return OAuth2Utils.toOAuth2Params(savedClientRegistrations); + clientRegistrationDao.deleteAll(); + clientRegistrationInfoDao.deleteAll(); + oauth2Params.getOAuth2DomainDtos().forEach(domainParams -> { + domainParams.getClientRegistrations().forEach(clientRegistrationDto -> { + OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo = OAuth2Utils.toClientRegistrationInfo(oauth2Params.isEnabled(), clientRegistrationDto); + OAuth2ClientRegistrationInfo savedClientRegistrationInfo = clientRegistrationInfoDao.save(TenantId.SYS_TENANT_ID, oAuth2ClientRegistrationInfo); + domainParams.getDomainInfos().forEach(domainInfo -> { + OAuth2ClientRegistration oAuth2ClientRegistration = OAuth2Utils.toClientRegistration(savedClientRegistrationInfo.getId(), + domainInfo.getScheme(), domainInfo.getName()); + clientRegistrationDao.save(TenantId.SYS_TENANT_ID, oAuth2ClientRegistration); + }); + }); + }); } @Override public OAuth2ClientsParams findOAuth2Params() { log.trace("Executing findOAuth2Params"); - return OAuth2Utils.toOAuth2Params(clientRegistrationDao.findAll()); - } - - @Override - public OAuth2ClientRegistration findClientRegistration(UUID id) { - log.trace("Executing findClientRegistration [{}]", id); - validateId(id, INCORRECT_CLIENT_REGISTRATION_ID + id); - return clientRegistrationDao.findById(null, id); - } - - @Override - public List findAllClientRegistrations() { - log.trace("Executing findAllClientRegistrations"); - return clientRegistrationDao.findAll(); + List extendedInfos = clientRegistrationInfoDao.findAllExtended(); + return OAuth2Utils.toOAuth2Params(extendedInfos); } @Override - public void deleteClientRegistrationById(OAuth2ClientRegistrationId id) { - log.trace("Executing deleteClientRegistrationById [{}]", id); + public OAuth2ClientRegistrationInfo findClientRegistrationInfo(UUID id) { + log.trace("Executing findClientRegistrationInfo [{}]", id); validateId(id, INCORRECT_CLIENT_REGISTRATION_ID + id); - clientRegistrationDao.removeById(TenantId.SYS_TENANT_ID, id.getId()); + return clientRegistrationInfoDao.findById(null, id); } @Override - @Transactional - public void deleteClientRegistrationsByDomain(String domain) { - log.trace("Executing deleteClientRegistrationsByDomain [{}]", domain); - validateString(domain, INCORRECT_DOMAIN_NAME + domain); - clientRegistrationDao.removeByDomainName(domain); + public List findAllClientRegistrationInfos() { + log.trace("Executing findAllClientRegistrationInfos"); + return clientRegistrationInfoDao.findAll(); } private final Consumer clientParamsValidator = oauth2Params -> { if (oauth2Params == null - || oauth2Params.getOAuth2DomainDtos() == null - || oauth2Params.getOAuth2DomainDtos().isEmpty()) { + || oauth2Params.getOAuth2DomainDtos() == null) { throw new DataValidationException("Domain params should be specified!"); } for (OAuth2ClientsDomainParams domainParams : oauth2Params.getOAuth2DomainDtos()) { - if (StringUtils.isEmpty(domainParams.getDomainName())) { - throw new DataValidationException("Domain name should be specified!"); + if (domainParams.getDomainInfos() == null + || domainParams.getDomainInfos().isEmpty()) { + throw new DataValidationException("List of domain configuration should be specified!"); } - if (StringUtils.isEmpty(domainParams.getRedirectUriTemplate())) { - throw new DataValidationException("Redirect URI template should be specified!"); + for (DomainInfo domainInfo : domainParams.getDomainInfos()) { + if (StringUtils.isEmpty(domainInfo.getName())) { + throw new DataValidationException("Domain name should be specified!"); + } + if (StringUtils.isEmpty(domainInfo.getScheme())) { + throw new DataValidationException("Domain scheme should be specified!"); + } } if (domainParams.getClientRegistrations() == null || domainParams.getClientRegistrations().isEmpty()) { throw new DataValidationException("Client registrations should be specified!"); diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java index 5b3e6654c1..a96a0d36c8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java @@ -15,93 +15,88 @@ */ package org.thingsboard.server.dao.oauth2; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId; import org.thingsboard.server.common.data.oauth2.*; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; +import java.util.*; public class OAuth2Utils { public static final String OAUTH2_AUTHORIZATION_PATH_TEMPLATE = "/oauth2/authorization/%s"; - public static OAuth2ClientInfo toClientInfo(OAuth2ClientRegistration clientRegistration) { + public static OAuth2ClientInfo toClientInfo(OAuth2ClientRegistrationInfo clientRegistrationInfo) { OAuth2ClientInfo client = new OAuth2ClientInfo(); - client.setName(clientRegistration.getLoginButtonLabel()); - client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, clientRegistration.getUuidId().toString())); - client.setIcon(clientRegistration.getLoginButtonIcon()); + client.setName(clientRegistrationInfo.getLoginButtonLabel()); + client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, clientRegistrationInfo.getUuidId().toString())); + client.setIcon(clientRegistrationInfo.getLoginButtonIcon()); return client; } - public static List toClientRegistrations(OAuth2ClientsParams oAuth2Params) { - return oAuth2Params.getOAuth2DomainDtos().stream() - .flatMap(domainParams -> domainParams.getClientRegistrations().stream() - .map(clientRegistrationDto -> OAuth2Utils.toClientRegistration(oAuth2Params.isEnabled(), - domainParams.getDomainName(), - domainParams.getRedirectUriTemplate(), - clientRegistrationDto) - )) - .collect(Collectors.toList()); - } - - public static OAuth2ClientsParams toOAuth2Params(List clientRegistrations) { - Map domainParamsMap = new HashMap<>(); - boolean enabled = true; - for (OAuth2ClientRegistration clientRegistration : clientRegistrations) { - enabled = clientRegistration.isEnabled(); - String domainName = clientRegistration.getDomainName(); - OAuth2ClientsDomainParams domainParams = domainParamsMap.computeIfAbsent(domainName, - key -> new OAuth2ClientsDomainParams(domainName, clientRegistration.getRedirectUriTemplate(), new ArrayList<>()) - ); - domainParams.getClientRegistrations() - .add(toClientRegistrationDto(clientRegistration)); + public static OAuth2ClientsParams toOAuth2Params(List extendedOAuth2ClientRegistrationInfos) { + Map> domainsByInfoId = new HashMap<>(); + Map infoById = new HashMap<>(); + for (ExtendedOAuth2ClientRegistrationInfo extendedClientRegistrationInfo : extendedOAuth2ClientRegistrationInfos) { + String domainName = extendedClientRegistrationInfo.getDomainName(); + SchemeType domainScheme = extendedClientRegistrationInfo.getDomainScheme(); + domainsByInfoId.computeIfAbsent(extendedClientRegistrationInfo.getId(), key -> new HashSet<>()) + .add(new DomainInfo(domainScheme, domainName)); + infoById.put(extendedClientRegistrationInfo.getId(), extendedClientRegistrationInfo); } - return new OAuth2ClientsParams(enabled, new ArrayList<>(domainParamsMap.values())); + Map, OAuth2ClientsDomainParams> domainParamsMap = new HashMap<>(); + domainsByInfoId.forEach((clientRegistrationInfoId, domainInfos) -> { + domainParamsMap.computeIfAbsent(domainInfos, + key -> new OAuth2ClientsDomainParams(key, new HashSet<>()) + ) + .getClientRegistrations() + .add(toClientRegistrationDto(infoById.get(clientRegistrationInfoId))); + }); + boolean enabled = extendedOAuth2ClientRegistrationInfos.stream() + .map(OAuth2ClientRegistrationInfo::isEnabled) + .findFirst().orElse(false); + return new OAuth2ClientsParams(enabled, new HashSet<>(domainParamsMap.values())); } - public static ClientRegistrationDto toClientRegistrationDto(OAuth2ClientRegistration oAuth2ClientRegistration) { + public static ClientRegistrationDto toClientRegistrationDto(OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo) { return ClientRegistrationDto.builder() - .id(oAuth2ClientRegistration.getId()) - .createdTime(oAuth2ClientRegistration.getCreatedTime()) - .mapperConfig(oAuth2ClientRegistration.getMapperConfig()) - .clientId(oAuth2ClientRegistration.getClientId()) - .clientSecret(oAuth2ClientRegistration.getClientSecret()) - .authorizationUri(oAuth2ClientRegistration.getAuthorizationUri()) - .accessTokenUri(oAuth2ClientRegistration.getAccessTokenUri()) - .scope(oAuth2ClientRegistration.getScope()) - .userInfoUri(oAuth2ClientRegistration.getUserInfoUri()) - .userNameAttributeName(oAuth2ClientRegistration.getUserNameAttributeName()) - .jwkSetUri(oAuth2ClientRegistration.getJwkSetUri()) - .clientAuthenticationMethod(oAuth2ClientRegistration.getClientAuthenticationMethod()) - .loginButtonLabel(oAuth2ClientRegistration.getLoginButtonLabel()) - .loginButtonIcon(oAuth2ClientRegistration.getLoginButtonIcon()) - .additionalInfo(oAuth2ClientRegistration.getAdditionalInfo()) + .mapperConfig(oAuth2ClientRegistrationInfo.getMapperConfig()) + .clientId(oAuth2ClientRegistrationInfo.getClientId()) + .clientSecret(oAuth2ClientRegistrationInfo.getClientSecret()) + .authorizationUri(oAuth2ClientRegistrationInfo.getAuthorizationUri()) + .accessTokenUri(oAuth2ClientRegistrationInfo.getAccessTokenUri()) + .scope(oAuth2ClientRegistrationInfo.getScope()) + .userInfoUri(oAuth2ClientRegistrationInfo.getUserInfoUri()) + .userNameAttributeName(oAuth2ClientRegistrationInfo.getUserNameAttributeName()) + .jwkSetUri(oAuth2ClientRegistrationInfo.getJwkSetUri()) + .clientAuthenticationMethod(oAuth2ClientRegistrationInfo.getClientAuthenticationMethod()) + .loginButtonLabel(oAuth2ClientRegistrationInfo.getLoginButtonLabel()) + .loginButtonIcon(oAuth2ClientRegistrationInfo.getLoginButtonIcon()) + .additionalInfo(oAuth2ClientRegistrationInfo.getAdditionalInfo()) .build(); } - private static OAuth2ClientRegistration toClientRegistration(boolean enabled, String domainName, - String redirectUriTemplate, - ClientRegistrationDto clientRegistrationDto) { + public static OAuth2ClientRegistrationInfo toClientRegistrationInfo(boolean enabled, ClientRegistrationDto clientRegistrationDto) { + OAuth2ClientRegistrationInfo clientRegistrationInfo = new OAuth2ClientRegistrationInfo(); + clientRegistrationInfo.setEnabled(enabled); + clientRegistrationInfo.setMapperConfig(clientRegistrationDto.getMapperConfig()); + clientRegistrationInfo.setClientId(clientRegistrationDto.getClientId()); + clientRegistrationInfo.setClientSecret(clientRegistrationDto.getClientSecret()); + clientRegistrationInfo.setAuthorizationUri(clientRegistrationDto.getAuthorizationUri()); + clientRegistrationInfo.setAccessTokenUri(clientRegistrationDto.getAccessTokenUri()); + clientRegistrationInfo.setScope(clientRegistrationDto.getScope()); + clientRegistrationInfo.setUserInfoUri(clientRegistrationDto.getUserInfoUri()); + clientRegistrationInfo.setUserNameAttributeName(clientRegistrationDto.getUserNameAttributeName()); + clientRegistrationInfo.setJwkSetUri(clientRegistrationDto.getJwkSetUri()); + clientRegistrationInfo.setClientAuthenticationMethod(clientRegistrationDto.getClientAuthenticationMethod()); + clientRegistrationInfo.setLoginButtonLabel(clientRegistrationDto.getLoginButtonLabel()); + clientRegistrationInfo.setLoginButtonIcon(clientRegistrationDto.getLoginButtonIcon()); + clientRegistrationInfo.setAdditionalInfo(clientRegistrationDto.getAdditionalInfo()); + return clientRegistrationInfo; + } + + public static OAuth2ClientRegistration toClientRegistration(OAuth2ClientRegistrationInfoId clientRegistrationInfoId, SchemeType domainScheme, String domainName) { OAuth2ClientRegistration clientRegistration = new OAuth2ClientRegistration(); - clientRegistration.setId(clientRegistrationDto.getId()); - clientRegistration.setEnabled(enabled); - clientRegistration.setCreatedTime(clientRegistrationDto.getCreatedTime()); + clientRegistration.setClientRegistrationId(clientRegistrationInfoId); clientRegistration.setDomainName(domainName); - clientRegistration.setRedirectUriTemplate(redirectUriTemplate); - clientRegistration.setMapperConfig(clientRegistrationDto.getMapperConfig()); - clientRegistration.setClientId(clientRegistrationDto.getClientId()); - clientRegistration.setClientSecret(clientRegistrationDto.getClientSecret()); - clientRegistration.setAuthorizationUri(clientRegistrationDto.getAuthorizationUri()); - clientRegistration.setAccessTokenUri(clientRegistrationDto.getAccessTokenUri()); - clientRegistration.setScope(clientRegistrationDto.getScope()); - clientRegistration.setUserInfoUri(clientRegistrationDto.getUserInfoUri()); - clientRegistration.setUserNameAttributeName(clientRegistrationDto.getUserNameAttributeName()); - clientRegistration.setJwkSetUri(clientRegistrationDto.getJwkSetUri()); - clientRegistration.setClientAuthenticationMethod(clientRegistrationDto.getClientAuthenticationMethod()); - clientRegistration.setLoginButtonLabel(clientRegistrationDto.getLoginButtonLabel()); - clientRegistration.setLoginButtonIcon(clientRegistrationDto.getLoginButtonIcon()); - clientRegistration.setAdditionalInfo(clientRegistrationDto.getAdditionalInfo()); + clientRegistration.setDomainScheme(domainScheme); return clientRegistration; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java index c9ba51b813..1fd901b4a3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java @@ -16,18 +16,15 @@ package org.thingsboard.server.dao.sql.oauth2; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; -import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationEntity; import org.thingsboard.server.dao.oauth2.OAuth2ClientRegistrationDao; import org.thingsboard.server.dao.sql.JpaAbstractDao; -import java.util.ArrayList; -import java.util.List; import java.util.UUID; -import java.util.stream.Collectors; @Component @RequiredArgsConstructor @@ -45,23 +42,7 @@ public class JpaOAuth2ClientRegistrationDao extends JpaAbstractDao findAll() { - Iterable entities = repository.findAll(); - List result = new ArrayList<>(); - entities.forEach(entity -> { - result.add(DaoUtil.getData(entity)); - }); - return result; - } - - @Override - public List findByDomainName(String domainName) { - List entities = repository.findAllByDomainName(domainName); - return entities.stream().map(DaoUtil::getData).collect(Collectors.toList()); - } - - @Override - public int removeByDomainName(String domainName) { - return repository.deleteByDomainName(domainName); + public void deleteAll() { + repository.deleteAll(); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationInfoDao.java new file mode 100644 index 0000000000..ec8651bc3a --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationInfoDao.java @@ -0,0 +1,76 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.oauth2; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.oauth2.ExtendedOAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.SchemeType; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationInfoEntity; +import org.thingsboard.server.dao.oauth2.OAuth2ClientRegistrationInfoDao; +import org.thingsboard.server.dao.sql.JpaAbstractDao; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +public class JpaOAuth2ClientRegistrationInfoDao extends JpaAbstractDao implements OAuth2ClientRegistrationInfoDao { + private final OAuth2ClientRegistrationInfoRepository repository; + + @Override + protected Class getEntityClass() { + return OAuth2ClientRegistrationInfoEntity.class; + } + + @Override + protected CrudRepository getCrudRepository() { + return repository; + } + + @Override + public List findAll() { + Iterable entities = repository.findAll(); + List result = new ArrayList<>(); + entities.forEach(entity -> { + result.add(DaoUtil.getData(entity)); + }); + return result; + } + + @Override + public List findAllExtended() { + return repository.findAllExtended().stream() + .map(DaoUtil::getData) + .collect(Collectors.toList()); + } + + @Override + public List findByDomainSchemesAndDomainName(List domainSchemes, String domainName) { + List entities = repository.findAllByDomainSchemesAndName(domainSchemes, domainName); + return entities.stream().map(DaoUtil::getData).collect(Collectors.toList()); + } + + @Override + public void deleteAll() { + repository.deleteAll(); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationInfoRepository.java new file mode 100644 index 0000000000..96094bd2fe --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationInfoRepository.java @@ -0,0 +1,41 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.oauth2; + +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; +import org.thingsboard.server.common.data.oauth2.SchemeType; +import org.thingsboard.server.dao.model.sql.ExtendedOAuth2ClientRegistrationInfoEntity; +import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationInfoEntity; + +import java.util.List; +import java.util.UUID; + +public interface OAuth2ClientRegistrationInfoRepository extends CrudRepository { + @Query("SELECT new OAuth2ClientRegistrationInfoEntity(cr_info) " + + "FROM OAuth2ClientRegistrationInfoEntity cr_info " + + "LEFT JOIN OAuth2ClientRegistrationEntity cr on cr_info.id = cr.clientRegistrationInfoId " + + "WHERE cr.domainName = :domainName " + + "AND cr.domainScheme IN (:domainSchemes)") + List findAllByDomainSchemesAndName(@Param("domainSchemes") List domainSchemes, + @Param("domainName") String domainName); + + @Query("SELECT new org.thingsboard.server.dao.model.sql.ExtendedOAuth2ClientRegistrationInfoEntity(cr_info, cr.domainName, cr.domainScheme) " + + "FROM OAuth2ClientRegistrationInfoEntity cr_info " + + "LEFT JOIN OAuth2ClientRegistrationEntity cr on cr_info.id = cr.clientRegistrationInfoId ") + List findAllExtended(); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java index 5c869ddc30..438f73c7d1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java @@ -18,11 +18,7 @@ package org.thingsboard.server.dao.sql.oauth2; import org.springframework.data.repository.CrudRepository; import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationEntity; -import java.util.List; import java.util.UUID; public interface OAuth2ClientRegistrationRepository extends CrudRepository { - List findAllByDomainName(String domainName); - - int deleteByDomainName(String domainName); } diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index a78d619361..7e93b47188 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -291,17 +291,15 @@ CREATE TABLE IF NOT EXISTS ts_kv_dictionary ( ); -CREATE TABLE IF NOT EXISTS oauth2_client_registration ( - id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, +CREATE TABLE IF NOT EXISTS oauth2_client_registration_info ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_info_pkey PRIMARY KEY, enabled boolean, created_time bigint NOT NULL, additional_info varchar, - domain_name varchar(255), client_id varchar(255), client_secret varchar(255), authorization_uri varchar(255), token_uri varchar(255), - redirect_uri_template varchar(255), scope varchar(255), user_info_uri varchar(255), user_name_attribute_name varchar(255), @@ -326,6 +324,14 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( custom_send_token boolean ); +CREATE TABLE IF NOT EXISTS oauth2_client_registration ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + created_time bigint NOT NULL, + domain_name varchar(255), + domain_scheme varchar(31), + client_registration_info_id uuid +); + CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY, created_time bigint NOT NULL, diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 3a53f4166e..e74513b897 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -316,17 +316,15 @@ CREATE TABLE IF NOT EXISTS ts_kv_dictionary CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) ); -CREATE TABLE IF NOT EXISTS oauth2_client_registration ( - id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, +CREATE TABLE IF NOT EXISTS oauth2_client_registration_info ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_info_pkey PRIMARY KEY, enabled boolean, created_time bigint NOT NULL, additional_info varchar, - domain_name varchar(255), client_id varchar(255), client_secret varchar(255), authorization_uri varchar(255), token_uri varchar(255), - redirect_uri_template varchar(255), scope varchar(255), user_info_uri varchar(255), user_name_attribute_name varchar(255), @@ -351,6 +349,14 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration ( custom_send_token boolean ); +CREATE TABLE IF NOT EXISTS oauth2_client_registration ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + created_time bigint NOT NULL, + domain_name varchar(255), + domain_scheme varchar(31), + client_registration_info_id uuid +); + CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY, created_time bigint NOT NULL, diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java index c96ba1f99d..fd5b5752cb 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.service; +import com.google.common.collect.Sets; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -22,170 +23,413 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.oauth2.*; import org.thingsboard.server.dao.oauth2.OAuth2Service; -import org.thingsboard.server.dao.oauth2.OAuth2Utils; import java.util.*; import java.util.stream.Collectors; -import static org.thingsboard.server.dao.oauth2.OAuth2Utils.toClientRegistrations; - public class BaseOAuth2ServiceTest extends AbstractServiceTest { + private static final OAuth2ClientsParams EMPTY_PARAMS = new OAuth2ClientsParams(false, new HashSet<>()); @Autowired protected OAuth2Service oAuth2Service; @Before public void beforeRun() { - Assert.assertTrue(oAuth2Service.findAllClientRegistrations().isEmpty()); + Assert.assertTrue(oAuth2Service.findAllClientRegistrationInfos().isEmpty()); } @After public void after() { - oAuth2Service.findAllClientRegistrations().forEach(clientRegistration -> { - oAuth2Service.deleteClientRegistrationById(clientRegistration.getId()); - }); - Assert.assertTrue(oAuth2Service.findAllClientRegistrations().isEmpty()); + oAuth2Service.saveOAuth2Params(EMPTY_PARAMS); + Assert.assertTrue(oAuth2Service.findAllClientRegistrationInfos().isEmpty()); + Assert.assertTrue(oAuth2Service.findOAuth2Params().getOAuth2DomainDtos().isEmpty()); + } + + @Test + public void testCreateAndFindParams() { + OAuth2ClientsParams clientsParams = createDefaultClientsParams(); + oAuth2Service.saveOAuth2Params(clientsParams); + OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params(); + Assert.assertNotNull(foundClientsParams); + // TODO ask if it's safe to check equality on AdditionalProperties + Assert.assertEquals(clientsParams, foundClientsParams); } @Test - public void testCreateNewParams() { - OAuth2ClientRegistration clientRegistration = validClientRegistration("domain-name"); - OAuth2ClientsParams savedOAuth2Params = oAuth2Service.saveOAuth2Params(OAuth2Utils.toOAuth2Params(Collections.singletonList(clientRegistration))); - Assert.assertNotNull(savedOAuth2Params); + public void testDisableParams() { + OAuth2ClientsParams clientsParams = createDefaultClientsParams(); + clientsParams.setEnabled(true); + oAuth2Service.saveOAuth2Params(clientsParams); + OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params(); + Assert.assertNotNull(foundClientsParams); + Assert.assertEquals(clientsParams, foundClientsParams); - List savedClientRegistrations = OAuth2Utils.toClientRegistrations(savedOAuth2Params); - Assert.assertEquals(1, savedClientRegistrations.size()); + clientsParams.setEnabled(false); + oAuth2Service.saveOAuth2Params(clientsParams); + OAuth2ClientsParams foundDisabledClientsParams = oAuth2Service.findOAuth2Params(); + Assert.assertEquals(clientsParams, foundDisabledClientsParams); + } - OAuth2ClientRegistration savedClientRegistration = savedClientRegistrations.get(0); - Assert.assertNotNull(savedClientRegistration.getId()); - clientRegistration.setId(savedClientRegistration.getId()); - clientRegistration.setCreatedTime(savedClientRegistration.getCreatedTime()); - Assert.assertEquals(clientRegistration, savedClientRegistration); + @Test + public void testClearDomainParams() { + OAuth2ClientsParams clientsParams = createDefaultClientsParams(); + oAuth2Service.saveOAuth2Params(clientsParams); + OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params(); + Assert.assertNotNull(foundClientsParams); + Assert.assertEquals(clientsParams, foundClientsParams); - oAuth2Service.deleteClientRegistrationsByDomain("domain-name"); + oAuth2Service.saveOAuth2Params(EMPTY_PARAMS); + OAuth2ClientsParams foundAfterClearClientsParams = oAuth2Service.findOAuth2Params(); + Assert.assertNotNull(foundAfterClearClientsParams); + Assert.assertEquals(EMPTY_PARAMS, foundAfterClearClientsParams); } @Test - public void testFindDomainParams() { - OAuth2ClientRegistration clientRegistration = validClientRegistration(); - oAuth2Service.saveOAuth2Params(OAuth2Utils.toOAuth2Params(Collections.singletonList(clientRegistration))); - - OAuth2ClientsParams foundOAuth2Params = oAuth2Service.findOAuth2Params(); - Assert.assertEquals(1, foundOAuth2Params.getOAuth2DomainDtos().size()); - Assert.assertEquals(1, oAuth2Service.findAllClientRegistrations().size()); - - List foundClientRegistrations = OAuth2Utils.toClientRegistrations(foundOAuth2Params); - OAuth2ClientRegistration foundClientRegistration = foundClientRegistrations.get(0); - Assert.assertNotNull(foundClientRegistration); - clientRegistration.setId(foundClientRegistration.getId()); - clientRegistration.setCreatedTime(foundClientRegistration.getCreatedTime()); - Assert.assertEquals(clientRegistration, foundClientRegistration); + public void testUpdateClientsParams() { + OAuth2ClientsParams clientsParams = createDefaultClientsParams(); + oAuth2Service.saveOAuth2Params(clientsParams); + OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params(); + Assert.assertNotNull(foundClientsParams); + Assert.assertEquals(clientsParams, foundClientsParams); + + OAuth2ClientsParams newClientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("another-domain").scheme(SchemeType.HTTPS).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto() + )) + .build(), + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("test-domain").scheme(SchemeType.MIXED).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto() + )) + .build() + )); + oAuth2Service.saveOAuth2Params(newClientsParams); + OAuth2ClientsParams foundAfterUpdateClientsParams = oAuth2Service.findOAuth2Params(); + Assert.assertNotNull(foundAfterUpdateClientsParams); + Assert.assertEquals(newClientsParams, foundAfterUpdateClientsParams); } @Test public void testGetOAuth2Clients() { - String testDomainName = "test_domain"; - OAuth2ClientRegistration first = validClientRegistration(testDomainName); - first.setEnabled(true); - OAuth2ClientRegistration second = validClientRegistration(testDomainName); - second.setEnabled(true); + Set firstGroup = Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto(), + validClientRegistrationDto(), + validClientRegistrationDto() + ); + Set secondGroup = Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto() + ); + Set thirdGroup = Sets.newHashSet( + validClientRegistrationDto() + ); + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), + DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), + DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() + )) + .clientRegistrations(firstGroup) + .build(), + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), + DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() + )) + .clientRegistrations(secondGroup) + .build(), + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), + DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() + )) + .clientRegistrations(thirdGroup) + .build() + )); + + oAuth2Service.saveOAuth2Params(clientsParams); + OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params(); + Assert.assertNotNull(foundClientsParams); + Assert.assertEquals(clientsParams, foundClientsParams); + + List firstGroupClientInfos = firstGroup.stream() + .map(clientRegistrationDto -> new OAuth2ClientInfo( + clientRegistrationDto.getLoginButtonLabel(), clientRegistrationDto.getLoginButtonIcon(), null)) + .collect(Collectors.toList()); + List secondGroupClientInfos = secondGroup.stream() + .map(clientRegistrationDto -> new OAuth2ClientInfo( + clientRegistrationDto.getLoginButtonLabel(), clientRegistrationDto.getLoginButtonIcon(), null)) + .collect(Collectors.toList()); + List thirdGroupClientInfos = thirdGroup.stream() + .map(clientRegistrationDto -> new OAuth2ClientInfo( + clientRegistrationDto.getLoginButtonLabel(), clientRegistrationDto.getLoginButtonIcon(), null)) + .collect(Collectors.toList()); - oAuth2Service.saveOAuth2Params(OAuth2Utils.toOAuth2Params(Collections.singletonList(first))); - oAuth2Service.saveOAuth2Params(OAuth2Utils.toOAuth2Params(Collections.singletonList(second))); + List nonExistentDomainClients = oAuth2Service.getOAuth2Clients("http", "non-existent-domain"); + Assert.assertTrue(nonExistentDomainClients.isEmpty()); - List oAuth2Clients = oAuth2Service.getOAuth2Clients(testDomainName); + List firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain"); + Assert.assertEquals(firstDomainHttpClients.size(), firstDomainHttpClients.size()); + firstGroupClientInfos.forEach(firstGroupClientInfo -> { + Assert.assertTrue( + firstDomainHttpClients.stream().anyMatch(clientInfo -> + clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) + && clientInfo.getName().equals(firstGroupClientInfo.getName())) + ); + }); - Set actualLabels = new HashSet<>(Arrays.asList(first.getLoginButtonLabel(), - second.getLoginButtonLabel())); + List firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain"); + Assert.assertTrue(firstDomainHttpsClients.isEmpty()); - Set foundLabels = oAuth2Clients.stream().map(OAuth2ClientInfo::getName).collect(Collectors.toSet()); - Assert.assertEquals(actualLabels, foundLabels); + List fourthDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "fourth-domain"); + Assert.assertEquals(fourthDomainHttpClients.size(), secondGroupClientInfos.size()); + secondGroupClientInfos.forEach(secondGroupClientInfo -> { + Assert.assertTrue( + fourthDomainHttpClients.stream().anyMatch(clientInfo -> + clientInfo.getIcon().equals(secondGroupClientInfo.getIcon()) + && clientInfo.getName().equals(secondGroupClientInfo.getName())) + ); + }); + List fourthDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "fourth-domain"); + Assert.assertEquals(fourthDomainHttpsClients.size(), secondGroupClientInfos.size()); + secondGroupClientInfos.forEach(secondGroupClientInfo -> { + Assert.assertTrue( + fourthDomainHttpsClients.stream().anyMatch(clientInfo -> + clientInfo.getIcon().equals(secondGroupClientInfo.getIcon()) + && clientInfo.getName().equals(secondGroupClientInfo.getName())) + ); + }); + + List secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain"); + Assert.assertEquals(secondDomainHttpClients.size(), firstGroupClientInfos.size() + secondGroupClientInfos.size()); + firstGroupClientInfos.forEach(firstGroupClientInfo -> { + Assert.assertTrue( + secondDomainHttpClients.stream().anyMatch(clientInfo -> + clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) + && clientInfo.getName().equals(firstGroupClientInfo.getName())) + ); + }); + secondGroupClientInfos.forEach(secondGroupClientInfo -> { + Assert.assertTrue( + secondDomainHttpClients.stream().anyMatch(clientInfo -> + clientInfo.getIcon().equals(secondGroupClientInfo.getIcon()) + && clientInfo.getName().equals(secondGroupClientInfo.getName())) + ); + }); + + List secondDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "second-domain"); + Assert.assertEquals(secondDomainHttpsClients.size(), firstGroupClientInfos.size() + thirdGroupClientInfos.size()); + firstGroupClientInfos.forEach(firstGroupClientInfo -> { + Assert.assertTrue( + secondDomainHttpsClients.stream().anyMatch(clientInfo -> + clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) + && clientInfo.getName().equals(firstGroupClientInfo.getName())) + ); + }); + thirdGroupClientInfos.forEach(thirdGroupClientInfo -> { + Assert.assertTrue( + secondDomainHttpsClients.stream().anyMatch(clientInfo -> + clientInfo.getIcon().equals(thirdGroupClientInfo.getIcon()) + && clientInfo.getName().equals(thirdGroupClientInfo.getName())) + ); + }); } @Test - public void testGetEmptyOAuth2Clients() { - String testDomainName = "test_domain"; - OAuth2ClientRegistration tenantClientRegistration = validClientRegistration(testDomainName); - OAuth2ClientRegistration sysAdminClientRegistration = validClientRegistration(testDomainName); - oAuth2Service.saveOAuth2Params(OAuth2Utils.toOAuth2Params(Collections.singletonList(tenantClientRegistration))); - oAuth2Service.saveOAuth2Params(OAuth2Utils.toOAuth2Params(Collections.singletonList(sysAdminClientRegistration))); - List oAuth2Clients = oAuth2Service.getOAuth2Clients("random-domain"); - Assert.assertTrue(oAuth2Clients.isEmpty()); + public void testGetDisabledOAuth2Clients() { + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), + DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), + DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto(), + validClientRegistrationDto() + )) + .build(), + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), + DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto() + )) + .build() + )); + + oAuth2Service.saveOAuth2Params(clientsParams); + + List secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain"); + Assert.assertEquals(5, secondDomainHttpClients.size()); + + clientsParams.setEnabled(false); + oAuth2Service.saveOAuth2Params(clientsParams); + + List secondDomainHttpDisabledClients = oAuth2Service.getOAuth2Clients("http", "second-domain"); + Assert.assertEquals(0, secondDomainHttpDisabledClients.size()); } @Test - public void testDeleteOAuth2ClientRegistration() { - OAuth2ClientRegistration first = validClientRegistration(); - OAuth2ClientRegistration second = validClientRegistration(); - - OAuth2ClientsParams savedFirstOAuth2Params = oAuth2Service.saveOAuth2Params( - OAuth2Utils.toOAuth2Params(Collections.singletonList(first))); - OAuth2ClientsParams savedSecondOAuth2Params = oAuth2Service.saveOAuth2Params( - OAuth2Utils.toOAuth2Params(Collections.singletonList(second))); - - OAuth2ClientRegistration savedFirstRegistration = toClientRegistrations(savedFirstOAuth2Params).get(0); - OAuth2ClientRegistration savedSecondRegistration = toClientRegistrations(savedSecondOAuth2Params).get(0); - - oAuth2Service.deleteClientRegistrationById(savedFirstRegistration.getId()); - List foundRegistrations = oAuth2Service.findAllClientRegistrations(); - Assert.assertEquals(1, foundRegistrations.size()); - Assert.assertEquals(savedSecondRegistration, foundRegistrations.get(0)); + public void testFindAllClientRegistrationInfos() { + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), + DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), + DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto(), + validClientRegistrationDto() + )) + .build(), + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), + DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto() + )) + .build(), + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), + DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto() + )) + .build() + )); + + oAuth2Service.saveOAuth2Params(clientsParams); + List foundClientRegistrationInfos = oAuth2Service.findAllClientRegistrationInfos(); + Assert.assertEquals(6, foundClientRegistrationInfos.size()); + clientsParams.getOAuth2DomainDtos().stream() + .flatMap(domainParams -> domainParams.getClientRegistrations().stream()) + .forEach(clientRegistrationDto -> + Assert.assertTrue( + foundClientRegistrationInfos.stream() + .anyMatch(clientRegistrationInfo -> clientRegistrationInfo.getClientId().equals(clientRegistrationDto.getClientId())) + ) + ); } @Test - public void testDeleteDomainOAuth2ClientRegistrations() { - oAuth2Service.saveOAuth2Params(OAuth2Utils.toOAuth2Params(Arrays.asList( - validClientRegistration("domain1"), - validClientRegistration("domain1"), - validClientRegistration("domain2") - ))); - oAuth2Service.saveOAuth2Params(OAuth2Utils.toOAuth2Params(Arrays.asList( - validClientRegistration("domain2") - ))); - Assert.assertEquals(4, oAuth2Service.findAllClientRegistrations().size()); - OAuth2ClientsParams oAuth2Params = oAuth2Service.findOAuth2Params(); - List clientRegistrations = toClientRegistrations(oAuth2Params); - Assert.assertEquals(2, oAuth2Params.getOAuth2DomainDtos().size()); - Assert.assertEquals(4, clientRegistrations.size()); - - oAuth2Service.deleteClientRegistrationsByDomain("domain1"); - Assert.assertEquals(2, oAuth2Service.findAllClientRegistrations().size()); - Assert.assertEquals(1, oAuth2Service.findOAuth2Params().getOAuth2DomainDtos().size()); - Assert.assertEquals(2, toClientRegistrations(oAuth2Service.findOAuth2Params()).size()); - } + public void testFindClientRegistrationById() { + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), + DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), + DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto(), + validClientRegistrationDto() + )) + .build(), + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), + DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto() + )) + .build(), + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), + DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto() + )) + .build() + )); - private OAuth2ClientRegistration validClientRegistration() { - return validClientRegistration(UUID.randomUUID().toString()); + oAuth2Service.saveOAuth2Params(clientsParams); + List clientRegistrationInfos = oAuth2Service.findAllClientRegistrationInfos(); + clientRegistrationInfos.forEach(clientRegistrationInfo -> { + OAuth2ClientRegistrationInfo foundClientRegistrationInfo = oAuth2Service.findClientRegistrationInfo(clientRegistrationInfo.getUuidId()); + Assert.assertEquals(clientRegistrationInfo, foundClientRegistrationInfo); + }); } - private OAuth2ClientRegistration validClientRegistration(String domainName) { - OAuth2ClientRegistration clientRegistration = new OAuth2ClientRegistration(); - clientRegistration.setDomainName(domainName); - clientRegistration.setMapperConfig( - OAuth2MapperConfig.builder() - .allowUserCreation(true) - .activateUser(true) - .type(MapperType.CUSTOM) - .custom( - OAuth2CustomMapperConfig.builder() - .url("UUID.randomUUID().toString()") - .build() - ) + private OAuth2ClientsParams createDefaultClientsParams() { + return new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), + DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), + DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto(), + validClientRegistrationDto(), + validClientRegistrationDto() + )) + .build(), + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), + DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto() + )) .build() - ); - clientRegistration.setClientId(UUID.randomUUID().toString()); - clientRegistration.setClientSecret(UUID.randomUUID().toString()); - clientRegistration.setAuthorizationUri(UUID.randomUUID().toString()); - clientRegistration.setAccessTokenUri(UUID.randomUUID().toString()); - clientRegistration.setRedirectUriTemplate(UUID.randomUUID().toString()); - clientRegistration.setScope(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString())); - clientRegistration.setUserInfoUri(UUID.randomUUID().toString()); - clientRegistration.setUserNameAttributeName(UUID.randomUUID().toString()); - clientRegistration.setJwkSetUri(UUID.randomUUID().toString()); - clientRegistration.setClientAuthenticationMethod(UUID.randomUUID().toString()); - clientRegistration.setLoginButtonLabel(UUID.randomUUID().toString()); - clientRegistration.setLoginButtonIcon(UUID.randomUUID().toString()); - clientRegistration.setAdditionalInfo(mapper.createObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString())); - return clientRegistration; + )); + } + + private ClientRegistrationDto validClientRegistrationDto() { + return ClientRegistrationDto.builder() + .clientId(UUID.randomUUID().toString()) + .clientSecret(UUID.randomUUID().toString()) + .authorizationUri(UUID.randomUUID().toString()) + .accessTokenUri(UUID.randomUUID().toString()) + .scope(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString())) + .userInfoUri(UUID.randomUUID().toString()) + .userNameAttributeName(UUID.randomUUID().toString()) + .jwkSetUri(UUID.randomUUID().toString()) + .clientAuthenticationMethod(UUID.randomUUID().toString()) + .loginButtonLabel(UUID.randomUUID().toString()) + .loginButtonIcon(UUID.randomUUID().toString()) + .additionalInfo(mapper.createObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString())) + .mapperConfig( + OAuth2MapperConfig.builder() + .allowUserCreation(true) + .activateUser(true) + .type(MapperType.CUSTOM) + .custom( + OAuth2CustomMapperConfig.builder() + .url(UUID.randomUUID().toString()) + .build() + ) + .build() + ) + .build(); } } From 6b41fb9c0eb726533a017552bf8ce8da0ae82535 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 1 Oct 2020 18:06:20 +0300 Subject: [PATCH 117/463] Refactored and used MiscUtils --- ...tomOAuth2AuthorizationRequestResolver.java | 12 +--- .../server/controller/OAuth2Controller.java | 3 +- .../thingsboard/server/utils/MiscUtils.java | 24 +++++-- .../thingsboard/server/utils/WebUtils.java | 70 ------------------- .../HybridClientRegistrationRepository.java | 2 + 5 files changed, 24 insertions(+), 87 deletions(-) delete mode 100644 application/src/main/java/org/thingsboard/server/utils/WebUtils.java diff --git a/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java b/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java index e8c59efde8..3d807fbdef 100644 --- a/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java +++ b/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java @@ -32,13 +32,12 @@ import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames import org.springframework.security.web.util.UrlUtils; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.stereotype.Service; -import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import org.thingsboard.server.dao.oauth2.OAuth2Configuration; -import org.thingsboard.server.utils.WebUtils; +import org.thingsboard.server.utils.MiscUtils; import javax.servlet.http.HttpServletRequest; import java.nio.charset.StandardCharsets; @@ -198,13 +197,8 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza private String getRedirectUri(HttpServletRequest request) { String loginProcessingUri = oauth2Configuration != null ? oauth2Configuration.getLoginProcessingUrl() : DEFAULT_LOGIN_PROCESSING_URI; - - String scheme = WebUtils.getScheme(request); - String host = WebUtils.getHost(request); - String port = WebUtils.getPort(request); - log.trace("Scheme - {}, host - {}, port - {}.", scheme, host, port); - String requestHost = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort(); - return requestHost + loginProcessingUri; + String baseUrl= MiscUtils.constructBaseUrl(request); + return baseUrl + loginProcessingUri; } /** diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index d94a706440..bb3a87ef92 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; import org.thingsboard.server.common.data.oauth2.SchemeType; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.utils.MiscUtils; import javax.servlet.http.HttpServletRequest; import java.util.List; @@ -37,7 +38,7 @@ public class OAuth2Controller extends BaseController { @ResponseBody public List getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { try { - return oAuth2Service.getOAuth2Clients(request.getScheme(), request.getServerName()); + return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainName(request)); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java b/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java index ed13ca603d..ad242c96e5 100644 --- a/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java +++ b/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java @@ -49,12 +49,27 @@ public class MiscUtils { } public static String constructBaseUrl(HttpServletRequest request) { - String scheme = request.getScheme(); + return String.format("%s://%s:%d", + getScheme(request), + getDomainName(request), + getPort(request)); + } + public static String getScheme(HttpServletRequest request){ + String scheme = request.getScheme(); String forwardedProto = request.getHeader("x-forwarded-proto"); if (forwardedProto != null) { scheme = forwardedProto; } + return scheme; + } + + public static String getDomainName(HttpServletRequest request){ + return request.getServerName(); + } + + public static int getPort(HttpServletRequest request){ + String forwardedProto = request.getHeader("x-forwarded-proto"); int serverPort = request.getServerPort(); if (request.getHeader("x-forwarded-port") != null) { @@ -72,11 +87,6 @@ public class MiscUtils { break; } } - - String baseUrl = String.format("%s://%s:%d", - scheme, - request.getServerName(), - serverPort); - return baseUrl; + return serverPort; } } diff --git a/application/src/main/java/org/thingsboard/server/utils/WebUtils.java b/application/src/main/java/org/thingsboard/server/utils/WebUtils.java deleted file mode 100644 index 31d61634af..0000000000 --- a/application/src/main/java/org/thingsboard/server/utils/WebUtils.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.utils; - -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang.StringUtils; - -import javax.servlet.http.HttpServletRequest; - -@Slf4j -public class WebUtils { - private static final String X_FORWARDED_HOST_HEADER_KEY = "x-forwarded-host"; - private static final String X_FORWARDED_PORT_HEADER_KEY = "x-forwarded-port"; - private static final String X_FORWARDED_PROTO_HEADER_KEY = "x-forwarded-proto"; - - public static String getHost(HttpServletRequest request) { - String forwardedHost = request.getHeader(X_FORWARDED_HOST_HEADER_KEY); - log.trace("Forwarded host - {}.", forwardedHost); - if (!StringUtils.isEmpty(forwardedHost)) { - if (forwardedHost.contains(":")) { - return forwardedHost.substring(0, forwardedHost.indexOf(":")); - } else { - return forwardedHost; - } - } else { - return request.getServerName(); - } - } - - public static String getScheme(HttpServletRequest request) { - String forwardedProto = request.getHeader(X_FORWARDED_PROTO_HEADER_KEY); - log.trace("Forwarded proto - {}.", forwardedProto); - if (!StringUtils.isEmpty(forwardedProto)) { - return forwardedProto; - } else { - return request.getServerName(); - } - } - - public static String getPort(HttpServletRequest request) { - String forwardedPort = request.getHeader(X_FORWARDED_PORT_HEADER_KEY); - log.trace("Forwarded port - {}.", forwardedPort); - if (!StringUtils.isEmpty(forwardedPort)) { - return forwardedPort; - } - String forwardedHost = request.getHeader(X_FORWARDED_HOST_HEADER_KEY); - if (!StringUtils.isEmpty(forwardedHost)) { - if (forwardedHost.contains(":")) { - return forwardedHost.substring(forwardedHost.indexOf(":")); - } else { - return "HTTP".equals(getScheme(request).toUpperCase()) ? - "80" : "443"; - } - } - return Integer.toString(request.getServerPort()); - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java index 93c4c47df0..4c9d44a5bc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java @@ -27,6 +27,7 @@ import java.util.UUID; @Component public class HybridClientRegistrationRepository implements ClientRegistrationRepository { + private static final String defaultRedirectUriTemplate = "{baseUrl}/login/oauth2/code/{registrationId}"; @Autowired private OAuth2Service oAuth2Service; @@ -52,6 +53,7 @@ public class HybridClientRegistrationRepository implements ClientRegistrationRep .userNameAttributeName(localClientRegistration.getUserNameAttributeName()) .jwkSetUri(localClientRegistration.getJwkSetUri()) .clientAuthenticationMethod(new ClientAuthenticationMethod(localClientRegistration.getClientAuthenticationMethod())) + .redirectUriTemplate(defaultRedirectUriTemplate) .build(); } } From 1f1cc84a92c46eb140114b277091ebb31ae540bf Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 1 Oct 2020 18:43:44 +0300 Subject: [PATCH 118/463] Changed getting redirectUri --- ...tomOAuth2AuthorizationRequestResolver.java | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java b/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java index 3d807fbdef..de34db0eaf 100644 --- a/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java +++ b/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java @@ -190,17 +190,33 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza uriVariables.put("action", action == null ? "" : action); - return UriComponentsBuilder.fromUriString(getRedirectUri(request)) + String redirectUri = getRedirectUri(request); + log.trace("Redirect URI - {}.", redirectUri); + + return UriComponentsBuilder.fromUriString(redirectUri) .buildAndExpand(uriVariables) .toUriString(); } private String getRedirectUri(HttpServletRequest request) { String loginProcessingUri = oauth2Configuration != null ? oauth2Configuration.getLoginProcessingUrl() : DEFAULT_LOGIN_PROCESSING_URI; - String baseUrl= MiscUtils.constructBaseUrl(request); + + String scheme = MiscUtils.getScheme(request); + String domainName = MiscUtils.getDomainName(request); + int port = MiscUtils.getPort(request); + String baseUrl = scheme + "://" + domainName; + if (needsPort(scheme, port)){ + baseUrl += ":" + port; + } return baseUrl + loginProcessingUri; } + private boolean needsPort(String scheme, int port) { + boolean isHttpDefault = "http".equals(scheme.toLowerCase()) && port == 80; + boolean isHttpsDefault = "https".equals(scheme.toLowerCase()) && port == 443; + return !isHttpDefault && !isHttpsDefault; + } + /** * Creates nonce and its hash for use in OpenID Connect 1.0 Authentication Requests. * From cd7a7ce4250ffd6b895cbbb4f9e5dbc982f0494d Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Fri, 2 Oct 2020 10:21:30 +0300 Subject: [PATCH 119/463] Renamed domainDtos to domainParams --- .../server/common/data/oauth2/OAuth2ClientsParams.java | 2 +- .../thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java | 6 +++--- .../server/dao/service/BaseOAuth2ServiceTest.java | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java index 3cb3b863fd..ee20021aca 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java @@ -26,5 +26,5 @@ import java.util.Set; @AllArgsConstructor public class OAuth2ClientsParams { private boolean enabled; - private Set oAuth2DomainDtos; + private Set domainsParams; } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index fab4c559d3..dd287f0755 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -72,7 +72,7 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se clientParamsValidator.accept(oauth2Params); clientRegistrationDao.deleteAll(); clientRegistrationInfoDao.deleteAll(); - oauth2Params.getOAuth2DomainDtos().forEach(domainParams -> { + oauth2Params.getDomainsParams().forEach(domainParams -> { domainParams.getClientRegistrations().forEach(clientRegistrationDto -> { OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo = OAuth2Utils.toClientRegistrationInfo(oauth2Params.isEnabled(), clientRegistrationDto); OAuth2ClientRegistrationInfo savedClientRegistrationInfo = clientRegistrationInfoDao.save(TenantId.SYS_TENANT_ID, oAuth2ClientRegistrationInfo); @@ -107,10 +107,10 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se private final Consumer clientParamsValidator = oauth2Params -> { if (oauth2Params == null - || oauth2Params.getOAuth2DomainDtos() == null) { + || oauth2Params.getDomainsParams() == null) { throw new DataValidationException("Domain params should be specified!"); } - for (OAuth2ClientsDomainParams domainParams : oauth2Params.getOAuth2DomainDtos()) { + for (OAuth2ClientsDomainParams domainParams : oauth2Params.getDomainsParams()) { if (domainParams.getDomainInfos() == null || domainParams.getDomainInfos().isEmpty()) { throw new DataValidationException("List of domain configuration should be specified!"); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java index fd5b5752cb..2daa0cb337 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -42,7 +42,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { public void after() { oAuth2Service.saveOAuth2Params(EMPTY_PARAMS); Assert.assertTrue(oAuth2Service.findAllClientRegistrationInfos().isEmpty()); - Assert.assertTrue(oAuth2Service.findOAuth2Params().getOAuth2DomainDtos().isEmpty()); + Assert.assertTrue(oAuth2Service.findOAuth2Params().getDomainsParams().isEmpty()); } @Test @@ -322,7 +322,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { oAuth2Service.saveOAuth2Params(clientsParams); List foundClientRegistrationInfos = oAuth2Service.findAllClientRegistrationInfos(); Assert.assertEquals(6, foundClientRegistrationInfos.size()); - clientsParams.getOAuth2DomainDtos().stream() + clientsParams.getDomainsParams().stream() .flatMap(domainParams -> domainParams.getClientRegistrations().stream()) .forEach(clientRegistrationDto -> Assert.assertTrue( From 1bb1f5be8b34a7f1955cc36929f264a3df4d2037 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Fri, 2 Oct 2020 10:41:55 +0300 Subject: [PATCH 120/463] Refactoring --- .../device/DeviceProvisionServiceImpl.java | 40 ++++++++++--------- .../server/dao/device/DeviceProfileDao.java | 2 +- .../sql/device/DeviceProfileRepository.java | 7 +--- .../dao/sql/device/JpaDeviceProfileDao.java | 4 +- 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 091e5099ce..2460ce0fde 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -27,8 +27,9 @@ import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; @@ -115,11 +116,9 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); } - DeviceProfile targetProfile = deviceProfileDao.findByProvisionDeviceKeyAndProvisionDeviceSecret(provisionRequestKey, provisionRequestSecret); + DeviceProfile targetProfile = deviceProfileDao.findByProvisionDeviceKey(provisionRequestKey); - if (targetProfile == null || - !(targetProfile.getProvisionType() != DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES || - targetProfile.getProvisionType() != DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES)) { + if (targetProfile == null) { return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); } @@ -127,23 +126,28 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { switch(targetProfile.getProvisionType()) { case ALLOW_CREATE_NEW_DEVICES: - if (targetDevice != null) { - log.warn("[{}] The device is present and could not be provisioned once more!", targetDevice.getName()); - notify(targetDevice, provisionRequest, DataConstants.PROVISION_FAILURE, false); - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); - } else { - return createDevice(provisionRequest, targetProfile); + if (((AllowCreateNewDevicesDeviceProfileProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration()).getProvisionDeviceSecret().equals(provisionRequestSecret)){ + if (targetDevice != null) { + log.warn("[{}] The device is present and could not be provisioned once more!", targetDevice.getName()); + notify(targetDevice, provisionRequest, DataConstants.PROVISION_FAILURE, false); + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); + } else { + return createDevice(provisionRequest, targetProfile); + } } + break; case CHECK_PRE_PROVISIONED_DEVICES: - if (targetDevice != null && targetDevice.getDeviceProfileId().equals(targetProfile.getId())){ - return processProvision(targetDevice, provisionRequest); - } else { - log.warn("[{}] Failed to find pre provisioned device!", provisionRequest.getDeviceName()); - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); + if (((CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration()).getProvisionDeviceSecret().equals(provisionRequestSecret)) { + if (targetDevice != null && targetDevice.getDeviceProfileId().equals(targetProfile.getId())) { + return processProvision(targetDevice, provisionRequest); + } else { + log.warn("[{}] Failed to find pre provisioned device!", provisionRequest.getDeviceName()); + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); + } } - default: - throw new RuntimeException("Strategy is not supported - " + targetProfile.getProvisionType().name()); + break; } + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); } private ListenableFuture processProvision(Device device, ProvisionRequest provisionRequest) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java index 74a251e3ff..9d11a34b2e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java @@ -38,7 +38,7 @@ public interface DeviceProfileDao extends Dao { DeviceProfileInfo findDefaultDeviceProfileInfo(TenantId tenantId); - DeviceProfile findByProvisionDeviceKeyAndProvisionDeviceSecret(String provisionDeviceKey, String provisionDeviceSecret); + DeviceProfile findByProvisionDeviceKey(String provisionDeviceKey); DeviceProfile findByName(TenantId tenantId, String profileName); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java index 43b6d68efe..0b0efdc8bd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java @@ -56,10 +56,5 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository'provisionConfiguration'->>'provisionDeviceSecret' = :provisionDeviceSecret", - nativeQuery = true) - DeviceProfileEntity findByProvisionDeviceKeyAndProvisionDeviceSecret(@Param("provisionDeviceKey") String provisionDeviceKey, - @Param("provisionDeviceSecret") String provisionDeviceSecret); + DeviceProfileEntity findByProvisionDeviceKey(@Param("provisionDeviceKey") String provisionDeviceKey); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java index 2d7c011a87..d47506b90e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java @@ -81,8 +81,8 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao Date: Sun, 4 Oct 2020 00:05:52 +0300 Subject: [PATCH 121/463] UI: Added device provision to device profile --- .../home/components/home-components.module.ts | 7 +- .../add-device-profile-dialog.component.html | 10 +- .../add-device-profile-dialog.component.ts | 20 ++- .../device-profile-data.component.html | 10 ++ .../profile/device-profile-data.component.ts | 4 +- ...ile-provision-configuration.component.html | 46 ++++++ ...ofile-provision-configuration.component.ts | 140 ++++++++++++++++++ .../profile/device-profile.component.ts | 10 +- ui-ngx/src/app/shared/models/device.models.ts | 24 +++ .../assets/locale/locale.constant-en_US.json | 12 +- 10 files changed, 275 insertions(+), 8 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index d583369016..5209dd3602 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -107,6 +107,7 @@ import { AlarmRuleKeyFiltersDialogComponent } from './profile/alarm/alarm-rule-k import { FilterTextComponent } from './filter/filter-text.component'; import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component'; import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component'; +import { DeviceProfileProvisionConfigurationComponent } from "./profile/device-profile-provision-configuration.component"; @NgModule({ declarations: @@ -196,7 +197,8 @@ import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomp DeviceProfileComponent, DeviceProfileDialogComponent, AddDeviceProfileDialogComponent, - RuleChainAutocompleteComponent + RuleChainAutocompleteComponent, + DeviceProfileProvisionConfigurationComponent ], imports: [ CommonModule, @@ -275,7 +277,8 @@ import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomp DeviceProfileComponent, DeviceProfileDialogComponent, AddDeviceProfileDialogComponent, - RuleChainAutocompleteComponent + RuleChainAutocompleteComponent, + DeviceProfileProvisionConfigurationComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html index d7f119a3b7..bda26153d1 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html @@ -93,6 +93,14 @@ + +

+ {{'device-profile.device-provisioning' | translate }} + + +
+
@@ -107,7 +115,7 @@ + (click)="nextStep()">{{ (selectedIndex === 3 ? 'action.add' : 'action.continue') | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts index 476c69488f..42edc3c613 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts @@ -77,6 +77,8 @@ export class AddDeviceProfileDialogComponent extends alarmRulesFormGroup: FormGroup; + provisionConfigurationFormGroup: FormGroup; + constructor(protected store: Store, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: AddDeviceProfileDialogData, @@ -111,6 +113,12 @@ export class AddDeviceProfileDialogComponent extends alarms: [null] } ); + + this.provisionConfigurationFormGroup = this.fb.group( + { + provisionConfiguration: [null] + } + ) } private deviceProfileTransportTypeChanged() { @@ -131,7 +139,7 @@ export class AddDeviceProfileDialogComponent extends } nextStep() { - if (this.selectedIndex < 2) { + if (this.selectedIndex < 3) { this.addDeviceProfileStepper.next(); } else { this.add(); @@ -146,6 +154,8 @@ export class AddDeviceProfileDialogComponent extends return this.transportConfigFormGroup; case 2: return this.alarmRulesFormGroup; + case 3: + return this.provisionConfigurationFormGroup; } } @@ -154,11 +164,17 @@ export class AddDeviceProfileDialogComponent extends name: this.deviceProfileDetailsFormGroup.get('name').value, type: this.deviceProfileDetailsFormGroup.get('type').value, transportType: this.transportConfigFormGroup.get('transportType').value, + provisionType: this.provisionConfigurationFormGroup.get('provisionConfiguration').value.type, + provisionDeviceKey: this.provisionConfigurationFormGroup.get('provisionConfiguration').value.provisionDeviceKey, description: this.deviceProfileDetailsFormGroup.get('description').value, profileData: { configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT), transportConfiguration: this.transportConfigFormGroup.get('transportConfiguration').value, - alarms: this.alarmRulesFormGroup.get('alarms').value + alarms: this.alarmRulesFormGroup.get('alarms').value, + provisionConfiguration: { + type: this.provisionConfigurationFormGroup.get('provisionConfiguration').value.type, + provisionDeviceSecret: this.provisionConfigurationFormGroup.get('provisionConfiguration').value.provisionDeviceSecret + } } }; if (this.deviceProfileDetailsFormGroup.get('defaultRuleChainId').value) { diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html index daae838285..eb4aef68eb 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html @@ -51,5 +51,15 @@ formControlName="alarms">
+ + + +
device-profile.device-provisioning
+
+
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.ts index 7d7fc55057..5b7fd9b998 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.ts @@ -72,7 +72,8 @@ export class DeviceProfileDataComponent implements ControlValueAccessor, OnInit this.deviceProfileDataFormGroup = this.fb.group({ configuration: [null, Validators.required], transportConfiguration: [null, Validators.required], - alarms: [null] + alarms: [null], + provisionConfiguration: [null] }); this.deviceProfileDataFormGroup.valueChanges.subscribe(() => { this.updateModel(); @@ -98,6 +99,7 @@ export class DeviceProfileDataComponent implements ControlValueAccessor, OnInit this.deviceProfileDataFormGroup.patchValue({configuration: value?.configuration}, {emitEvent: false}); this.deviceProfileDataFormGroup.patchValue({transportConfiguration: value?.transportConfiguration}, {emitEvent: false}); this.deviceProfileDataFormGroup.patchValue({alarms: value?.alarms}, {emitEvent: false}); + this.deviceProfileDataFormGroup.patchValue({provisionConfiguration: value?.provisionConfiguration}, {emitEvent: false}); } private updateModel() { diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html new file mode 100644 index 0000000000..46d15905d1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html @@ -0,0 +1,46 @@ + +
+ + device-profile.provision-strategy + + + {{deviceProvisionTypeTranslateMap.get(type) | translate}} + + + + {{ 'device-profile.provision-strategy-required' | translate }} + + +
+ + device-profile.provision-device-secret + + + {{ 'device-profile.provision-device-secret-required' | translate }} + + + + device-profile.provision-device-key + + + {{ 'device-profile.provision-device-key-required' | translate }} + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts new file mode 100644 index 0000000000..f37d8bb2c6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -0,0 +1,140 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit } from "@angular/core"; +import { + ControlValueAccessor, + FormBuilder, + FormControl, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + Validators +} from "@angular/forms"; +import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { + DeviceProvisionConfiguration, + DeviceProvisionType, + deviceProvisionTypeTranslationMap +} from "@shared/models/device.models"; +import { isDefinedAndNotNull } from "@core/utils"; + +@Component({ + selector: 'tb-device-profile-provision-configuration', + templateUrl: './device-profile-provision-configuration.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DeviceProfileProvisionConfigurationComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => DeviceProfileProvisionConfigurationComponent), + multi: true, + } + ] +}) +export class DeviceProfileProvisionConfigurationComponent implements ControlValueAccessor, OnInit, Validator { + + provisionConfigurationFormGroup: FormGroup; + + deviceProvisionType = DeviceProvisionType; + deviceProvisionTypes = Object.keys(DeviceProvisionType); + deviceProvisionTypeTranslateMap = deviceProvisionTypeTranslationMap; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + private propagateChange = (v: any) => { }; + + constructor(private fb: FormBuilder) { + } + + ngOnInit(): void { + this.provisionConfigurationFormGroup = this.fb.group({ + type: [DeviceProvisionType.DISABLED, Validators.required], + provisionDeviceSecret: [{value: null, disabled: true}, Validators.required], + provisionDeviceKey: [{value: null, disabled: true}, Validators.required] + }); + this.provisionConfigurationFormGroup.get('type').valueChanges.subscribe((type) => { + if(type === DeviceProvisionType.DISABLED) { + this.provisionConfigurationFormGroup.get('provisionDeviceSecret').disable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('provisionDeviceSecret').patchValue(null,{emitEvent: false}); + this.provisionConfigurationFormGroup.get('provisionDeviceKey').disable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('provisionDeviceKey').patchValue(null); + } else { + this.provisionConfigurationFormGroup.get('provisionDeviceSecret').enable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('provisionDeviceKey').enable({emitEvent: false}); + } + }); + this.provisionConfigurationFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + writeValue(value: DeviceProvisionConfiguration | null): void { + if(isDefinedAndNotNull(value)){ + this.provisionConfigurationFormGroup.patchValue(value, {emitEvent: false}); + } else { + this.provisionConfigurationFormGroup.patchValue({type: DeviceProvisionType.DISABLED}); + } + } + + setDisabledState(isDisabled: boolean){ + this.disabled = isDisabled; + if(this.disabled){ + this.provisionConfigurationFormGroup.disable(); + } else { + this.provisionConfigurationFormGroup.enable({emitEvent: false}); + } + } + + validate(c: FormControl): ValidationErrors | null { + return (this.provisionConfigurationFormGroup.valid) ? null : { + provisionConfiguration: { + valid: false, + }, + }; + } + + private updateModel(): void { + let deviceProvisionConfiguration: DeviceProvisionConfiguration = null; + if (this.provisionConfigurationFormGroup.valid) { + deviceProvisionConfiguration = this.provisionConfigurationFormGroup.getRawValue(); + } + this.propagateChange(deviceProvisionConfiguration); + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts index e3c441c8a2..9c5fc9b5a5 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts @@ -126,6 +126,10 @@ export class DeviceProfileComponent extends EntityComponent { } updateForm(entity: DeviceProfile) { + if(entity?.profileData?.provisionConfiguration) { + entity.profileData.provisionConfiguration.provisionDeviceKey = entity?.provisionDeviceKey; + } + this.entityForm.patchValue({name: entity.name}); this.entityForm.patchValue({type: entity.type}, {emitEvent: false}); this.entityForm.patchValue({transportType: entity.transportType}, {emitEvent: false}); @@ -138,7 +142,11 @@ export class DeviceProfileComponent extends EntityComponent { if (formValue.defaultRuleChainId) { formValue.defaultRuleChainId = new RuleChainId(formValue.defaultRuleChainId); } - return formValue; + formValue.provisionType = formValue.profileData.provisionConfiguration.type; + formValue.provisionDeviceKey = formValue.profileData.provisionConfiguration.provisionDeviceKey; + delete formValue.profileData.provisionConfiguration.provisionDeviceKey; + + return super.prepareFormValue(formValue); } onDeviceProfileIdCopied(event) { diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index fe92268b80..a0d91c908e 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -36,6 +36,12 @@ export enum DeviceTransportType { LWM2M = 'LWM2M' } +export enum DeviceProvisionType { + DISABLED = 'DISABLED', + ALLOW_CREATE_NEW_DEVICES = 'ALLOW_CREATE_NEW_DEVICES', + CHECK_PRE_PROVISIONED_DEVICES = 'CHECK_PRE_PROVISIONED_DEVICES' +} + export interface DeviceConfigurationFormInfo { hasProfileConfiguration: boolean; hasDeviceConfiguration: boolean; @@ -67,6 +73,15 @@ export const deviceTransportTypeTranslationMap = new Map( + [ + [DeviceProvisionType.DISABLED, 'device-profile.provision-strategy-disabled'], + [DeviceProvisionType.ALLOW_CREATE_NEW_DEVICES, 'device-profile.provision-strategy-created-new'], + [DeviceProvisionType.CHECK_PRE_PROVISIONED_DEVICES, 'device-profile.provision-strategy-check-pre-provisioned'] + ] +) + export const deviceTransportTypeConfigurationInfoMap = new Map( [ [ @@ -125,6 +140,12 @@ export interface DeviceProfileTransportConfiguration extends DeviceProfileTransp type: DeviceTransportType; } +export interface DeviceProvisionConfiguration { + type: DeviceProvisionType; + provisionDeviceSecret?: string; + provisionDeviceKey?: string; +} + export function createDeviceProfileConfiguration(type: DeviceProfileType): DeviceProfileConfiguration { let configuration: DeviceProfileConfiguration = null; if (type) { @@ -220,6 +241,7 @@ export interface DeviceProfileData { configuration: DeviceProfileConfiguration; transportConfiguration: DeviceProfileTransportConfiguration; alarms?: Array; + provisionConfiguration?: DeviceProvisionConfiguration; } export interface DeviceProfile extends BaseData { @@ -229,6 +251,8 @@ export interface DeviceProfile extends BaseData { default?: boolean; type: DeviceProfileType; transportType: DeviceTransportType; + provisionType: DeviceProvisionType; + provisionDeviceKey?: string; defaultRuleChainId?: RuleChainId; profileData: DeviceProfileData; } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index c98a2d5c17..23d2fba8df 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -840,7 +840,17 @@ "alarm-details": "Alarm details", "alarm-rule-condition": "Alarm rule condition", "enter-alarm-rule-condition-prompt": "Please add alarm rule condition", - "edit-alarm-rule-condition": "Edit alarm rule condition" + "edit-alarm-rule-condition": "Edit alarm rule condition", + "device-provisioning": "Device provisioning", + "provision-strategy": "Provision strategy", + "provision-strategy-required": "Provision strategy is required.", + "provision-strategy-disabled": "Disabled", + "provision-strategy-created-new": "Allow create new devices", + "provision-strategy-check-pre-provisioned": "Check pre provisioned devices", + "provision-device-key": "Provision device key", + "provision-device-key-required": "Provision device key is required.", + "provision-device-secret": "Provision device secret", + "provision-device-secret-required": "Provision device secret is required." }, "dialog": { "close": "Close dialog" From b5969eacc865b379c3522d80e994a3792d8ab660 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 5 Oct 2020 10:39:04 +0300 Subject: [PATCH 122/463] Improvements --- .../server/common/data/device/profile/MqttTopics.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java index 4abbedbee7..23c173ae97 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java @@ -34,8 +34,8 @@ public class MqttTopics { private static final String ATTRIBUTES_RESPONSE = ATTRIBUTES + RESPONSE; private static final String ATTRIBUTES_REQUEST = ATTRIBUTES + REQUEST; - private static final String DEVICE_RPC_RESPONSE = RPC + RESPONSE; - private static final String DEVICE_RPC_REQUEST = RPC + REQUEST; + private static final String DEVICE_RPC_RESPONSE = RPC + RESPONSE + "/"; + private static final String DEVICE_RPC_REQUEST = RPC + REQUEST + "/"; private static final String DEVICE_ATTRIBUTES_RESPONSE = ATTRIBUTES_RESPONSE + "/"; private static final String DEVICE_ATTRIBUTES_REQUEST = ATTRIBUTES_REQUEST + "/"; From 2020da577d54205c91d963996751d83b9384785c Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 5 Oct 2020 10:40:47 +0300 Subject: [PATCH 123/463] Changed provision device key/secret fields position in UI --- ...-profile-provision-configuration.component.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html index 46d15905d1..2e9299f0cf 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html @@ -28,13 +28,6 @@
- - device-profile.provision-device-secret - - - {{ 'device-profile.provision-device-secret-required' | translate }} - - device-profile.provision-device-key @@ -42,5 +35,12 @@ {{ 'device-profile.provision-device-key-required' | translate }} + + device-profile.provision-device-secret + + + {{ 'device-profile.provision-device-secret-required' | translate }} + +
From fe00a9bd45842c425b2ffe05d1ff61360b1dd849 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 5 Oct 2020 11:09:41 +0300 Subject: [PATCH 124/463] UI: Added support multiple domains is OAuth2 settings --- ui-ngx/src/app/core/http/admin.service.ts | 19 +- .../modules/home/pages/admin/admin.module.ts | 4 +- .../edit-redirect-uri-panel.component.html | 49 -- .../edit-redirect-uri-panel.component.scss | 18 - .../edit-redirect-uri-panel.component.ts | 76 -- .../admin/oauth2-settings.component.html | 720 ++++++++++-------- .../admin/oauth2-settings.component.scss | 22 +- .../pages/admin/oauth2-settings.component.ts | 283 +++---- .../src/app/shared/models/settings.models.ts | 47 +- .../assets/locale/locale.constant-en_US.json | 20 +- 10 files changed, 614 insertions(+), 644 deletions(-) delete mode 100644 ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.html delete mode 100644 ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.scss delete mode 100644 ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.ts diff --git a/ui-ngx/src/app/core/http/admin.service.ts b/ui-ngx/src/app/core/http/admin.service.ts index d11a8f373e..9423392773 100644 --- a/ui-ngx/src/app/core/http/admin.service.ts +++ b/ui-ngx/src/app/core/http/admin.service.ts @@ -60,27 +60,16 @@ export class AdminService { defaultHttpOptionsFromConfig(config)); } - public getOAuth2Settings(config?: RequestConfig): Observable> { - return this.http.get>(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config)); + public getOAuth2Settings(config?: RequestConfig): Observable { + return this.http.get(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config)); } public getOAuth2Template(config?: RequestConfig): Observable> { return this.http.get>(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config)); } - public saveOAuth2Settings(OAuth2Setting: OAuth2Settings[], - config?: RequestConfig): Observable> { - return this.http.post>('/api/oauth2/config', OAuth2Setting, - defaultHttpOptionsFromConfig(config)); - } - - public deleteOAuth2Domain(OAuth2Domain: string, config?: RequestConfig) { - return this.http.delete(`/api/oauth2/config/domain/${OAuth2Domain}`, - defaultHttpOptionsFromConfig(config)); - } - - public deleteOAuthCclientRegistrationId(clientRegistrationId: string, config?: RequestConfig) { - return this.http.delete(`/api/oauth2/config/${clientRegistrationId}`, + public saveOAuth2Settings(OAuth2Setting: OAuth2Settings, config?: RequestConfig): Observable { + return this.http.post('/api/oauth2/config', OAuth2Setting, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts index 189550d1c2..73fef6a32f 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts @@ -24,7 +24,6 @@ import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-sett import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component'; import { HomeComponentsModule } from '@modules/home/components/home-components.module'; import { OAuth2SettingsComponent } from '@modules/home/pages/admin/oauth2-settings.component'; -import { EditRedirectUriPanelComponent } from './edit-redirect-uri-panel.component'; @NgModule({ declarations: @@ -32,8 +31,7 @@ import { EditRedirectUriPanelComponent } from './edit-redirect-uri-panel.compone GeneralSettingsComponent, MailServerComponent, SecuritySettingsComponent, - OAuth2SettingsComponent, - EditRedirectUriPanelComponent + OAuth2SettingsComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.html b/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.html deleted file mode 100644 index 6f4efc061c..0000000000 --- a/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.html +++ /dev/null @@ -1,49 +0,0 @@ - -
-
- - admin.oauth2.redirect-uri-template - - - {{ 'admin.oauth2.redirect-uri-template-required' | translate }} - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - -
-
- - - -
-
diff --git a/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.scss b/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.scss deleted file mode 100644 index 43b16cf165..0000000000 --- a/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.scss +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -:host { - background-color: #f9f9f9; -} diff --git a/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.ts b/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.ts deleted file mode 100644 index 44f6bff0f3..0000000000 --- a/ui-ngx/src/app/modules/home/pages/admin/edit-redirect-uri-panel.component.ts +++ /dev/null @@ -1,76 +0,0 @@ -/// -/// Copyright © 2016-2020 The Thingsboard Authors -/// -/// Licensed under the Apache License, Version 2.0 (the "License"); -/// you may not use this file except in compliance with the License. -/// You may obtain a copy of the License at -/// -/// http://www.apache.org/licenses/LICENSE-2.0 -/// -/// Unless required by applicable law or agreed to in writing, software -/// distributed under the License is distributed on an "AS IS" BASIS, -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -/// See the License for the specific language governing permissions and -/// limitations under the License. -/// - -import { Component, Inject, InjectionToken, OnInit, SkipSelf } from '@angular/core'; -import { ErrorStateMatcher } from '@angular/material/core'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; -import { PageComponent } from '@shared/components/page.component'; -import { OverlayRef } from '@angular/cdk/overlay'; - -export const EDIT_REDIRECT_URI_PANEL_DATA = new InjectionToken('EditRedirectUriPanelData'); - -export interface EditRedirectUriPanelData { - redirectURI: any; -} - -@Component({ - selector: 'tb-edit-redirect-uri-panel', - templateUrl: './edit-redirect-uri-panel.component.html', - providers: [{provide: ErrorStateMatcher, useExisting: EditRedirectUriPanelComponent}], - styleUrls: ['./edit-redirect-uri-panel.component.scss'] -}) -export class EditRedirectUriPanelComponent extends PageComponent implements OnInit, ErrorStateMatcher { - - private URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.?+=&%@\-/]*)?$/; - - redirectURIFormGroup: FormGroup; - - result: any = null; - - submitted = false; - - constructor(protected store: Store, - @Inject(EDIT_REDIRECT_URI_PANEL_DATA) public data: EditRedirectUriPanelData, - @SkipSelf() private errorStateMatcher: ErrorStateMatcher, - public overlayRef: OverlayRef, - public fb: FormBuilder) { - super(store); - } - - ngOnInit(): void { - this.redirectURIFormGroup = this.fb.group({ - value: [this.data.redirectURI, [Validators.required, Validators.pattern(this.URL_REGEXP)]] - }); - } - - isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { - const originalErrorState = this.errorStateMatcher.isErrorState(control, form); - const customErrorState = !!(control && control.invalid && this.submitted); - return originalErrorState || customErrorState; - } - - cancel(): void { - this.overlayRef.dispose(); - } - - update(): void { - this.submitted = true; - this.result = this.redirectURIFormGroup.get('value').value; - this.overlayRef.dispose(); - } -} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html index 9a0d9a1858..6e0048612c 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -30,23 +30,20 @@
- -
- - - - - - {{ domain.get('domainName').value ? domain.get('domainName').value : ("admin.oauth2.new-domain" | translate) }} - - - + + {{ 'admin.oauth2.enable' | translate }} + +
+ +
+ + + + + + {{ domainListTittle(domain) }} + + - - - - - - admin.domain-name - - - {{ 'admin.error-verification-url' | translate }} - - - {{ 'admin.domain-name-unique' | translate }} - - - - -
- - - - {{ getProviderName(registration) }} - - - - - - - -
-
- - admin.oauth2.login-provider - - - {{ provider | uppercase }} + + + + + +
+
+
+
+ + admin.oauth2.protocol + + + {{ domainSchemaTranslations.get(protocol) | translate | uppercase }} -
-
-
- admin.oauth2.client-id - - - {{ 'admin.oauth2.client-id-required' | translate }} + admin.domain-name + + + {{ 'admin.error-verification-url' | translate }} + + + {{ 'admin.domain-name-unique' | translate }} +
+
- admin.oauth2.client-secret - - - {{ 'admin.oauth2.client-secret-required' | translate }} - + admin.oauth2.redirect-uri-template + + + + + + + +
- - - - {{ 'admin.oauth2.custom-setting' | translate }} - - - - - -
- - admin.oauth2.access-token-uri - - - - {{ 'admin.oauth2.access-token-uri-required' | translate }} - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - +
- - admin.oauth2.authorization-uri - - - - {{ 'admin.oauth2.authorization-uri-required' | translate }} - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - -
- -
- - admin.oauth2.jwk-set-uri - - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - +
+ +
+
+
+
+ +
+
+ + +
+ + + + {{ getProviderName(registration) }} + + + + + + + +
+
+ + admin.oauth2.login-provider + + + {{ provider | uppercase }} + + + +
+
+ +
+ + admin.oauth2.client-id + + + {{ 'admin.oauth2.client-id-required' | translate }} + + + + + admin.oauth2.client-secret + + + {{ 'admin.oauth2.client-secret-required' | translate }} + + +
+ + + + + {{ 'admin.oauth2.custom-setting' | translate }} + + + + + +
+ + admin.oauth2.access-token-uri + + + + {{ 'admin.oauth2.access-token-uri-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + + - - admin.oauth2.user-info-uri - - - - {{ 'admin.oauth2.user-info-uri-required' | translate }} - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - -
- - - admin.oauth2.client-authentication-method - - - {{ clientAuthenticationMethod | uppercase }} - - - - -
- - admin.oauth2.login-button-label - - - {{ 'admin.oauth2.login-button-label-required' | translate }} - - + + admin.oauth2.authorization-uri + + + + {{ 'admin.oauth2.authorization-uri-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + + +
- - admin.oauth2.login-button-icon - - -
- -
-
- - {{ 'admin.oauth2.allow-user-creation' | translate }} - - - {{ 'admin.oauth2.activate-user' | translate }} - +
+ + admin.oauth2.jwk-set-uri + + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + + + + + admin.oauth2.user-info-uri + + + + {{ 'admin.oauth2.user-info-uri-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + +
-
- - - admin.oauth2.scope - - - {{scope}} - cancel - - - - - - - - - admin.oauth2.user-name-attribute-name - - - {{ 'admin.oauth2.user-name-attribute-name-required' | translate }} - - - -
+ - admin.oauth2.type - - - {{ converterTypeExternalUser }} + admin.oauth2.client-authentication-method + + + {{ clientAuthenticationMethod | uppercase }} -
- - admin.oauth2.email-attribute-key - - - {{ 'admin.oauth2.email-attribute-key-required' | translate }} +
+ + admin.oauth2.login-button-label + + + {{ 'admin.oauth2.login-button-label-required' | translate }} -
- - admin.oauth2.first-name-attribute-key - - + + admin.oauth2.login-button-icon + + +
- - admin.oauth2.last-name-attribute-key - - +
+
+ + {{ 'admin.oauth2.allow-user-creation' | translate }} + + + {{ 'admin.oauth2.activate-user' | translate }} +
+
-
- - admin.oauth2.tenant-name-strategy - - - {{ tenantNameStrategy }} - - - + + admin.oauth2.scope + + + {{scope}} + cancel + + + + + {{ 'admin.oauth2.scope-required' | translate }} + + - - admin.oauth2.tenant-name-pattern - - - {{ 'admin.oauth2.tenant-name-pattern-required' | translate }} - - -
+ + + + admin.oauth2.user-name-attribute-name + + + {{ 'admin.oauth2.user-name-attribute-name-required' | translate }} + + +
- admin.oauth2.customer-name-pattern - + admin.oauth2.type + + + {{ converterTypeExternalUser }} + + -
- - admin.oauth2.default-dashboard-name - +
+ + admin.oauth2.email-attribute-key + + + {{ 'admin.oauth2.email-attribute-key-required' | translate }} + - - {{ 'admin.oauth2.always-fullscreen' | translate}} - -
-
+
+ + admin.oauth2.first-name-attribute-key + + + + + admin.oauth2.last-name-attribute-key + + +
+ +
+ + admin.oauth2.tenant-name-strategy + + + {{ tenantNameStrategy }} + + + + + + admin.oauth2.tenant-name-pattern + + + {{ 'admin.oauth2.tenant-name-pattern-required' | translate }} + + +
-
- - admin.oauth2.url - - - {{ 'admin.oauth2.url-required' | translate }} - - - {{ 'admin.oauth2.url-pattern' | translate }} - - - -
- common.username - + admin.oauth2.customer-name-pattern + - - common.password - +
+ + admin.oauth2.default-dashboard-name + + + + + {{ 'admin.oauth2.always-fullscreen' | translate}} + +
+
+ +
+ + admin.oauth2.url + + + {{ 'admin.oauth2.url-required' | translate }} + + + {{ 'admin.oauth2.url-pattern' | translate }} + -
+ +
+ + common.username + + + + + common.password + + +
+
- -
- - - - - - - + + + + + + + + +
+
+ +
+
- - -
- -
- - - - - - - - -
- - -
+ + + + + + + +
+
+ + +
diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss index ae7e94a5ac..0ad644f61b 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss @@ -15,11 +15,11 @@ */ :host{ .checkbox-row { - margin-top: 16px; + margin-top: 1em; } .registration-card{ - margin-bottom: 8px; + margin-bottom: 0.5em; border: 1px solid rgba(0, 0, 0, 0.2); .custom-settings{ @@ -31,7 +31,15 @@ } .container{ - margin-bottom: 16px; + margin-bottom: 1em; + + .tb-highlight{ + margin: 0; + } + + .tb-hint{ + padding-bottom: 0; + } } } @@ -39,7 +47,7 @@ .registration-card{ .custom-settings{ .mat-expansion-panel-body{ - padding: 0 2px 16px; + padding: 0 2px 1em; } .mat-tab-label{ text-transform: none; @@ -49,4 +57,10 @@ } } } + .domains-list{ + margin-bottom: 1.5em; + .mat-form-field-suffix .mat-icon-button .mat-icon{ + font-size: 24px; + } + } } diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index fc0729c3e1..b2ffc2bb6e 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -14,12 +14,16 @@ /// limitations under the License. /// -import { ChangeDetectorRef, Component, Inject, OnDestroy, OnInit, ViewContainerRef } from '@angular/core'; +import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ClientAuthenticationMethod, ClientProviderTemplated, ClientRegistration, + DomainInfo, + DomainSchema, + domainSchemaTranslations, + DomainsParam, MapperConfig, MapperConfigBasic, MapperConfigCustom, @@ -38,13 +42,7 @@ import { WINDOW } from '@core/services/window.service'; import { forkJoin, Subscription } from 'rxjs'; import { DialogService } from '@core/services/dialog.service'; import { TranslateService } from '@ngx-translate/core'; -import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; -import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; -import { - EDIT_REDIRECT_URI_PANEL_DATA, - EditRedirectUriPanelComponent, - EditRedirectUriPanelData -} from './edit-redirect-uri-panel.component'; +import { isDefined } from '@core/utils'; @Component({ selector: 'tb-oauth2-settings', @@ -60,15 +58,15 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha additionalInfo: { providerName: 'Custom' }, - clientAuthenticationMethod: 'POST', + clientAuthenticationMethod: ClientAuthenticationMethod.POST, userNameAttributeName: 'email', mapperConfig: { allowUserCreation: true, activateUser: false, - type: 'BASIC', + type: MapperConfigType.BASIC, basic: { emailAttributeKey: 'email', - tenantNameStrategy: 'DOMAIN', + tenantNameStrategy: TenantNameStrategy.DOMAIN, alwaysFullScreen: false } } @@ -77,19 +75,18 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha readonly separatorKeysCodes: number[] = [ENTER, COMMA]; oauth2SettingsForm: FormGroup; - oauth2Settings: OAuth2Settings[]; + oauth2Settings: OAuth2Settings; - clientAuthenticationMethods: ClientAuthenticationMethod[] = ['BASIC', 'POST']; - converterTypesExternalUser: MapperConfigType[] = ['BASIC', 'CUSTOM']; - tenantNameStrategies: TenantNameStrategy[] = ['DOMAIN', 'EMAIL', 'CUSTOM']; + clientAuthenticationMethods = Object.keys(ClientAuthenticationMethod); + converterTypesExternalUser = Object.keys(MapperConfigType); + tenantNameStrategies = Object.keys(TenantNameStrategy); + protocols = Object.keys(DomainSchema); + domainSchemaTranslations = domainSchemaTranslations; templateProvider = ['Custom']; constructor(protected store: Store, private adminService: AdminService, - private overlay: Overlay, - private viewContainerRef: ViewContainerRef, - private cd: ChangeDetectorRef, private fb: FormBuilder, private dialogService: DialogService, private translate: TranslateService, @@ -124,8 +121,8 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha this.templateProvider.sort(); } - get clientDomains(): FormArray { - return this.oauth2SettingsForm.get('clientDomains') as FormArray; + get domainsParams(): FormArray { + return this.oauth2SettingsForm.get('domainsParams') as FormArray; } private formBasicGroup(mapperConfigBasic?: MapperConfigBasic): FormGroup { @@ -139,7 +136,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha emailAttributeKey: [mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required], firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : ''], lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : ''], - tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : 'DOMAIN'], + tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : TenantNameStrategy.DOMAIN], tenantNamePattern: [tenantNamePattern, Validators.required], customerNamePattern: [mapperConfigBasic?.customerNamePattern ? mapperConfigBasic.customerNamePattern : null], defaultDashboardName: [mapperConfigBasic?.defaultDashboardName ? mapperConfigBasic.defaultDashboardName : null], @@ -167,69 +164,78 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha private buildOAuth2SettingsForm(): void { this.oauth2SettingsForm = this.fb.group({ - clientDomains: this.fb.array([]) + domainsParams: this.fb.array([]), + enabled: [false] }); } - private initOAuth2Settings(oauth2Settings: OAuth2Settings[]): void { + private initOAuth2Settings(oauth2Settings: OAuth2Settings): void { if (oauth2Settings) { - oauth2Settings.forEach((domain) => { - this.clientDomains.push(this.buildSettingsDomain(domain)); + this.oauth2SettingsForm.patchValue({enabled: oauth2Settings.enabled}, {emitEvent: false}); + oauth2Settings.domainsParams.forEach((domain) => { + this.domainsParams.push(this.buildDomainsForm(domain)); }); } } private uniqueDomainValidator(control: AbstractControl): { [key: string]: boolean } | null { - if (control.root.value.clientDomains?.length > 1) { - const listDomainName = []; - control.root.value.clientDomains.forEach((domain) => { - listDomainName.push(domain.domainName); - }); - if (listDomainName.indexOf(control.value) > -1) { + if (control.parent?.parent?.value) { + const domain = control.value; + const listProtocols = control.parent.parent.value + .filter((domainInfo) => domainInfo.name === domain) + .map((domainInfo) => domainInfo.scheme); + if (listProtocols.length > 1 && listProtocols.indexOf(DomainSchema.MIXED) > -1) { return {unique: true}; } } return null; } - private buildSettingsDomain(domainParams?: OAuth2Settings): FormGroup { - let url = this.window.location.protocol + '//' + this.window.location.hostname; - const port = this.window.location.port; - if (port !== '80' && port !== '443') { - url += ':' + port; + public domainListTittle(control: AbstractControl): string { + const domainInfos = control.get('domainInfos').value as DomainInfo[]; + if (domainInfos.length) { + const domainList = new Set(); + domainInfos.forEach((domain) => { + domainList.add(domain.name); + }); + return Array.from(domainList).join(', '); } - url += '/login/oauth2/code/'; + return this.translate.instant('admin.oauth2.new-domain'); + } + + private buildDomainsForm(domainParams?: DomainsParam): FormGroup { const formDomain = this.fb.group({ - domainName: [domainParams?.domainName ? domainParams.domainName : this.window.location.hostname, [ - Validators.required, - Validators.pattern('((?![:/]).)*$'), - this.uniqueDomainValidator]], - redirectUriTemplate: [domainParams?.redirectUriTemplate ? domainParams.redirectUriTemplate : url, [ - Validators.required, - Validators.pattern(this.URL_REGEXP)]], + domainInfos: this.fb.array([], Validators.required), clientRegistrations: this.fb.array([], Validators.required) }); - this.subscriptions.push(formDomain.get('domainName').valueChanges.subscribe((domain) => { - if (!domain) { - domain = this.window.location.hostname; - } - const uri = this.window.location.protocol + `//${domain}/login/oauth2/code/`; - formDomain.get('redirectUriTemplate').patchValue(uri, {emitEvent: false}); - })); - if (domainParams) { + domainParams.domainInfos.forEach((domain) => { + this.clientDomainInfos(formDomain).push(this.buildDomainForm(domain)); + }); domainParams.clientRegistrations.forEach((registration) => { - this.clientDomainRegistrations(formDomain).push(this.buildSettingsRegistration(registration)); + this.clientDomainProviders(formDomain).push(this.buildProviderForm(registration)); }); } else { - this.clientDomainRegistrations(formDomain).push(this.buildSettingsRegistration()); + this.clientDomainProviders(formDomain).push(this.buildProviderForm()); + this.clientDomainInfos(formDomain).push(this.buildDomainForm()); } return formDomain; } - private buildSettingsRegistration(registrationData?: ClientRegistration): FormGroup { + private buildDomainForm(domainInfo?: DomainInfo): FormGroup { + const domain = this.fb.group({ + name: [domainInfo ? domainInfo.name : this.window.location.hostname, [ + Validators.required, + Validators.pattern('((?![:/]).)*$'), + this.uniqueDomainValidator]], + scheme: [domainInfo?.scheme ? domainInfo.scheme : DomainSchema.HTTPS, Validators.required] + }); + return domain; + } + + private buildProviderForm(registrationData?: ClientRegistration): FormGroup { let additionalInfo = null; if (registrationData?.additionalInfo) { additionalInfo = JSON.parse(registrationData.additionalInfo); @@ -237,13 +243,18 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha additionalInfo.providerName = 'Custom'; } } + let defaultProviderName = 'Custom'; + if (this.templateProvider.indexOf('Google')) { + defaultProviderName = 'Google'; + } + const clientRegistration = this.fb.group({ id: this.fb.group({ id: [registrationData?.id?.id ? registrationData.id.id : null], entityType: [registrationData?.id?.entityType ? registrationData.id.entityType : null] }), additionalInfo: this.fb.group({ - providerName: [additionalInfo?.providerName ? additionalInfo?.providerName : 'Custom', Validators.required] + providerName: [additionalInfo?.providerName ? additionalInfo?.providerName : defaultProviderName, Validators.required] }), loginButtonLabel: [registrationData?.loginButtonLabel ? registrationData.loginButtonLabel : null, Validators.required], loginButtonIcon: [registrationData?.loginButtonIcon ? registrationData.loginButtonIcon : null], @@ -255,19 +266,20 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha authorizationUri: [registrationData?.authorizationUri ? registrationData.authorizationUri : '', [Validators.required, Validators.pattern(this.URL_REGEXP)]], - scope: this.fb.array(registrationData?.scope ? registrationData.scope : []), + scope: this.fb.array(registrationData?.scope ? registrationData.scope : [], Validators.required), jwkSetUri: [registrationData?.jwkSetUri ? registrationData.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)], userInfoUri: [registrationData?.userInfoUri ? registrationData.userInfoUri : '', [Validators.required, Validators.pattern(this.URL_REGEXP)]], clientAuthenticationMethod: [ - registrationData?.clientAuthenticationMethod ? registrationData.clientAuthenticationMethod : 'POST', Validators.required], + registrationData?.clientAuthenticationMethod ? registrationData.clientAuthenticationMethod : ClientAuthenticationMethod.POST, + Validators.required], userNameAttributeName: [ registrationData?.userNameAttributeName ? registrationData.userNameAttributeName : 'email', Validators.required], mapperConfig: this.fb.group({ allowUserCreation: [registrationData?.mapperConfig?.allowUserCreation ? registrationData.mapperConfig.allowUserCreation : true], activateUser: [registrationData?.mapperConfig?.activateUser ? registrationData.mapperConfig.activateUser : false], - type: [registrationData?.mapperConfig?.type ? registrationData.mapperConfig.type : 'BASIC', Validators.required] + type: [registrationData?.mapperConfig?.type ? registrationData.mapperConfig.type : MapperConfigType.BASIC, Validators.required] } ) }); @@ -275,7 +287,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha if (registrationData) { this.changeMapperConfigType(clientRegistration, registrationData.mapperConfig.type, registrationData.mapperConfig); } else { - this.changeMapperConfigType(clientRegistration, 'BASIC'); + this.changeMapperConfigType(clientRegistration, MapperConfigType.BASIC); } this.subscriptions.push(clientRegistration.get('mapperConfig.type').valueChanges.subscribe((value) => { @@ -313,7 +325,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha private changeMapperConfigType(control: AbstractControl, type: MapperConfigType, predefinedValue?: MapperConfig) { const mapperConfig = control.get('mapperConfig') as FormGroup; - if (type === 'BASIC') { + if (type === MapperConfigType.BASIC) { mapperConfig.removeControl('custom'); mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic)); } else { @@ -323,7 +335,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } save(): void { - const setting = this.prepareFormValue(this.oauth2SettingsForm.getRawValue().clientDomains); + const setting = this.prepareFormValue(this.oauth2SettingsForm.getRawValue()); this.adminService.saveOAuth2Settings(setting).subscribe( (oauth2Settings) => { this.oauth2Settings = oauth2Settings; @@ -333,20 +345,16 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha ); } - private prepareFormValue(formValue: OAuth2Settings[]): OAuth2Settings[]{ - const prepereValue = []; - formValue.forEach((setting, index) => { - if (this.clientDomains.at(index).dirty) { - setting.clientRegistrations.forEach((registration) => { - registration.additionalInfo = JSON.stringify(registration.additionalInfo); - if (registration.id.id === null) { - delete registration.id; - } - }); - prepereValue.push(setting); - } + private prepareFormValue(formValue: OAuth2Settings): OAuth2Settings{ + formValue.domainsParams.forEach((setting, index) => { + setting.clientRegistrations.forEach((registration) => { + registration.additionalInfo = JSON.stringify(registration.additionalInfo); + if (registration.id.id === null) { + delete registration.id; + } + }); }); - return prepereValue; + return formValue; } confirmForm(): FormGroup { @@ -359,6 +367,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha const controller = control.get('scope') as FormArray; if ((value.trim() !== '')) { controller.push(this.fb.control(value.trim())); + controller.markAsDirty(); } if (input) { @@ -369,10 +378,12 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha removeScope(i: number, control: AbstractControl): void { const controller = control.get('scope') as FormArray; controller.removeAt(i); + controller.markAsTouched(); + controller.markAsDirty(); } addDomain(): void { - this.clientDomains.push(this.buildSettingsDomain()); + this.domainsParams.push(this.buildDomainsForm()); } deleteDomain($event: Event, index: number): void { @@ -381,97 +392,48 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha $event.preventDefault(); } - const domainName = this.clientDomains.at(index).get('domainName').value; + const domainName = this.domainListTittle(this.domainsParams.at(index)); this.dialogService.confirm( this.translate.instant('admin.oauth2.delete-domain-title', {domainName: domainName || ''}), this.translate.instant('admin.oauth2.delete-domain-text'), null, this.translate.instant('action.delete') ).subscribe((data) => { if (data) { - if (index < this.oauth2Settings.length) { - this.adminService.deleteOAuth2Domain(this.oauth2Settings[index].domainName).subscribe(() => { - this.oauth2Settings.splice(index, 1); - this.clientDomains.removeAt(index); - }); - } else { - this.clientDomains.removeAt(index); - } + this.domainsParams.removeAt(index); + this.domainsParams.markAsTouched(); + this.domainsParams.markAsDirty(); } }); } - clientDomainRegistrations(control: AbstractControl): FormArray { + clientDomainProviders(control: AbstractControl): FormArray { return control.get('clientRegistrations') as FormArray; } - addRegistration(control: AbstractControl): void { - this.clientDomainRegistrations(control).push(this.buildSettingsRegistration()); + clientDomainInfos(control: AbstractControl): FormArray { + return control.get('domainInfos') as FormArray; } - deleteRegistration($event: Event, control: AbstractControl, index: number): void { + addProvider(control: AbstractControl): void { + this.clientDomainProviders(control).push(this.buildProviderForm()); + } + + deleteProvider($event: Event, control: AbstractControl, index: number): void { if ($event) { $event.stopPropagation(); $event.preventDefault(); } - const providerName = this.clientDomainRegistrations(control).at(index).get('additionalInfo.providerName').value; + const providerName = this.clientDomainProviders(control).at(index).get('additionalInfo.providerName').value; this.dialogService.confirm( this.translate.instant('admin.oauth2.delete-registration-title', {name: providerName || ''}), this.translate.instant('admin.oauth2.delete-registration-text'), null, this.translate.instant('action.delete') ).subscribe((data) => { if (data) { - const registrationId = this.clientDomainRegistrations(control).at(index).get('id.id').value; - if (registrationId) { - this.adminService.deleteOAuthCclientRegistrationId(registrationId).subscribe(() => { - this.clientDomainRegistrations(control).removeAt(index); - }); - } else { - this.clientDomainRegistrations(control).removeAt(index); - } - } - }); - } - - editRedirectURI($event: MouseEvent, index: number) { - if ($event) { - $event.stopPropagation(); - $event.preventDefault(); - } - const target = $event.target || $event.currentTarget; - const config = new OverlayConfig(); - config.backdropClass = 'cdk-overlay-transparent-backdrop'; - config.hasBackdrop = true; - const connectedPosition: ConnectedPosition = { - originX: 'end', - originY: 'bottom', - overlayX: 'end', - overlayY: 'top' - }; - config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) - .withPositions([connectedPosition]); - - const overlayRef = this.overlay.create(config); - overlayRef.backdropClick().subscribe(() => { - overlayRef.dispose(); - }); - - const redirectURI = this.clientDomains.at(index).get('redirectUriTemplate').value; - - const injectionTokens = new WeakMap([ - [EDIT_REDIRECT_URI_PANEL_DATA, { - redirectURI - } as EditRedirectUriPanelData], - [OverlayRef, overlayRef] - ]); - const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); - const componentRef = overlayRef.attach(new ComponentPortal(EditRedirectUriPanelComponent, - this.viewContainerRef, injector)); - componentRef.onDestroy(() => { - if (componentRef.instance.result !== null) { - const attributeValue = componentRef.instance.result; - this.clientDomains.at(index).get('redirectUriTemplate').patchValue(attributeValue); - this.clientDomains.at(index).get('redirectUriTemplate').markAsDirty(); + this.clientDomainProviders(control).removeAt(index); + this.clientDomainProviders(control).markAsTouched(); + this.clientDomainProviders(control).markAsDirty(); } }); } @@ -491,4 +453,43 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } return this.templates.get(provider).helpLink; } + + addDomainInfo(control: AbstractControl): void { + this.clientDomainInfos(control).push(this.buildDomainForm({ + name: '', + scheme: DomainSchema.HTTPS + })); + } + + removeDomain($event: Event, control: AbstractControl, index: number): void { + if ($event) { + $event.stopPropagation(); + $event.preventDefault(); + } + this.clientDomainInfos(control).removeAt(index); + this.clientDomainInfos(control).markAsTouched(); + this.clientDomainInfos(control).markAsDirty(); + } + + redirectURI(control: AbstractControl, schema?: DomainSchema): string { + const domainInfo = control.value as DomainInfo; + if (domainInfo.name !== '') { + let protocol; + if (isDefined(schema)) { + protocol = schema.toLowerCase(); + } else { + protocol = domainInfo.scheme === DomainSchema.MIXED ? DomainSchema.HTTPS.toLowerCase() : domainInfo.scheme.toLowerCase(); + } + return `${protocol}://${domainInfo.name}/login/oauth2/code/`; + } + return ''; + } + + redirectURIMixed(control: AbstractControl): string { + return this.redirectURI(control, DomainSchema.HTTP); + } + + trackByParams(index: number): number { + return index; + } } diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index f27d80a260..f31dc5ebe8 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -26,10 +26,6 @@ export interface AdminSettings { export declare type SmtpProtocol = 'smtp' | 'smtps'; -export declare type ClientAuthenticationMethod = 'BASIC' | 'POST'; -export declare type MapperConfigType = 'BASIC' | 'CUSTOM'; -export declare type TenantNameStrategy = 'DOMAIN' | 'EMAIL' | 'CUSTOM'; - export interface MailServerSettings { mailFrom: string; smtpProtocol: SmtpProtocol; @@ -69,9 +65,43 @@ export interface UpdateMessage { } export interface OAuth2Settings { - domainName: string; - redirectUriTemplate: string; + enabled: boolean; + domainsParams: DomainsParam[]; +} + +export interface DomainsParam { clientRegistrations: ClientRegistration[]; + domainInfos: DomainInfo[]; +} + +export interface DomainInfo { + name: string; + scheme: DomainSchema; +} + +export enum DomainSchema{ + HTTP = 'HTTP', + HTTPS = 'HTTPS', + MIXED = 'MIXED' +} + +export const domainSchemaTranslations = new Map( + [ + [DomainSchema.HTTP, 'admin.oauth2.domain-schema-http'], + [DomainSchema.HTTPS, 'admin.oauth2.domain-schema-https'], + [DomainSchema.MIXED, 'admin.oauth2.domain-schema-mixed'] + ] +); + +export enum MapperConfigType{ + BASIC = 'BASIC', + CUSTOM = 'CUSTOM' +} + +export enum TenantNameStrategy{ + DOMAIN = 'DOMAIN', + EMAIL = 'EMAIL', + CUSTOM = 'CUSTOM' } export interface ClientProviderTemplated extends ClientRegistration{ @@ -100,6 +130,11 @@ export interface ClientRegistration { additionalInfo: string; } +export enum ClientAuthenticationMethod { + BASIC = 'BASIC', + POST = 'POST' +} + export interface MapperConfig { allowUserCreation: boolean; activateUser: boolean; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 8cb452e075..6a4fc5e084 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -128,7 +128,9 @@ "access-token-uri-required": "Access token URI is required.", "activate-user": "Activate user", "add-domain": "Add domain", + "delete-domain": "Delete domain", "add-provider": "Add provider", + "delete-provider": "Delete provider", "allow-user-creation": "Allow user creation", "always-fullscreen": "Always fullscreen", "authorization-uri": "Authorization URI", @@ -141,10 +143,10 @@ "custom-setting": "Custom settings", "customer-name-pattern": "Customer name pattern", "default-dashboard-name": "Default dashboard name", - "delete-domain-text": "Be careful, after the confirmation a domain and all registration data will be unavailable.", - "delete-domain-title": "Are you sure you want to delete the domain '{{domainName}}'?", - "delete-registration-text": "Be careful, after the confirmation a registration data will be unavailable.", - "delete-registration-title": "Are you sure you want to delete the registration '{{name}}'?", + "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.", "first-name-attribute-key": "First name attribute key", @@ -160,11 +162,12 @@ "new-domain": "New domain", "oauth2": "OAuth2", "redirect-uri-template": "Redirect URI template", - "redirect-uri-template-required": "Redirect URI template is required.", + "copy-redirect-uri": "Copy redirect URI", "registration-id": "Registration ID", "registration-id-required": "Registration ID is required.", "registration-id-unique": "Registration ID need to unique for the system.", "scope": "Scope", + "scope-required": "Scope is required.", "tenant-name-pattern": "Tenant name pattern", "tenant-name-pattern-required": "Tenant name pattern is required.", "tenant-name-strategy": "Tenant name strategy", @@ -176,7 +179,12 @@ "user-info-uri": "User info URI", "user-info-uri-required": "User info URI is required.", "user-name-attribute-name": "User name attribute key", - "user-name-attribute-name-required": "User name attribute key is required" + "user-name-attribute-name-required": "User name attribute key is required", + "protocol": "Protocol", + "domain-schema-http": "HTTP", + "domain-schema-https": "HTTPS", + "domain-schema-mixed": "HTTP+HTTPS", + "enable": "Enable OAuth2 settings" } }, "alarm": { From 0c56a1d8d85a02b786e15c7e762e2a99a7f42cf0 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 5 Oct 2020 14:44:43 +0300 Subject: [PATCH 125/463] Improvements --- .../server/transport/mqtt/MqttTransportHandler.java | 4 +++- .../server/transport/mqtt/session/DeviceSessionCtx.java | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 621fcf8ea5..b8c2e64790 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -175,7 +175,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0))); break; case DISCONNECT: - ctx.close(); + if (checkConnected(ctx, msg)) { + processDisconnect(ctx); + } break; } } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index 1a27b34ab7..c0c5e4cc52 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -49,7 +49,7 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { @Getter @Setter - private boolean provisionOnly; + private boolean provisionOnly = false; private volatile MqttTopicFilter telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); private volatile MqttTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); From ed2920eddc365383a50eb3ed087929d12b398609 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 5 Oct 2020 18:00:26 +0300 Subject: [PATCH 126/463] Remove form reset when saving, add translatable error messages --- .../lib/multiple-input-widget.component.html | 60 ++++++++++--------- .../lib/multiple-input-widget.component.scss | 22 +++---- .../lib/multiple-input-widget.component.ts | 56 +++++++++++------ 3 files changed, 76 insertions(+), 62 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html index 1423000d4a..6d2b4d4a30 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html @@ -39,7 +39,7 @@ (blur)="key.isFocused = false; inputChanged(source, key)"> {{key.settings.icon}} - {{key.settings.requiredErrorMessage}} + {{ getErrorMessageText(key.settings.requiredErrorMessage, '') }} @@ -59,52 +59,54 @@ (blur)="key.isFocused = false; inputChanged(source, key)"> {{key.settings.icon}} - {{key.settings.requiredErrorMessage}} + {{ getErrorMessageText(key.settings.requiredErrorMessage, '') }} - {{ 'widgets.input-widgets.min-value-error' | translate:{value: key.settings.minValue} }} + {{ getErrorMessageText(key.settings.minValueErrorMessage, + 'widgets.input-widgets.min-value-error', + {value: key.settings.minValue}) }} - {{ 'widgets.input-widgets.max-value-error' | translate:{value: key.settings.maxValue} }} + {{ getErrorMessageText(key.settings.maxValueErrorMessage, + 'widgets.input-widgets.max-value-error', + {value: key.settings.maxValue}) }}
- {{key.label}} + {{key.label}}
- {{key.label}} + {{key.label}}
-
- - {{key.label}} - - - - - {{key.settings.requiredErrorMessage}} - - - {{ 'widgets.input-widgets.invalid-date' | translate }} - - -
+ + {{key.label}} + + + + + {{ getErrorMessageText(key.settings.requiredErrorMessage, '') }} + + + {{ getErrorMessageText(key.settings.invalidDateErrorMessage, 'widgets.input-widgets.invalid-date') }} + +
@@ -114,12 +116,12 @@ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss index de1a672535..a5afcfabf3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss @@ -44,7 +44,13 @@ display: block; margin-top: 20px; margin-bottom: 16px; - white-space: normal; + .label-wrapper { + white-space: normal; + } + } + + mat-slide-toggle { + height: auto; } .date-time-input { @@ -56,20 +62,6 @@ .vertical-alignment { flex-direction: column; - - mat-checkbox, - mat-slide-toggle { - margin-top: 18px; - } - - mat-slide-toggle { - display: flex; - justify-content: space-between; - } - } - - .vertically-aligned { - flex-direction: column; } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts index 21a8d1d062..361b1a06e9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts @@ -47,6 +47,8 @@ interface MultipleInputWidgetSettings { widgetTitle: string; showActionButtons: boolean; updateAllValues: boolean; + saveButtonLabel: string; + resetButtonLabel: string; showResultMessage: boolean; showGroupTitle: boolean; groupTitle: string; @@ -67,27 +69,19 @@ interface MultipleInputWidgetDataKeySettings { maxValue?: number; requiredErrorMessage?: string; invalidDateErrorMessage?: string; - minErrorMessage?: string; - maxErrorMessage?: string; + minValueErrorMessage?: string; + maxValueErrorMessage?: string; icon: string; inputTypeNumber?: boolean; readOnly?: boolean; disabledOnCondition?: boolean; } -interface MultipleInputWidgetDataKeyErrorMessages { - requiredErrorMessage?: string; - invalidDateErrorMessage?: string; - minErrorMessage?: string; - maxErrorMessage?: string; -} - interface MultipleInputWidgetDataKey extends DataKey { formId?: string; settings: MultipleInputWidgetDataKeySettings; isFocused: boolean; value?: any; - errorMessages?: MultipleInputWidgetDataKeyErrorMessages; } interface MultipleInputWidgetSource { @@ -170,6 +164,17 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni this.settings.groupTitle = this.settings.groupTitle || '${entityName}'; + if (this.settings.saveButtonLabel && this.settings.saveButtonLabel.length) { + this.settings.saveButtonLabel = this.utils.customTranslation(this.settings.saveButtonLabel, this.settings.saveButtonLabel); + } else { + this.settings.saveButtonLabel = this.translate.instant('action.save'); + } + if (this.settings.resetButtonLabel && this.settings.resetButtonLabel.length) { + this.settings.resetButtonLabel = this.utils.customTranslation(this.settings.resetButtonLabel, this.settings.resetButtonLabel); + } else { + this.settings.resetButtonLabel = this.translate.instant('action.undo'); + } + // For backward compatibility if (isUndefined(this.settings.showActionButtons)) { this.settings.showActionButtons = true; @@ -338,25 +343,40 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni private updateWidgetDisplaying() { this.changeAlignment = (this.ctx.$container && this.ctx.$container[0].offsetWidth < 620); - this.smallWidthContainer = (this.ctx.$container && this.ctx.$container[0].offsetWidth < 420); } public onDataUpdated() { - this.ngZone.run(() => { + // this.ngZone.run(() => { this.updateWidgetData(this.subscription.data); this.ctx.detectChanges(); - }); + // }); } private resize() { - this.ngZone.run(() => { + // this.ngZone.run(() => { this.updateWidgetDisplaying(); this.ctx.detectChanges(); - }); + // }); } public getGroupTitle(datasource: Datasource): string { - return createLabelFromDatasource(datasource, this.settings.groupTitle); + const groupTitle = createLabelFromDatasource(datasource, this.settings.groupTitle); + return this.utils.customTranslation(groupTitle, groupTitle); + } + + public getErrorMessageText(errorMessage, defaultMessage, messageValues?): string { + let messageText; + if (errorMessage && errorMessage.length) { + messageText = this.utils.customTranslation(errorMessage, errorMessage); + } else if (defaultMessage && defaultMessage.length) { + if (!messageValues) { + messageValues = {}; + } + messageText = this.translate.instant(defaultMessage, messageValues); + } else { + messageText = ''; + } + return messageText; } public visibleKeys(source: MultipleInputWidgetSource): MultipleInputWidgetDataKey[] { @@ -480,8 +500,8 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni if (tasks.length) { forkJoin(tasks).subscribe( () => { - // this.multipleInputFormGroup.reset(undefined, {emitEvent: false}); this.multipleInputFormGroup.markAsPristine(); + this.ctx.detectChanges(); if (this.settings.showResultMessage) { this.ctx.showSuccessToast(this.translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', this.toastTargetId); @@ -494,8 +514,8 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni } }); } else { - // this.multipleInputFormGroup.reset(undefined, {emitEvent: false}); this.multipleInputFormGroup.markAsPristine(); + this.ctx.detectChanges(); } } From fd80cda30a98a3b6a4bd3ba8a939f6b9d6d43bb4 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 6 Oct 2020 09:50:47 +0300 Subject: [PATCH 127/463] Added TbCoreComponent annotation --- .../server/service/device/DeviceProvisionServiceImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 2460ce0fde..2e2f1cbb80 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -60,6 +60,7 @@ import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; +import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.state.DeviceStateService; import java.util.Collections; @@ -70,6 +71,7 @@ import java.util.concurrent.locks.ReentrantLock; @Service @Slf4j +@TbCoreComponent public class DeviceProvisionServiceImpl implements DeviceProvisionService { protected TbQueueProducer> ruleEngineMsgProducer; From 616f6a8b4f8a7ba1d84012d6c84440abea1c5a03 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 6 Oct 2020 17:17:50 +0300 Subject: [PATCH 128/463] Changed Provision statuses --- .../service/transport/DefaultTransportApiService.java | 1 + .../dao/device/provision/ProvisionResponseStatus.java | 2 +- common/queue/src/main/proto/queue.proto | 7 ++++--- .../server/transport/mqtt/MqttTransportHandler.java | 1 + .../server/common/transport/adaptor/JsonConverter.java | 4 ++++ 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index dc5535f9e1..7b4701754b 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -315,6 +315,7 @@ public class DefaultTransportApiService implements TransportApiService { .setCredentialsValue(deviceCredentials.getCredentialsValue() != null ? deviceCredentials.getCredentialsValue() : "") .build(); } + private ListenableFuture handle(GetTenantRoutingInfoRequestMsg requestMsg) { TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB())); // TODO: Tenant Profile from cache diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java index 3c993b0e14..46764f1b67 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.device.provision; public enum ProvisionResponseStatus { - + UNKNOWN, SUCCESS, NOT_FOUND, FAILURE diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index bf308d922e..6a96f8a6d6 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -271,9 +271,10 @@ message ProvisionDeviceResponseMsg { } enum ProvisionResponseStatus { - SUCCESS = 0; - NOT_FOUND = 1; - FAILURE = 2; + UNKNOWN = 0; + SUCCESS = 1; + NOT_FOUND = 2; + FAILURE = 3; } //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level. message SubscriptionInfoProto { diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index b8c2e64790..fcb9b53e2b 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -68,6 +68,7 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 509e691668..cd070e6927 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -43,6 +43,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionResponseStatus; import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; @@ -411,8 +412,10 @@ public class JsonConverter { JsonObject result = new JsonObject(); if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.NOT_FOUND) { result.addProperty("errorMsg", "Provision data was not found!"); + result.addProperty("provisionDeviceStatus", ProvisionResponseStatus.NOT_FOUND.name()); } else if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.FAILURE) { result.addProperty("errorMsg", "Failed to provision device!"); + result.addProperty("provisionDeviceStatus", ProvisionResponseStatus.FAILURE.name()); } else { if (toGateway) { result.addProperty("id", requestId); @@ -422,6 +425,7 @@ public class JsonConverter { result.addProperty("credentialsId", payload.getDeviceCredentials().getCredentialsId()); result.addProperty("credentialsValue", StringUtils.isEmpty(payload.getDeviceCredentials().getCredentialsValue()) ? null : payload.getDeviceCredentials().getCredentialsValue()); + result.addProperty("provisionDeviceStatus", ProvisionResponseStatus.SUCCESS.name()); } return result; } From 2ec09e08ff377d84b02e1223009bb76dc0aad594 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 6 Oct 2020 18:27:58 +0300 Subject: [PATCH 129/463] Added ProvisionTransportType to get protocol --- .../server/transport/mqtt/MqttTransportHandler.java | 8 +++++++- .../server/transport/mqtt/session/DeviceSessionCtx.java | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index fcb9b53e2b..fdbcf01ac7 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -42,6 +42,7 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.transport.SessionMsgListener; @@ -159,6 +160,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement if (e.getCause().toString().contains("JsonSyntaxException")) { TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg); transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg)); + deviceSessionCtx.setProvisionPayloadType(TransportPayloadType.PROTOBUF); log.trace("[{}][{}] Processing provision publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId); } else { throw e; @@ -326,7 +328,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement ctx.writeAndFlush(createMqttPubAckMsg(msgId)); } try { - deviceSessionCtx.getPayloadAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); + if (deviceSessionCtx.getProvisionPayloadType().equals(TransportPayloadType.JSON)) { + deviceSessionCtx.getContext().getJsonMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); + } else { + deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); + } } catch (Exception e) { log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index c0c5e4cc52..20ddfb5a24 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -55,6 +55,10 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { private volatile MqttTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); private volatile TransportPayloadType payloadType = TransportPayloadType.JSON; + @Getter + @Setter + private TransportPayloadType provisionPayloadType = payloadType; + public DeviceSessionCtx(UUID sessionId, ConcurrentMap mqttQoSMap, MqttTransportContext context) { super(sessionId, mqttQoSMap); this.context = context; From 6d0dc981a0837defaa37c30f2a0edbf4f62fccaf Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 6 Oct 2020 18:28:44 +0300 Subject: [PATCH 130/463] Adding tests for device provisioning feature --- .../mqtt/AbstractMqttIntegrationTest.java | 47 ++++- .../server/mqtt/MqttSqlTestSuite.java | 3 +- .../AbstractMqttProvisionJsonDeviceTest.java | 188 +++++++++++++++++ .../AbstractMqttProvisionProtoDeviceTest.java | 189 ++++++++++++++++++ .../sql/MqttProvisionDeviceJsonSqlTest.java | 23 +++ .../sql/MqttProvisionDeviceProtoSqlTest.java | 23 +++ 6 files changed, 469 insertions(+), 4 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java create mode 100644 application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java create mode 100644 application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceJsonSqlTest.java create mode 100644 application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceProtoSqlTest.java diff --git a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java index 846343b65e..4e8f3e9fa4 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java @@ -25,13 +25,18 @@ import org.junit.Assert; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.DeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; @@ -62,7 +67,18 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest protected Device savedGateway; protected String gatewayAccessToken; - protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception { + protected void processBeforeTest (String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception { + this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, null); + } + + protected void processBeforeTest(String deviceName, + String gatewayName, + TransportPayloadType payloadType, + String telemetryTopic, + String attributesTopic, + DeviceProfileProvisionType provisionType, + String provisionKey, String provisionSecret + ) throws Exception { loginSysAdmin(); Tenant tenant = new Tenant(); @@ -91,7 +107,7 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest gateway.setAdditionalInfo(additionalInfo); if (payloadType != null) { - DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic); + DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic, provisionType, provisionKey, provisionSecret); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class); device.setType(savedDeviceProfile.getName()); device.setDeviceProfileId(savedDeviceProfile.getId()); @@ -181,11 +197,23 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest return keyValueProtoBuilder.build(); } - protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType, String telemetryTopic, String attributesTopic) { + protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType, + String telemetryTopic, String attributesTopic + ) { + return this.createMqttDeviceProfile(transportPayloadType, telemetryTopic, attributesTopic, null, null, null); + } + + protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType, + String telemetryTopic, String attributesTopic, + DeviceProfileProvisionType provisionType, + String provisionKey, String provisionSecret + ) { DeviceProfile deviceProfile = new DeviceProfile(); deviceProfile.setName(transportPayloadType.name()); deviceProfile.setType(DeviceProfileType.DEFAULT); deviceProfile.setTransportType(DeviceTransportType.MQTT); + deviceProfile.setProvisionType(provisionType); + deviceProfile.setProvisionDeviceKey(provisionKey); deviceProfile.setDescription(transportPayloadType.name() + " Test"); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); @@ -198,6 +226,19 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest transportConfiguration.setDeviceAttributesTopic(attributesTopic); } deviceProfileData.setTransportConfiguration(transportConfiguration); + DeviceProfileProvisionConfiguration provisionConfiguration; + switch (provisionType) { + case ALLOW_CREATE_NEW_DEVICES: + provisionConfiguration = new AllowCreateNewDevicesDeviceProfileProvisionConfiguration(provisionSecret); + break; + case CHECK_PRE_PROVISIONED_DEVICES: + provisionConfiguration = new CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration(provisionSecret); + break; + case DISABLED: + default: + provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(provisionSecret); + } + deviceProfileData.setProvisionConfiguration(provisionConfiguration); deviceProfileData.setConfiguration(configuration); deviceProfile.setProfileData(deviceProfileData); deviceProfile.setDefault(false); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java index 095a5c3e7a..f71e8f37a2 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java @@ -31,7 +31,8 @@ import java.util.Arrays; "org.thingsboard.server.mqtt.telemetry.attributes.sql.*Test", "org.thingsboard.server.mqtt.attributes.updates.sql.*Test", "org.thingsboard.server.mqtt.attributes.request.sql.*Test", - "org.thingsboard.server.mqtt.claim.sql.*Test" + "org.thingsboard.server.mqtt.claim.sql.*Test", + "org.thingsboard.server.mqtt.provision.sql.*Test" }) public class MqttSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java new file mode 100644 index 0000000000..26d7e2afce --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java @@ -0,0 +1,188 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.mqtt.provision; + +import com.google.gson.JsonObject; +import io.netty.handler.codec.mqtt.MqttQoS; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.MqttAsyncClient; +import org.eclipse.paho.client.mqttv3.MqttCallback; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.transport.util.JsonUtils; +import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; +import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@Slf4j +public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest { + + @Autowired + DeviceCredentialsService deviceCredentialsService; + + @Autowired + DeviceService deviceService; + + @After + public void afterTest() throws Exception { + super.processAfterTest(); + } + + @Test + public void testProvisioningDisabledDevice() throws Exception { + processTestProvisioningDisabledDevice(); + } + + @Test + public void testProvisioningCheckPreProvisionedDevice() throws Exception { + processTestProvisioningCheckPreProvisionedDevice(); + } + + @Test + public void testProvisioningCreateNewDevice() throws Exception { + processTestProvisioningCreateNewDevice(); + } + + @Test + public void testProvisioningWithBadKeyDevice() throws Exception { + processTestProvisioningWithBadKeyDevice(); + } + + + protected void processTestProvisioningDisabledDevice() throws Exception { + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.DISABLED, null, null); + byte[] result = createMqttClientAndPublish().getPayloadBytes(); + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("provisionDeviceStatus").getAsString()); + } + + + protected void processTestProvisioningCreateNewDevice() throws Exception { + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + byte[] result = createMqttClientAndPublish().getPayloadBytes(); + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString()); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + } + + protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); + byte[] result = createMqttClientAndPublish().getPayloadBytes(); + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + Assert.assertEquals(savedDevice.getId().toString(), response.get("deviceId").getAsString()); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + } + + protected void processTestProvisioningWithBadKeyDevice() throws Exception { + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); + byte[] result = createMqttClientAndPublish().getPayloadBytes(); + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("provisionDeviceStatus").getAsString()); + } + + protected TestMqttCallback createMqttClientAndPublish() throws Exception{ + String provisionRequestMsg = createTestProvisionMessage(); + MqttAsyncClient client = getMqttAsyncClient("provision"); + TestMqttCallback onProvisionCallback = getTestMqttCallback(); + client.setCallback(onProvisionCallback); + client.subscribe(MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, MqttQoS.AT_MOST_ONCE.value()); + Thread.sleep(2000); + client.publish(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC, new MqttMessage(provisionRequestMsg.getBytes())); + onProvisionCallback.getLatch().await(3, TimeUnit.SECONDS); + return onProvisionCallback; + } + + + protected TestMqttCallback getTestMqttCallback() { + CountDownLatch latch = new CountDownLatch(1); + return new TestMqttCallback(latch); + } + + + protected static class TestMqttCallback implements MqttCallback { + + private final CountDownLatch latch; + private Integer qoS; + private byte[] payloadBytes; + + TestMqttCallback(CountDownLatch latch) { + this.latch = latch; + } + + public int getQoS() { + return qoS; + } + + public byte[] getPayloadBytes() { + return payloadBytes; + } + + public CountDownLatch getLatch() { + return latch; + } + + @Override + public void connectionLost(Throwable throwable) { + } + + @Override + public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception { + qoS = mqttMessage.getQos(); + payloadBytes = mqttMessage.getPayload(); + latch.countDown(); + } + + @Override + public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { + + } + } + + + protected String createTestProvisionMessage() { + return "{\"deviceName\":\"Test Provision device\",\"provisionDeviceKey\":\"testProvisionKey\", \"provisionDeviceSecret\":\"testProvisionSecret\"}"; + } + +} diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java new file mode 100644 index 0000000000..7720ddad4a --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java @@ -0,0 +1,189 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.mqtt.provision; + +import com.google.gson.JsonObject; +import com.sun.mail.iap.ByteArray; +import io.netty.handler.codec.mqtt.MqttQoS; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.MqttAsyncClient; +import org.eclipse.paho.client.mqttv3.MqttCallback; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.transport.util.JsonUtils; +import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceCredentialsMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; +import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest; + +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@Slf4j +public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest { + + @Autowired + DeviceCredentialsService deviceCredentialsService; + + @Autowired + DeviceService deviceService; + + @After + public void afterTest() throws Exception { + super.processAfterTest(); + } + + @Test + public void testProvisioningDisabledDevice() throws Exception { + processTestProvisioningDisabledDevice(); + } + + @Test + public void testProvisioningCheckPreProvisionedDevice() throws Exception { + processTestProvisioningCheckPreProvisionedDevice(); + } + + @Test + public void testProvisioningCreateNewDevice() throws Exception { + processTestProvisioningCreateNewDevice(); + } + + @Test + public void testProvisioningWithBadKeyDevice() throws Exception { + processTestProvisioningWithBadKeyDevice(); + } + + + protected void processTestProvisioningDisabledDevice() throws Exception { + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.DISABLED, null, null); + ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); + Assert.assertNotNull(result); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getProvisionResponseStatus().toString()); + } + + + protected void processTestProvisioningCreateNewDevice() throws Exception { + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + } + + protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); + Assert.assertEquals(savedDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + } + + protected void processTestProvisioningWithBadKeyDevice() throws Exception { + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getProvisionResponseStatus().toString()); + } + + protected TestMqttCallback createMqttClientAndPublish() throws Exception{ + byte[] provisionRequestMsg = createTestProvisionMessage(); + MqttAsyncClient client = getMqttAsyncClient("provision"); + TestMqttCallback onProvisionCallback = getTestMqttCallback(); + client.setCallback(onProvisionCallback); + client.subscribe(MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, MqttQoS.AT_MOST_ONCE.value()); + Thread.sleep(2000); + client.publish(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC, new MqttMessage(provisionRequestMsg)); + onProvisionCallback.getLatch().await(3, TimeUnit.SECONDS); + return onProvisionCallback; + } + + + protected TestMqttCallback getTestMqttCallback() { + CountDownLatch latch = new CountDownLatch(1); + return new TestMqttCallback(latch); + } + + + protected static class TestMqttCallback implements MqttCallback { + + private final CountDownLatch latch; + private Integer qoS; + private byte[] payloadBytes; + + TestMqttCallback(CountDownLatch latch) { + this.latch = latch; + } + + public int getQoS() { + return qoS; + } + + public byte[] getPayloadBytes() { + return payloadBytes; + } + + public CountDownLatch getLatch() { + return latch; + } + + @Override + public void connectionLost(Throwable throwable) { + } + + @Override + public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception { + qoS = mqttMessage.getQos(); + payloadBytes = mqttMessage.getPayload(); + latch.countDown(); + } + + @Override + public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { + + } + } + + + protected byte[] createTestProvisionMessage() { + return ProvisionDeviceRequestMsg.newBuilder().setX509CertPubKey("").setDeviceName("Test Provision device").setProvisionDeviceCredentialsMsg(ProvisionDeviceCredentialsMsg.newBuilder().setProvisionDeviceKey("testProvisionKey").setProvisionDeviceSecret("testProvisionSecret")).build().toByteArray(); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceJsonSqlTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceJsonSqlTest.java new file mode 100644 index 0000000000..c9e6495fd3 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceJsonSqlTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.mqtt.provision.sql; + +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.mqtt.provision.AbstractMqttProvisionJsonDeviceTest; + +@DaoSqlTest +public class MqttProvisionDeviceJsonSqlTest extends AbstractMqttProvisionJsonDeviceTest { +} diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceProtoSqlTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceProtoSqlTest.java new file mode 100644 index 0000000000..7a8170d01e --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceProtoSqlTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.mqtt.provision.sql; + +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.mqtt.provision.AbstractMqttProvisionProtoDeviceTest; + +@DaoSqlTest +public class MqttProvisionDeviceProtoSqlTest extends AbstractMqttProvisionProtoDeviceTest { +} From 9c5b353a17a21d3d392afc2e3b43d2849100cf8f Mon Sep 17 00:00:00 2001 From: Vladyslav Prykhodko Date: Wed, 7 Oct 2020 00:44:36 +0300 Subject: [PATCH 131/463] UI: Added validation unique domain --- .../admin/oauth2-settings.component.html | 49 +++++++------- .../pages/admin/oauth2-settings.component.ts | 65 ++++++++++--------- .../assets/locale/locale.constant-en_US.json | 2 +- 3 files changed, 60 insertions(+), 56 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html index 6e0048612c..fc8e294774 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -59,30 +59,31 @@
-
-
- - admin.oauth2.protocol - - - {{ domainSchemaTranslations.get(protocol) | translate | uppercase }} - - - - - - admin.domain-name - - - {{ 'admin.error-verification-url' | translate }} - - - {{ 'admin.domain-name-unique' | translate }} - - +
+
+
+ + admin.oauth2.protocol + + + {{ domainSchemaTranslations.get(protocol) | translate | uppercase }} + + + + + admin.domain-name + + + {{ 'admin.error-verification-url' | translate }} + + +
+ + {{ 'admin.domain-name-unique' | translate }} +
-
+
admin.oauth2.redirect-uri-template @@ -94,8 +95,7 @@ - +
-
diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index b2ffc2bb6e..11b39468e9 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -178,13 +178,14 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } } - private uniqueDomainValidator(control: AbstractControl): { [key: string]: boolean } | null { - if (control.parent?.parent?.value) { - const domain = control.value; - const listProtocols = control.parent.parent.value + private uniqueDomainValidator(control: FormGroup): { [key: string]: boolean } | null { + if (control.parent?.value) { + const domain = control.value.name; + const listProtocols = control.parent.getRawValue() .filter((domainInfo) => domainInfo.name === domain) .map((domainInfo) => domainInfo.scheme); - if (listProtocols.length > 1 && listProtocols.indexOf(DomainSchema.MIXED) > -1) { + if (listProtocols.length > 1 && listProtocols.indexOf(DomainSchema.MIXED) > -1 || + new Set(listProtocols).size !== listProtocols.length) { return {unique: true}; } } @@ -228,10 +229,9 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha const domain = this.fb.group({ name: [domainInfo ? domainInfo.name : this.window.location.hostname, [ Validators.required, - Validators.pattern('((?![:/]).)*$'), - this.uniqueDomainValidator]], + Validators.pattern('((?![:/]).)*$')]], scheme: [domainInfo?.scheme ? domainInfo.scheme : DomainSchema.HTTPS, Validators.required] - }); + }, {validators: this.uniqueDomainValidator}); return domain; } @@ -288,6 +288,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha this.changeMapperConfigType(clientRegistration, registrationData.mapperConfig.type, registrationData.mapperConfig); } else { this.changeMapperConfigType(clientRegistration, MapperConfigType.BASIC); + this.setProviderDefaultValue(defaultProviderName, clientRegistration); } this.subscriptions.push(clientRegistration.get('mapperConfig.type').valueChanges.subscribe((value) => { @@ -296,33 +297,37 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha this.subscriptions.push(clientRegistration.get('additionalInfo.providerName').valueChanges.subscribe((provider) => { (clientRegistration.get('scope') as FormArray).clear(); - if (provider === 'Custom') { - const defaultSettings = {...this.defaultProvider, ...{id: clientRegistration.get('id').value}}; - clientRegistration.reset(defaultSettings, {emitEvent: false}); - clientRegistration.get('accessTokenUri').enable(); - clientRegistration.get('authorizationUri').enable(); - clientRegistration.get('jwkSetUri').enable(); - clientRegistration.get('userInfoUri').enable(); - } else { - const template = this.templates.get(provider); - delete template.id; - delete template.additionalInfo; - template.clientId = ''; - template.clientSecret = ''; - template.scope.forEach(() => { - (clientRegistration.get('scope') as FormArray).push(this.fb.control('')); - }); - clientRegistration.get('accessTokenUri').disable(); - clientRegistration.get('authorizationUri').disable(); - clientRegistration.get('jwkSetUri').disable(); - clientRegistration.get('userInfoUri').disable(); - clientRegistration.patchValue(template, {emitEvent: false}); - } + this.setProviderDefaultValue(provider, clientRegistration); })); return clientRegistration; } + private setProviderDefaultValue(provider: string, clientRegistration: FormGroup) { + if (provider === 'Custom') { + const defaultSettings = {...this.defaultProvider, ...{id: clientRegistration.get('id').value}}; + clientRegistration.reset(defaultSettings, {emitEvent: false}); + clientRegistration.get('accessTokenUri').enable(); + clientRegistration.get('authorizationUri').enable(); + clientRegistration.get('jwkSetUri').enable(); + clientRegistration.get('userInfoUri').enable(); + } else { + const template = this.templates.get(provider); + delete template.id; + delete template.additionalInfo; + template.clientId = ''; + template.clientSecret = ''; + template.scope.forEach(() => { + (clientRegistration.get('scope') as FormArray).push(this.fb.control('')); + }); + clientRegistration.get('accessTokenUri').disable(); + clientRegistration.get('authorizationUri').disable(); + clientRegistration.get('jwkSetUri').disable(); + clientRegistration.get('userInfoUri').disable(); + clientRegistration.patchValue(template, {emitEvent: false}); + } + } + private changeMapperConfigType(control: AbstractControl, type: MapperConfigType, predefinedValue?: MapperConfig) { const mapperConfig = control.get('mapperConfig') as FormGroup; if (type === MapperConfigType.BASIC) { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 6a4fc5e084..df5b962535 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -121,7 +121,7 @@ "minimum-max-failed-login-attempts-range": "Maximum number of failed login attempts can't be negative", "user-lockout-notification-email": "In case user account lockout, send notification to email", "domain-name": "Domain name", - "domain-name-unique": "Domain name need to unique for the system.", + "domain-name-unique": "Domain name and protocol need to unique.", "error-verification-url": "A domain name shouldn't contain symbols '/' and ':'. Example: thingsboard.io", "oauth2": { "access-token-uri": "Access token URI", From 4494f3dd67504a4e488869868ba3eade8bf087b7 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Wed, 7 Oct 2020 12:10:05 +0300 Subject: [PATCH 132/463] Fix for tests --- .../server/mqtt/AbstractMqttIntegrationTest.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java index 2cf7a0ac62..a11243533f 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java @@ -69,7 +69,7 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest protected String gatewayAccessToken; protected void processBeforeTest (String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception { - this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, null); + this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, DeviceProfileProvisionType.DISABLED, null, null); } protected void processBeforeTest(String deviceName, @@ -198,12 +198,6 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest return keyValueProtoBuilder.build(); } - protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType, - String telemetryTopic, String attributesTopic - ) { - return this.createMqttDeviceProfile(transportPayloadType, telemetryTopic, attributesTopic, null, null, null); - } - protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType, String telemetryTopic, String attributesTopic, DeviceProfileProvisionType provisionType, From ab9199d8b8d323b4f79c7f066203e535f1f4f1ed Mon Sep 17 00:00:00 2001 From: Illia Barkov Date: Wed, 7 Oct 2020 12:56:35 +0300 Subject: [PATCH 133/463] Update locale.constant-en_US.json --- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index c78d2b83cb..751ae10434 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -855,7 +855,7 @@ "provision-device-key": "Provision device key", "provision-device-key-required": "Provision device key is required.", "provision-device-secret": "Provision device secret", - "provision-device-secret-required": "Provision device secret is required." + "provision-device-secret-required": "Provision device secret is required.", "condition": "Condition", "condition-type": "Condition type", "condition-type-simple": "Simple", From 91f05282ea51b355cb2c04948919600cffb73f87 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Wed, 7 Oct 2020 13:06:22 +0300 Subject: [PATCH 134/463] Validate domains for SchemeTypes combinations --- .../server/dao/oauth2/OAuth2ServiceImpl.java | 9 +- .../dao/service/BaseOAuth2ServiceTest.java | 99 ++++++++++++++++++- 2 files changed, 102 insertions(+), 6 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index dd287f0755..2ad0ad4f20 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -119,10 +119,17 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se if (StringUtils.isEmpty(domainInfo.getName())) { throw new DataValidationException("Domain name should be specified!"); } - if (StringUtils.isEmpty(domainInfo.getScheme())) { + if (domainInfo.getScheme() == null) { throw new DataValidationException("Domain scheme should be specified!"); } } + domainParams.getDomainInfos().stream() + .collect(Collectors.groupingBy(DomainInfo::getName)) + .forEach((domainName, domainInfos) -> { + if (domainInfos.size() > 1 && domainInfos.stream().anyMatch(domainInfo -> domainInfo.getScheme() == SchemeType.MIXED)) { + throw new DataValidationException("MIXED scheme type shouldn't be combined with another scheme type!"); + } + }); if (domainParams.getClientRegistrations() == null || domainParams.getClientRegistrations().isEmpty()) { throw new DataValidationException("Client registrations should be specified!"); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java index 2daa0cb337..b54505a3d7 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -22,6 +22,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.oauth2.*; +import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.oauth2.OAuth2Service; import java.util.*; @@ -45,6 +46,44 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { Assert.assertTrue(oAuth2Service.findOAuth2Params().getDomainsParams().isEmpty()); } + @Test(expected = DataValidationException.class) + public void testSaveHttpAndMixedDomainsTogether() { + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), + DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(), + DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto(), + validClientRegistrationDto() + )) + .build() + )); + oAuth2Service.saveOAuth2Params(clientsParams); + } + + @Test(expected = DataValidationException.class) + public void testSaveHttpsAndMixedDomainsTogether() { + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build(), + DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(), + DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() + )) + .clientRegistrations(Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto(), + validClientRegistrationDto() + )) + .build() + )); + oAuth2Service.saveOAuth2Params(clientsParams); + } + @Test public void testCreateAndFindParams() { OAuth2ClientsParams clientsParams = createDefaultClientsParams(); @@ -178,7 +217,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { Assert.assertTrue(nonExistentDomainClients.isEmpty()); List firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain"); - Assert.assertEquals(firstDomainHttpClients.size(), firstDomainHttpClients.size()); + Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpClients.size()); firstGroupClientInfos.forEach(firstGroupClientInfo -> { Assert.assertTrue( firstDomainHttpClients.stream().anyMatch(clientInfo -> @@ -191,7 +230,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { Assert.assertTrue(firstDomainHttpsClients.isEmpty()); List fourthDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "fourth-domain"); - Assert.assertEquals(fourthDomainHttpClients.size(), secondGroupClientInfos.size()); + Assert.assertEquals(secondGroupClientInfos.size(), fourthDomainHttpClients.size()); secondGroupClientInfos.forEach(secondGroupClientInfo -> { Assert.assertTrue( fourthDomainHttpClients.stream().anyMatch(clientInfo -> @@ -200,7 +239,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { ); }); List fourthDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "fourth-domain"); - Assert.assertEquals(fourthDomainHttpsClients.size(), secondGroupClientInfos.size()); + Assert.assertEquals(secondGroupClientInfos.size(), fourthDomainHttpsClients.size()); secondGroupClientInfos.forEach(secondGroupClientInfo -> { Assert.assertTrue( fourthDomainHttpsClients.stream().anyMatch(clientInfo -> @@ -210,7 +249,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { }); List secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain"); - Assert.assertEquals(secondDomainHttpClients.size(), firstGroupClientInfos.size() + secondGroupClientInfos.size()); + Assert.assertEquals(firstGroupClientInfos.size() + secondGroupClientInfos.size(), secondDomainHttpClients.size()); firstGroupClientInfos.forEach(firstGroupClientInfo -> { Assert.assertTrue( secondDomainHttpClients.stream().anyMatch(clientInfo -> @@ -227,7 +266,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { }); List secondDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "second-domain"); - Assert.assertEquals(secondDomainHttpsClients.size(), firstGroupClientInfos.size() + thirdGroupClientInfos.size()); + Assert.assertEquals(firstGroupClientInfos.size() + thirdGroupClientInfos.size(), secondDomainHttpsClients.size()); firstGroupClientInfos.forEach(firstGroupClientInfo -> { Assert.assertTrue( secondDomainHttpsClients.stream().anyMatch(clientInfo -> @@ -244,6 +283,56 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { }); } + @Test + public void testGetOAuth2ClientsForHttpAndHttps() { + Set firstGroup = Sets.newHashSet( + validClientRegistrationDto(), + validClientRegistrationDto(), + validClientRegistrationDto(), + validClientRegistrationDto() + ); + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsDomainParams.builder() + .domainInfos(Sets.newHashSet( + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), + DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), + DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build() + )) + .clientRegistrations(firstGroup) + .build() + )); + + oAuth2Service.saveOAuth2Params(clientsParams); + OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params(); + Assert.assertNotNull(foundClientsParams); + Assert.assertEquals(clientsParams, foundClientsParams); + + List firstGroupClientInfos = firstGroup.stream() + .map(clientRegistrationDto -> new OAuth2ClientInfo( + clientRegistrationDto.getLoginButtonLabel(), clientRegistrationDto.getLoginButtonIcon(), null)) + .collect(Collectors.toList()); + + List firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain"); + Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpClients.size()); + firstGroupClientInfos.forEach(firstGroupClientInfo -> { + Assert.assertTrue( + firstDomainHttpClients.stream().anyMatch(clientInfo -> + clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) + && clientInfo.getName().equals(firstGroupClientInfo.getName())) + ); + }); + + List firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain"); + Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpsClients.size()); + firstGroupClientInfos.forEach(firstGroupClientInfo -> { + Assert.assertTrue( + firstDomainHttpsClients.stream().anyMatch(clientInfo -> + clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) + && clientInfo.getName().equals(firstGroupClientInfo.getName())) + ); + }); + } + @Test public void testGetDisabledOAuth2Clients() { OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( From 5dd460b81d616852ef1bd216a20aae0ca0fdc7c0 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 7 Oct 2020 14:13:25 +0300 Subject: [PATCH 135/463] Some improvements --- .../server/common/data/device/profile/MqttTopics.java | 9 ++++----- .../server/transport/mqtt/MqttTransportHandler.java | 7 +++++-- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java index 23c173ae97..24086e4194 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java @@ -40,7 +40,6 @@ public class MqttTopics { private static final String DEVICE_ATTRIBUTES_RESPONSE = ATTRIBUTES_RESPONSE + "/"; private static final String DEVICE_ATTRIBUTES_REQUEST = ATTRIBUTES_REQUEST + "/"; - private static final String PROVISION_RESPONSE = PROVISION + RESPONSE; // V1_JSON topics public static final String BASE_DEVICE_API_TOPIC = "v1/devices/me"; @@ -55,8 +54,8 @@ public class MqttTopics { public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + TELEMETRY; public static final String DEVICE_CLAIM_TOPIC = BASE_DEVICE_API_TOPIC + CLAIM; public static final String DEVICE_ATTRIBUTES_TOPIC = BASE_DEVICE_API_TOPIC + ATTRIBUTES; - public static final String DEVICE_PROVISION_REQUEST_TOPIC = BASE_DEVICE_API_TOPIC + PROVISION; - public static final String DEVICE_PROVISION_RESPONSE_TOPIC = BASE_DEVICE_API_TOPIC + PROVISION_RESPONSE; + public static final String DEVICE_PROVISION_REQUEST_TOPIC = PROVISION + REQUEST; + public static final String DEVICE_PROVISION_RESPONSE_TOPIC = PROVISION + RESPONSE; // V1_JSON gateway topics @@ -69,8 +68,8 @@ public class MqttTopics { public static final String GATEWAY_RPC_TOPIC = BASE_GATEWAY_API_TOPIC + RPC; public static final String GATEWAY_ATTRIBUTES_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES_REQUEST; public static final String GATEWAY_ATTRIBUTES_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES_RESPONSE; - public static final String GATEWAY_PROVISION_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + PROVISION; - public static final String GATEWAY_PROVISION_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + PROVISION_RESPONSE; + public static final String GATEWAY_PROVISION_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + PROVISION + REQUEST; + public static final String GATEWAY_PROVISION_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + PROVISION + RESPONSE; private MqttTopics() { } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index cabae348a2..82841d4307 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.mqtt; import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonParseException; import com.google.gson.JsonSyntaxException; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; @@ -158,7 +159,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg)); log.trace("[{}][{}] Processing provision publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId); } catch (Exception e) { - if (e.getCause().toString().contains("JsonSyntaxException")) { + if (e instanceof JsonParseException || (e.getCause() != null && e.getCause() instanceof JsonParseException)) { TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg); transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg)); deviceSessionCtx.setProvisionPayloadType(TransportPayloadType.PROTOBUF); @@ -334,6 +335,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } else { deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); } + + //TODO: close session with some delay. + //transportService.getScheduler().submit task with 60 seconds delay to close the session. } catch (Exception e) { log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); } @@ -448,7 +452,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED)); } else { X509Certificate cert; - if (sslHandler != null && (cert = getX509Certificate()) != null) { processX509CertConnect(ctx, cert); } else { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index c78d2b83cb..751ae10434 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -855,7 +855,7 @@ "provision-device-key": "Provision device key", "provision-device-key-required": "Provision device key is required.", "provision-device-secret": "Provision device secret", - "provision-device-secret-required": "Provision device secret is required." + "provision-device-secret-required": "Provision device secret is required.", "condition": "Condition", "condition-type": "Condition type", "condition-type-simple": "Simple", From 90234bf7a36e40c502e6650fd65d827b325bf38d Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 8 Oct 2020 11:37:04 +0300 Subject: [PATCH 136/463] Fixed merge --- .../main/data/upgrade/3.1.1/schema_update.sql | 89 ------------------- .../upgrade/3.1.2/schema_update_before.sql | 68 ++++++++++++++ .../install/ThingsboardInstallService.java | 4 - .../service/security/permission/Resource.java | 1 - ui-ngx/src/app/core/services/menu.service.ts | 1 + 5 files changed, 69 insertions(+), 94 deletions(-) delete mode 100644 application/src/main/data/upgrade/3.1.1/schema_update.sql diff --git a/application/src/main/data/upgrade/3.1.1/schema_update.sql b/application/src/main/data/upgrade/3.1.1/schema_update.sql deleted file mode 100644 index 0b99e797b0..0000000000 --- a/application/src/main/data/upgrade/3.1.1/schema_update.sql +++ /dev/null @@ -1,89 +0,0 @@ --- --- Copyright © 2016-2020 The Thingsboard Authors --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- - -DROP TABLE IF EXISTS oauth2_client_registration_info; - -CREATE TABLE IF NOT EXISTS oauth2_client_registration_info ( - id uuid NOT NULL CONSTRAINT oauth2_client_registration_info_pkey PRIMARY KEY, - enabled boolean, - created_time bigint NOT NULL, - additional_info varchar, - client_id varchar(255), - client_secret varchar(255), - authorization_uri varchar(255), - token_uri varchar(255), - scope varchar(255), - user_info_uri varchar(255), - user_name_attribute_name varchar(255), - jwk_set_uri varchar(255), - client_authentication_method varchar(255), - login_button_label varchar(255), - login_button_icon varchar(255), - allow_user_creation boolean, - activate_user boolean, - type varchar(31), - basic_email_attribute_key varchar(31), - basic_first_name_attribute_key varchar(31), - basic_last_name_attribute_key varchar(31), - basic_tenant_name_strategy varchar(31), - basic_tenant_name_pattern varchar(255), - basic_customer_name_pattern varchar(255), - basic_default_dashboard_name varchar(255), - basic_always_full_screen boolean, - custom_url varchar(255), - custom_username varchar(255), - custom_password varchar(255), - custom_send_token boolean -); - -DROP TABLE IF EXISTS oauth2_client_registration; - -CREATE TABLE IF NOT EXISTS oauth2_client_registration ( - id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, - created_time bigint NOT NULL, - domain_name varchar(255), - domain_scheme varchar(31), - client_registration_info_id uuid -); - -DROP TABLE IF EXISTS oauth2_client_registration_template; - -CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( - id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY, - created_time bigint NOT NULL, - additional_info varchar, - provider_id varchar(255), - authorization_uri varchar(255), - token_uri varchar(255), - scope varchar(255), - user_info_uri varchar(255), - user_name_attribute_name varchar(255), - jwk_set_uri varchar(255), - client_authentication_method varchar(255), - basic_email_attribute_key varchar(31), - basic_first_name_attribute_key varchar(31), - basic_last_name_attribute_key varchar(31), - basic_tenant_name_strategy varchar(31), - basic_tenant_name_pattern varchar(255), - basic_customer_name_pattern varchar(255), - basic_default_dashboard_name varchar(255), - basic_always_full_screen boolean, - comment varchar, - login_button_icon varchar(255), - login_button_label varchar(255), - help_link varchar(255), - CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) -); diff --git a/application/src/main/data/upgrade/3.1.2/schema_update_before.sql b/application/src/main/data/upgrade/3.1.2/schema_update_before.sql index c1591e7831..fe8ee4531b 100644 --- a/application/src/main/data/upgrade/3.1.2/schema_update_before.sql +++ b/application/src/main/data/upgrade/3.1.2/schema_update_before.sql @@ -14,6 +14,74 @@ -- limitations under the License. -- +CREATE TABLE IF NOT EXISTS oauth2_client_registration_info ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_info_pkey PRIMARY KEY, + enabled boolean, + created_time bigint NOT NULL, + additional_info varchar, + client_id varchar(255), + client_secret varchar(255), + authorization_uri varchar(255), + token_uri varchar(255), + scope varchar(255), + user_info_uri varchar(255), + user_name_attribute_name varchar(255), + jwk_set_uri varchar(255), + client_authentication_method varchar(255), + login_button_label varchar(255), + login_button_icon varchar(255), + allow_user_creation boolean, + activate_user boolean, + type varchar(31), + basic_email_attribute_key varchar(31), + basic_first_name_attribute_key varchar(31), + basic_last_name_attribute_key varchar(31), + basic_tenant_name_strategy varchar(31), + basic_tenant_name_pattern varchar(255), + basic_customer_name_pattern varchar(255), + basic_default_dashboard_name varchar(255), + basic_always_full_screen boolean, + custom_url varchar(255), + custom_username varchar(255), + custom_password varchar(255), + custom_send_token boolean +); + +CREATE TABLE IF NOT EXISTS oauth2_client_registration ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + created_time bigint NOT NULL, + domain_name varchar(255), + domain_scheme varchar(31), + client_registration_info_id uuid +); + +CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY, + created_time bigint NOT NULL, + additional_info varchar, + provider_id varchar(255), + authorization_uri varchar(255), + token_uri varchar(255), + scope varchar(255), + user_info_uri varchar(255), + user_name_attribute_name varchar(255), + jwk_set_uri varchar(255), + client_authentication_method varchar(255), + basic_email_attribute_key varchar(31), + basic_first_name_attribute_key varchar(31), + basic_last_name_attribute_key varchar(31), + basic_tenant_name_strategy varchar(31), + basic_tenant_name_pattern varchar(255), + basic_customer_name_pattern varchar(255), + basic_default_dashboard_name varchar(255), + basic_always_full_screen boolean, + comment varchar, + login_button_icon varchar(255), + login_button_label varchar(255), + help_link varchar(255), + CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) +); + CREATE TABLE IF NOT EXISTS device_profile ( id uuid NOT NULL CONSTRAINT device_profile_pkey PRIMARY KEY, created_time bigint NOT NULL, diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index c51bf905f8..6f6bd7a139 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -185,10 +185,6 @@ public class ThingsboardInstallService { databaseEntitiesUpgradeService.upgradeDatabase("3.1.2"); log.info("Updating system data..."); systemDataLoaderService.updateSystemWidgets(); - case "3.1.1": - log.info("Upgrading ThingsBoard from version 3.1.1 to 3.2.0 ..."); - databaseEntitiesUpgradeService.upgradeDatabase("3.1.1"); - log.info("Updating system data..."); systemDataLoaderService.createOAuth2Templates(); break; default: diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java index f96c526bfe..671e0539cc 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java @@ -34,7 +34,6 @@ public enum Resource { WIDGET_TYPE(EntityType.WIDGET_TYPE), OAUTH2_CONFIGURATION_INFO(EntityType.OAUTH2_CLIENT_REGISTRATION_INFO), OAUTH2_CONFIGURATION_TEMPLATE(EntityType.OAUTH2_CLIENT_REGISTRATION_TEMPLATE), - WIDGET_TYPE(EntityType.WIDGET_TYPE), TENANT_PROFILE(EntityType.TENANT_PROFILE), DEVICE_PROFILE(EntityType.DEVICE_PROFILE); diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index c921fde616..e44c3c6ec2 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -133,6 +133,7 @@ export class MenuService { icon: 'security' }, { + id: guid(), name: 'admin.oauth2.oauth2', type: 'link', path: '/settings/oauth2', From d2f45db0a66298669788a83024a194fdc595be43 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 8 Oct 2020 11:47:11 +0300 Subject: [PATCH 137/463] Removed unused changes to AttributeDao --- .../dao/attributes/AttributesService.java | 2 -- .../server/dao/attributes/AttributesDao.java | 2 -- .../dao/attributes/BaseAttributesService.java | 6 ---- .../dao/model/sql/AttributeKvEntity.java | 33 +++++++------------ .../sql/attributes/AttributeKvRepository.java | 4 --- .../dao/sql/attributes/JpaAttributeDao.java | 12 ------- 6 files changed, 12 insertions(+), 47 deletions(-) diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java index 75122aba57..d34cc157d0 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java @@ -36,8 +36,6 @@ public interface AttributesService { ListenableFuture> findAll(TenantId tenantId, EntityId entityId, String scope); - ListenableFuture> findAllByAttributeKey(String attributeKey); - ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes); ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, String scope, List attributeKeys); diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java index 85f6b2de44..83e705062c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java @@ -36,8 +36,6 @@ public interface AttributesDao { ListenableFuture> findAll(TenantId tenantId, EntityId entityId, String attributeType); - ListenableFuture> findAllByAttributeKey(String attributeKey); - ListenableFuture save(TenantId tenantId, EntityId entityId, String attributeType, AttributeKvEntry attribute); ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, String attributeType, List keys); diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java index 9956da347d..53cee47511 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java @@ -60,12 +60,6 @@ public class BaseAttributesService implements AttributesService { return attributesDao.findAll(tenantId, entityId, scope); } - @Override - public ListenableFuture> findAllByAttributeKey(String attributeKey) { - Validator.validateString(attributeKey, "Incorrect attribute key " + attributeKey); - return attributesDao.findAllByAttributeKey(attributeKey); - } - @Override public ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes) { validate(entityId, scope); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java index c652a67469..f0de269cea 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java @@ -16,10 +16,20 @@ package org.thingsboard.server.dao.model.sql; import lombok.Data; -import org.thingsboard.server.common.data.kv.*; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.BooleanDataEntry; +import org.thingsboard.server.common.data.kv.DoubleDataEntry; +import org.thingsboard.server.common.data.kv.JsonDataEntry; +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.dao.model.ToData; -import javax.persistence.*; +import javax.persistence.Column; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.Table; import java.io.Serializable; import static org.thingsboard.server.dao.model.ModelConstants.BOOLEAN_VALUE_COLUMN; @@ -32,7 +42,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.STRING_VALUE_COLUM @Data @Entity @Table(name = "attribute_kv") -// TODO maybe move ToData to local field as well (or implement ToData differently) public class AttributeKvEntity implements ToData, Serializable { @EmbeddedId @@ -73,22 +82,4 @@ public class AttributeKvEntity implements ToData, Serializable return new BaseAttributeKvEntry(kvEntry, lastUpdateTs); } - - @Transient - public final ToData toEntityAttributeKvEntry = () -> { - KvEntry kvEntry = null; - if (strValue != null) { - kvEntry = new StringDataEntry(id.getAttributeKey(), strValue); - } else if (booleanValue != null) { - kvEntry = new BooleanDataEntry(id.getAttributeKey(), booleanValue); - } else if (doubleValue != null) { - kvEntry = new DoubleDataEntry(id.getAttributeKey(), doubleValue); - } else if (longValue != null) { - kvEntry = new LongDataEntry(id.getAttributeKey(), longValue); - } else if (jsonValue != null) { - kvEntry = new JsonDataEntry(id.getAttributeKey(), jsonValue); - } - - return new BaseEntityAttributeKvEntry(id.getEntityId(), lastUpdateTs, kvEntry); - }; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java index 185113d8e4..f3ef44e6c7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java @@ -36,10 +36,6 @@ public interface AttributeKvRepository extends CrudRepository findAllByAttributeKey(@Param("attributeKey") String attributeKey); - @Transactional @Modifying @Query("DELETE FROM AttributeKvEntity a WHERE a.id.entityType = :entityType " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java index 449a7eef8a..5c89725494 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java @@ -137,18 +137,6 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl attributeType)))); } - @Override - public ListenableFuture> findAllByAttributeKey(String attributeKey) { - return Futures.immediateFuture( - DaoUtil.convertDataList( - attributeKvRepository.findAllByAttributeKey(attributeKey).stream() - .map(attributeKvEntity -> attributeKvEntity.toEntityAttributeKvEntry) - .collect(Collectors.toList()) - ) - - ); - } - @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, String attributeType, AttributeKvEntry attribute) { AttributeKvEntity entity = new AttributeKvEntity(); From 6ae75e295cf74e538bf499c969201825a290e4f1 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 8 Oct 2020 12:21:53 +0300 Subject: [PATCH 138/463] Removed LockService --- .../thingsboard/server/dao/lock/LockKey.java | 30 ------------- .../server/dao/lock/LockService.java | 25 ----------- .../server/dao/lock/LockServiceImpl.java | 45 ------------------- .../server/dao/sql/lock/LockRepository.java | 20 --------- .../dao/sql/lock/PsqlLockRepository.java | 40 ----------------- 5 files changed, 160 deletions(-) delete mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockKey.java delete mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockService.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/lock/LockServiceImpl.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/lock/LockRepository.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/lock/PsqlLockRepository.java diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockKey.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockKey.java deleted file mode 100644 index 5de01b3e2c..0000000000 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockKey.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.lock; - -public enum LockKey { - OAUTH2_CONFIG(999); - - private int id; - - LockKey(int id) { - this.id = id; - } - - public int getId() { - return id; - } -} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockService.java deleted file mode 100644 index 4ffd05233d..0000000000 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/lock/LockService.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.lock; - -public interface LockService { - /** - * Lock with SQL Database till the end of transaction - * - * @param key identifier - */ - void transactionLock(LockKey key); -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/lock/LockServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/lock/LockServiceImpl.java deleted file mode 100644 index ff1e052f97..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/lock/LockServiceImpl.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.lock; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.thingsboard.server.dao.sql.lock.LockRepository; - -import javax.annotation.PostConstruct; - -@Service -@Slf4j -public class LockServiceImpl implements LockService { - - @Autowired(required = false) - private LockRepository lockRepository; - - @PostConstruct - public void init(){ - if (lockRepository == null) { - log.warn("Locking with DB is not enabled."); - } - } - - @Override - public void transactionLock(LockKey key) { - if (lockRepository == null) return; - log.trace("Locking transaction key [{}]", key); - lockRepository.transactionLock(key.getId()); - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/lock/LockRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/lock/LockRepository.java deleted file mode 100644 index eced945acd..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/lock/LockRepository.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.sql.lock; - -public interface LockRepository { - void transactionLock(Integer key); -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/lock/PsqlLockRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/lock/PsqlLockRepository.java deleted file mode 100644 index 76c4b6ebc8..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/lock/PsqlLockRepository.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.sql.lock; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.stereotype.Repository; -import org.thingsboard.server.dao.util.PsqlDao; - -@Slf4j -@PsqlDao -@Repository -public class PsqlLockRepository implements LockRepository { - private static final String TRANSACTION_LOCK_QUERY = "SELECT pg_advisory_xact_lock(?)"; - - @Autowired - private JdbcTemplate jdbcTemplate; - - @Override - public void transactionLock(Integer key) { - jdbcTemplate.query(TRANSACTION_LOCK_QUERY, - preparedStatement -> preparedStatement.setInt(1, key), - resultSet -> {} - ); - } -} From c42c23c380005902d7605aad970d01e5381dc450 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 8 Oct 2020 12:22:26 +0300 Subject: [PATCH 139/463] Refactored and fixed code --- .../dao/attributes/AttributesService.java | 1 - .../dao/settings/AdminSettingsService.java | 2 +- .../data/kv/BaseEntityAttributeKvEntry.java | 39 ------------------- .../data/kv/EntityAttributeKvEntry.java | 23 ----------- .../server/dao/attributes/AttributesDao.java | 1 - .../dao/attributes/BaseAttributesService.java | 1 - .../dao/sql/attributes/JpaAttributeDao.java | 2 - .../server/dao/tenant/TenantServiceImpl.java | 4 -- .../resources/sql/hsql/drop-all-tables.sql | 2 +- .../resources/sql/psql/drop-all-tables.sql | 4 +- .../sql/timescale/drop-all-tables.sql | 4 +- 11 files changed, 7 insertions(+), 76 deletions(-) delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseEntityAttributeKvEntry.java delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/kv/EntityAttributeKvEntry.java diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java index d34cc157d0..3af4737f06 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java @@ -19,7 +19,6 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.kv.EntityAttributeKvEntry; import java.util.Collection; import java.util.List; diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java index a33b0c095f..a0c8d46d72 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java @@ -26,5 +26,5 @@ public interface AdminSettingsService { AdminSettings findAdminSettingsByKey(TenantId tenantId, String key); AdminSettings saveAdminSettings(TenantId tenantId, AdminSettings adminSettings); - + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseEntityAttributeKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseEntityAttributeKvEntry.java deleted file mode 100644 index 5c97acd90e..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseEntityAttributeKvEntry.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.common.data.kv; - -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.thingsboard.server.common.data.EntityType; - -import java.util.UUID; - -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class BaseEntityAttributeKvEntry extends BaseAttributeKvEntry implements EntityAttributeKvEntry { - - private final UUID entityId; - - public BaseEntityAttributeKvEntry(UUID entityId, long lastUpdateTs, KvEntry kv) { - super(kv, lastUpdateTs); - this.entityId = entityId; - } - - @Override - public UUID getEntityId() { - return entityId; - } -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/EntityAttributeKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/EntityAttributeKvEntry.java deleted file mode 100644 index ceb6c62a3c..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/EntityAttributeKvEntry.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.common.data.kv; - -import java.util.UUID; - -public interface EntityAttributeKvEntry extends AttributeKvEntry { - - UUID getEntityId(); -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java index 83e705062c..8ec43f49a5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java @@ -19,7 +19,6 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.kv.EntityAttributeKvEntry; import java.util.Collection; import java.util.List; diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java index 53cee47511..a6924960ca 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java @@ -23,7 +23,6 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.kv.EntityAttributeKvEntry; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.Validator; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java index 5c89725494..69616155d2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java @@ -24,12 +24,10 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.kv.*; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.attributes.AttributesDao; -import org.thingsboard.server.dao.model.ToData; import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey; import org.thingsboard.server.dao.model.sql.AttributeKvEntity; import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index 3f37339467..52761184fd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -35,7 +35,6 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; @@ -85,9 +84,6 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe @Autowired private RuleChainService ruleChainService; - @Autowired - private OAuth2Service oAuth2Service; - @Override public Tenant findTenantById(TenantId tenantId) { log.trace("Executing findTenantById [{}]", tenantId); diff --git a/dao/src/test/resources/sql/hsql/drop-all-tables.sql b/dao/src/test/resources/sql/hsql/drop-all-tables.sql index ac10fe8d26..160049a144 100644 --- a/dao/src/test/resources/sql/hsql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/hsql/drop-all-tables.sql @@ -18,12 +18,12 @@ DROP TABLE IF EXISTS ts_kv_latest; DROP TABLE IF EXISTS user_credentials; DROP TABLE IF EXISTS widget_type; DROP TABLE IF EXISTS widgets_bundle; +DROP TABLE IF EXISTS entity_view; DROP TABLE IF EXISTS device_profile; DROP TABLE IF EXISTS tenant_profile; DROP TABLE IF EXISTS rule_node_state; DROP TABLE IF EXISTS rule_node; DROP TABLE IF EXISTS rule_chain; -DROP TABLE IF EXISTS entity_view; DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS oauth2_client_registration_info; DROP TABLE IF EXISTS oauth2_client_registration_template; diff --git a/dao/src/test/resources/sql/psql/drop-all-tables.sql b/dao/src/test/resources/sql/psql/drop-all-tables.sql index 62eb884457..e277c39403 100644 --- a/dao/src/test/resources/sql/psql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/psql/drop-all-tables.sql @@ -18,13 +18,13 @@ DROP TABLE IF EXISTS ts_kv_dictionary; DROP TABLE IF EXISTS user_credentials; DROP TABLE IF EXISTS widget_type; DROP TABLE IF EXISTS widgets_bundle; +DROP TABLE IF EXISTS entity_view; DROP TABLE IF EXISTS device_profile; DROP TABLE IF EXISTS tenant_profile; DROP TABLE IF EXISTS rule_node_state; DROP TABLE IF EXISTS rule_node; DROP TABLE IF EXISTS rule_chain; -DROP TABLE IF EXISTS entity_view; +DROP TABLE IF EXISTS tb_schema_settings; DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS oauth2_client_registration_info; DROP TABLE IF EXISTS oauth2_client_registration_template; -DROP TABLE IF EXISTS tb_schema_settings; \ No newline at end of file diff --git a/dao/src/test/resources/sql/timescale/drop-all-tables.sql b/dao/src/test/resources/sql/timescale/drop-all-tables.sql index 48b944d7a3..da7eaff876 100644 --- a/dao/src/test/resources/sql/timescale/drop-all-tables.sql +++ b/dao/src/test/resources/sql/timescale/drop-all-tables.sql @@ -22,7 +22,9 @@ DROP TABLE IF EXISTS rule_node_state; DROP TABLE IF EXISTS rule_node; DROP TABLE IF EXISTS rule_chain; DROP TABLE IF EXISTS entity_view; +DROP TABLE IF EXISTS device_profile; +DROP TABLE IF EXISTS tenant_profile; +DROP TABLE IF EXISTS tb_schema_settings; DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS oauth2_client_registration_info; DROP TABLE IF EXISTS oauth2_client_registration_template; -DROP TABLE IF EXISTS tb_schema_settings; \ No newline at end of file From adf5069da1b0f3f8f66c98e01290c70259a3f67d Mon Sep 17 00:00:00 2001 From: zbeacon Date: Thu, 8 Oct 2020 15:08:29 +0300 Subject: [PATCH 140/463] Added ability to provision credentials type from device using provision feature --- .../device/DeviceProvisionServiceImpl.java | 70 ++++++++++++++++--- .../transport/DefaultTransportApiService.java | 8 ++- .../AbstractMqttProvisionProtoDeviceTest.java | 2 +- .../device/provision/ProvisionRequest.java | 5 +- .../server/common/data/DataConstants.java | 6 ++ .../ProvisionDeviceCredentialsData.java | 27 +++++++ .../data/device/profile/MqttTopics.java | 2 - common/queue/src/main/proto/queue.proto | 20 +++++- .../transport/mqtt/MqttTransportHandler.java | 21 +++--- .../mqtt/adaptors/JsonMqttAdaptor.java | 7 -- .../mqtt/adaptors/MqttTransportAdaptor.java | 2 - .../mqtt/adaptors/ProtoMqttAdaptor.java | 9 --- .../common/transport/TransportService.java | 4 ++ .../transport/adaptor/JsonConverter.java | 20 +++++- .../service/DefaultTransportService.java | 10 +-- 15 files changed, 165 insertions(+), 48 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/ProvisionDeviceCredentialsData.java diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 2e2f1cbb80..6f88cec33b 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.id.CustomerId; @@ -118,6 +119,13 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); } + if (provisionRequest.getCredentialsType() != null) { + ListenableFuture error = validateCredentials(provisionRequest); + if (error != null) { + return error; + } + } + DeviceProfile targetProfile = deviceProfileDao.findByProvisionDeviceKey(provisionRequestKey); if (targetProfile == null) { @@ -152,6 +160,32 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); } + private ListenableFuture validateCredentials(ProvisionRequest provisionRequest) { + switch (provisionRequest.getCredentialsType()) { + case ACCESS_TOKEN: + if (StringUtils.isEmpty(provisionRequest.getCredentialsData().getToken())) { + log.error("Failed to get token from credentials data!"); + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); + } + break; + case MQTT_BASIC: + if (StringUtils.isEmpty(provisionRequest.getCredentialsData().getClientId()) || + StringUtils.isEmpty(provisionRequest.getCredentialsData().getUsername()) || + StringUtils.isEmpty(provisionRequest.getCredentialsData().getPassword())) { + log.error("Failed to get basic mqtt credentials from credentials data!"); + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); + } + break; + case X509_CERTIFICATE: + if (StringUtils.isEmpty(provisionRequest.getCredentialsData().getHash())) { + log.error("Failed to get hash from credentials data!"); + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); + } + break; + } + return null; + } + private ListenableFuture processProvision(Device device, ProvisionRequest provisionRequest) { ListenableFuture> provisionStateFuture = attributesService.find(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, DEVICE_PROVISION_STATE); @@ -205,7 +239,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { return Futures.transform(saveProvisionStateAttribute(savedDevice), input -> new ProvisionResponse( - getDeviceCredentials(savedDevice, provisionRequest.getX509CertPubKey()), + getDeviceCredentials(savedDevice), ProvisionResponseStatus.SUCCESS), MoreExecutors.directExecutor()); } log.warn("[{}] The device is already provisioned!", device.getName()); @@ -224,17 +258,33 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { device.setName(provisionRequest.getDeviceName()); device.setType(profile.getName()); device.setTenantId(profile.getTenantId()); - return deviceService.saveDevice(device); + Device savedDevice = deviceService.saveDevice(device); + if (provisionRequest.getCredentialsType() != null) { + DeviceCredentials deviceCredentials = new DeviceCredentials(); + deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType()); + switch (provisionRequest.getCredentialsType()) { + case ACCESS_TOKEN: + deviceCredentials.setDeviceId(savedDevice.getId()); + deviceCredentials.setCredentialsId(provisionRequest.getCredentialsData().getToken()); + break; + case MQTT_BASIC: + BasicMqttCredentials mqttCredentials = new BasicMqttCredentials(); + mqttCredentials.setClientId(provisionRequest.getCredentialsData().getClientId()); + mqttCredentials.setUserName(provisionRequest.getCredentialsData().getUsername()); + mqttCredentials.setPassword(provisionRequest.getCredentialsData().getPassword()); + deviceCredentials.setCredentialsValue(JacksonUtil.toString(mqttCredentials)); + break; + case X509_CERTIFICATE: + deviceCredentials.setCredentialsValue(provisionRequest.getCredentialsData().getHash()); + break; + } + deviceCredentialsService.updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials); + } + return savedDevice; } - private DeviceCredentials getDeviceCredentials(Device device, String x509CertPubKey) { - DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); - if (!StringUtils.isEmpty(x509CertPubKey)) { - credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); - credentials.setCredentialsValue(x509CertPubKey); - return deviceCredentialsService.updateDeviceCredentials(device.getTenantId(), credentials); - } - return credentials; + private DeviceCredentials getDeviceCredentials(Device device) { + return deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); } private void pushProvisionEventToRuleEngine(ProvisionRequest request, Device device, String type) { diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 7b4701754b..b2c93a0ecb 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; +import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData; import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; @@ -281,7 +282,12 @@ public class DefaultTransportApiService implements TransportApiService { provisionResponseFuture = deviceProvisionService.provisionDevice( new ProvisionRequest( requestMsg.getDeviceName(), - requestMsg.getX509CertPubKey(), + requestMsg.getCredentialsType() != null ? DeviceCredentialsType.valueOf(requestMsg.getCredentialsType().name()) : null, + new ProvisionDeviceCredentialsData(requestMsg.getCredentialsDataProto().getValidateDeviceTokenRequestMsg().getToken(), + requestMsg.getCredentialsDataProto().getValidateBasicMqttCredRequestMsg().getClientId(), + requestMsg.getCredentialsDataProto().getValidateBasicMqttCredRequestMsg().getUserName(), + requestMsg.getCredentialsDataProto().getValidateBasicMqttCredRequestMsg().getPassword(), + requestMsg.getCredentialsDataProto().getValidateDeviceX509CertRequestMsg().getHash()), new ProvisionDeviceProfileCredentials( requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceKey(), requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret()))); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java index 7720ddad4a..54e99077ea 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java @@ -183,7 +183,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI protected byte[] createTestProvisionMessage() { - return ProvisionDeviceRequestMsg.newBuilder().setX509CertPubKey("").setDeviceName("Test Provision device").setProvisionDeviceCredentialsMsg(ProvisionDeviceCredentialsMsg.newBuilder().setProvisionDeviceKey("testProvisionKey").setProvisionDeviceSecret("testProvisionSecret")).build().toByteArray(); + return ProvisionDeviceRequestMsg.newBuilder().setDeviceName("Test Provision device").setProvisionDeviceCredentialsMsg(ProvisionDeviceCredentialsMsg.newBuilder().setProvisionDeviceKey("testProvisionKey").setProvisionDeviceSecret("testProvisionSecret")).build().toByteArray(); } } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java index 1c743fd855..f45b541074 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java @@ -17,12 +17,15 @@ package org.thingsboard.server.dao.device.provision; import lombok.AllArgsConstructor; import lombok.Data; +import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData; import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; @Data @AllArgsConstructor public class ProvisionRequest { private String deviceName; - private String x509CertPubKey; + private DeviceCredentialsType credentialsType; + private ProvisionDeviceCredentialsData credentialsData; private ProvisionDeviceProfileCredentials credentials; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index d2c4aaf60b..5aadca44ec 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -79,5 +79,11 @@ public class DataConstants { public static final String DEVICE_NAME = "deviceName"; public static final String DEVICE_TYPE = "deviceType"; public static final String CERT_PUB_KEY = "x509CertPubKey"; + public static final String CREDENTIALS_TYPE = "credentialsType"; + public static final String TOKEN = "token"; + public static final String HASH = "hash"; + public static final String CLIENT_ID = "clientId"; + public static final String USERNAME = "username"; + public static final String PASSWORD = "password"; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/ProvisionDeviceCredentialsData.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/ProvisionDeviceCredentialsData.java new file mode 100644 index 0000000000..a3e70afd83 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/ProvisionDeviceCredentialsData.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.credentials; + +import lombok.Data; + +@Data +public class ProvisionDeviceCredentialsData { + private final String token; + private final String clientId; + private final String username; + private final String password; + private final String hash; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java index 24086e4194..b226b87999 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java @@ -68,8 +68,6 @@ public class MqttTopics { public static final String GATEWAY_RPC_TOPIC = BASE_GATEWAY_API_TOPIC + RPC; public static final String GATEWAY_ATTRIBUTES_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES_REQUEST; public static final String GATEWAY_ATTRIBUTES_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES_RESPONSE; - public static final String GATEWAY_PROVISION_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + PROVISION + REQUEST; - public static final String GATEWAY_PROVISION_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + PROVISION + RESPONSE; private MqttTopics() { } diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 6a96f8a6d6..e6dc171550 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -76,6 +76,7 @@ enum KeyValueType { enum CredentialsType { ACCESS_TOKEN = 0; X509_CERTIFICATE = 1; + BASIC_MQTT = 2; } message KeyValueProto { @@ -254,10 +255,22 @@ message DeviceCredentialsProto { string credentialsValue = 5; } +message CredentialsDataProto { + ValidateDeviceTokenRequestMsg validateDeviceTokenRequestMsg = 1; + ValidateDeviceX509CertRequestMsg validateDeviceX509CertRequestMsg = 2; + ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 3; +} + message ProvisionDeviceRequestMsg { string deviceName = 1; - string x509CertPubKey = 2; + CredentialsType credentialsType = 2; ProvisionDeviceCredentialsMsg provisionDeviceCredentialsMsg = 3; + CredentialsDataProto credentialsDataProto = 4; +} + +message GatewayProvisionRequestMsg { + int32 requestId = 1; + ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 2; } message ProvisionDeviceCredentialsMsg { @@ -270,6 +283,11 @@ message ProvisionDeviceResponseMsg { ProvisionResponseStatus provisionResponseStatus = 2; } +message GatewayProvisionResponseMsg { + int32 requestId = 1; + ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 2; +} + enum ProvisionResponseStatus { UNKNOWN = 0; SUCCESS = 1; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 82841d4307..2ef4085ac0 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -17,7 +17,6 @@ package org.thingsboard.server.transport.mqtt; import com.fasterxml.jackson.databind.JsonNode; import com.google.gson.JsonParseException; -import com.google.gson.JsonSyntaxException; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.mqtt.MqttConnAckMessage; @@ -70,11 +69,10 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.Date; +import java.util.concurrent.TimeUnit; import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED; import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED; @@ -156,11 +154,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement if (topicName.equals(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC)) { try { TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = deviceSessionCtx.getContext().getJsonMqttAdaptor().convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg); + validateProvisionMessage(provisionRequestMsg); transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg)); log.trace("[{}][{}] Processing provision publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId); } catch (Exception e) { if (e instanceof JsonParseException || (e.getCause() != null && e.getCause() instanceof JsonParseException)) { TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg); + validateProvisionMessage(provisionRequestMsg); transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg)); deviceSessionCtx.setProvisionPayloadType(TransportPayloadType.PROTOBUF); log.trace("[{}][{}] Processing provision publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId); @@ -187,6 +187,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } + private void validateProvisionMessage(TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg) { + if (provisionRequestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceKey() != null && + provisionRequestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret() != null && + provisionRequestMsg.getDeviceName() != null) + throw new RuntimeException("Wrong credentials!"); + } + private void processRegularSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) { switch (msg.fixedHeader().messageType()) { case PUBLISH: @@ -335,9 +342,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } else { deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); } - - //TODO: close session with some delay. - //transportService.getScheduler().submit task with 60 seconds delay to close the session. + transportService.getSchedulerExecutor().schedule(() -> processDisconnect(ctx), 60, TimeUnit.SECONDS); } catch (Exception e) { log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); } @@ -379,7 +384,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement case MqttTopics.GATEWAY_ATTRIBUTES_TOPIC: case MqttTopics.GATEWAY_RPC_TOPIC: case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC: - case MqttTopics.GATEWAY_PROVISION_RESPONSE_TOPIC: case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC: registerSubQoS(topic, grantedQoSList, reqQoS); break; @@ -447,7 +451,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private void processConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) { log.info("[{}] Processing connect msg for client: {}!", sessionId, msg.payload().clientIdentifier()); String userName = msg.payload().userName(); - if (DataConstants.PROVISION.equals(userName)) { + String clientId = msg.payload().clientIdentifier(); + if (DataConstants.PROVISION.equals(userName) || DataConstants.PROVISION.equals(clientId)) { deviceSessionCtx.setProvisionOnly(true); ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED)); } else { diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java index 54f8075c24..0799d29c57 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java @@ -153,13 +153,6 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, JsonConverter.toJson(provisionResponse))); } - @Override - public Optional convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.ProvisionDeviceResponseMsg responseMsg) { - return Optional.of(createMqttPublishMsg(ctx, - MqttTopics.GATEWAY_PROVISION_RESPONSE_TOPIC, - JsonConverter.toGatewayJson(deviceName, responseMsg))); - } - public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException { String payload = validatePayload(sessionId, payloadData, false); try { diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java index c0f1dd2369..bcb2401ff7 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java @@ -69,6 +69,4 @@ public interface MqttTransportAdaptor { Optional convertToPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException; - Optional convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException; - } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java index d5e9599812..1a0d33cde4 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java @@ -187,15 +187,6 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_RPC_TOPIC, payloadBytes)); } - @Override - public Optional convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.ProvisionDeviceResponseMsg responseMsg) throws AdaptorException { - TransportApiProtos.GatewayProvisionResponseMsg.Builder builder = TransportApiProtos.GatewayProvisionResponseMsg.newBuilder(); - builder.setDeviceName(deviceName); - builder.setProvisionDeviceResponseMsg(responseMsg); - byte[] payloadBytes = builder.build().toByteArray(); - return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_PROVISION_RESPONSE_TOPIC, payloadBytes)); - } - public static byte[] toBytes(ByteBuf inbound) { byte[] bytes = new byte[inbound.readableBytes()]; int readerIndex = inbound.readerIndex(); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 995b9eca91..775a91f720 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -40,6 +40,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCre import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; +import java.util.concurrent.ScheduledExecutorService; + /** * Created by ashvayka on 04.10.18. */ @@ -88,6 +90,8 @@ public interface TransportService { void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback callback); + ScheduledExecutorService getSchedulerExecutor(); + void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener); void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index cd070e6927..a2cba83360 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -37,6 +37,8 @@ import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.CredentialsDataProto.Builder; +import org.thingsboard.server.gen.transport.TransportProtos.CredentialsType; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto; import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType; @@ -46,7 +48,11 @@ import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRespo import org.thingsboard.server.gen.transport.TransportProtos.ProvisionResponseStatus; import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; +import javax.xml.crypto.Data; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -554,7 +560,17 @@ public class JsonConverter { private static TransportProtos.ProvisionDeviceRequestMsg buildProvisionRequestMsg(JsonObject jo) { return TransportProtos.ProvisionDeviceRequestMsg.newBuilder() .setDeviceName(getStrValue(jo, DataConstants.DEVICE_NAME, true)) - .setX509CertPubKey(getStrValue(jo, DataConstants.CERT_PUB_KEY, false)) + .setCredentialsType(TransportProtos.CredentialsType.valueOf(getStrValue(jo, DataConstants.CREDENTIALS_TYPE, false))) + .setCredentialsDataProto(TransportProtos.CredentialsDataProto.newBuilder() + .setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken(getStrValue(jo, DataConstants.TOKEN, false)).build()) + .setValidateBasicMqttCredRequestMsg(ValidateBasicMqttCredRequestMsg.newBuilder() + .setClientId(getStrValue(jo, DataConstants.CLIENT_ID, false)) + .setUserName(getStrValue(jo, DataConstants.USERNAME, false)) + .setPassword(getStrValue(jo, DataConstants.PASSWORD, false)) + .build()) + .setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder() + .setHash(getStrValue(jo, DataConstants.PASSWORD, false)).build()) + .build()) .setProvisionDeviceCredentialsMsg(buildProvisionDeviceCredentialsMsg( getStrValue(jo, DataConstants.PROVISION_KEY, true), getStrValue(jo, DataConstants.PROVISION_SECRET, true))) @@ -567,6 +583,8 @@ public class JsonConverter { .setProvisionDeviceSecret(provisionSecret) .build(); } + + private static String getStrValue(JsonObject jo, String field, boolean requiredField) { if (jo.has(field)) { return jo.get(field).getAsString(); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 58b06e696e..47abc07286 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -34,7 +34,6 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceQueue; import org.thingsboard.server.common.msg.queue.ServiceType; @@ -49,7 +48,6 @@ import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; @@ -77,11 +75,9 @@ import org.thingsboard.server.common.stats.StatsType; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -93,7 +89,6 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; /** * Created by ashvayka on 17.10.18. @@ -236,6 +231,11 @@ public class DefaultTransportService implements TransportService { } } + @Override + public ScheduledExecutorService getSchedulerExecutor(){ + return this.schedulerExecutor; + } + @Override public void registerAsyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener) { sessions.putIfAbsent(toSessionId(sessionInfo), new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listener)); From ea9d18e32dbad351d723b0cfcc507c9029b773b8 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 8 Oct 2020 15:59:51 +0300 Subject: [PATCH 141/463] Removed upper-case from providerId --- .../app/modules/home/pages/admin/oauth2-settings.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html index fc8e294774..30564e9306 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -156,7 +156,7 @@ admin.oauth2.login-provider - {{ provider | uppercase }} + {{ provider }} From e2791a289b8f4af35cbeb97c43e1095072e4cdda Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 8 Oct 2020 16:04:03 +0300 Subject: [PATCH 142/463] Removed EntityId for OAuth2Config --- .../server/controller/BaseController.java | 3 --- .../OAuth2ConfigTemplateController.java | 17 +++++------------ .../server/controller/OAuth2Controller.java | 4 ++++ .../service/security/permission/Resource.java | 4 ++-- .../server/common/data/id/EntityIdFactory.java | 4 ---- .../data/id/OAuth2ClientRegistrationId.java | 8 +------- .../data/id/OAuth2ClientRegistrationInfoId.java | 8 +------- .../id/OAuth2ClientRegistrationTemplateId.java | 8 +------- 8 files changed, 14 insertions(+), 42 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index f17185dcd0..87ddfbb088 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -431,9 +431,6 @@ public abstract class BaseController { case WIDGET_TYPE: checkWidgetTypeId(new WidgetTypeId(entityId.getId()), operation); return; - case OAUTH2_CLIENT_REGISTRATION_INFO: - case OAUTH2_CLIENT_REGISTRATION_TEMPLATE: - return; default: throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType()); } diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java index 1cb969e9e6..35ed01f03e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2ConfigTemplateController.java @@ -25,6 +25,8 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; import java.util.List; @@ -40,6 +42,7 @@ public class OAuth2ConfigTemplateController extends BaseController { @ResponseStatus(value = HttpStatus.OK) public OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(@RequestBody OAuth2ClientRegistrationTemplate clientRegistrationTemplate) throws ThingsboardException { try { + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, Operation.WRITE); return oAuth2ConfigTemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate); } catch (Exception e) { throw handleException(e); @@ -52,21 +55,10 @@ public class OAuth2ConfigTemplateController extends BaseController { public void deleteClientRegistrationTemplate(@PathVariable(CLIENT_REGISTRATION_TEMPLATE_ID) String strClientRegistrationTemplateId) throws ThingsboardException { checkParameter(CLIENT_REGISTRATION_TEMPLATE_ID, strClientRegistrationTemplateId); try { + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, Operation.DELETE); OAuth2ClientRegistrationTemplateId clientRegistrationTemplateId = new OAuth2ClientRegistrationTemplateId(toUUID(strClientRegistrationTemplateId)); oAuth2ConfigTemplateService.deleteClientRegistrationTemplateById(clientRegistrationTemplateId); - - logEntityAction(clientRegistrationTemplateId, - null, - null, - ActionType.DELETED, null, strClientRegistrationTemplateId); - } catch (Exception e) { - - logEntityAction(emptyId(EntityType.OAUTH2_CLIENT_REGISTRATION_TEMPLATE), - null, - null, - ActionType.DELETED, e, strClientRegistrationTemplateId); - throw handleException(e); } } @@ -76,6 +68,7 @@ public class OAuth2ConfigTemplateController extends BaseController { @ResponseBody public List getClientRegistrationTemplates() throws ThingsboardException { try { + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_TEMPLATE, Operation.READ); return oAuth2ConfigTemplateService.findAllClientRegistrationTemplates(); } catch (Exception e) { throw handleException(e); diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index bb3a87ef92..ecd74bef39 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -24,6 +24,8 @@ import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; import org.thingsboard.server.common.data.oauth2.SchemeType; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.utils.MiscUtils; import javax.servlet.http.HttpServletRequest; @@ -49,6 +51,7 @@ public class OAuth2Controller extends BaseController { @ResponseBody public OAuth2ClientsParams getCurrentOAuth2Params() throws ThingsboardException { try { + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ); return oAuth2Service.findOAuth2Params(); } catch (Exception e) { throw handleException(e); @@ -60,6 +63,7 @@ public class OAuth2Controller extends BaseController { @ResponseStatus(value = HttpStatus.OK) public OAuth2ClientsParams saveOAuth2Params(@RequestBody OAuth2ClientsParams oauth2Params) throws ThingsboardException { try { + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.WRITE); oAuth2Service.saveOAuth2Params(oauth2Params); return oAuth2Service.findOAuth2Params(); } catch (Exception e) { diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java index 671e0539cc..08534dc2d4 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java @@ -32,8 +32,8 @@ public enum Resource { USER(EntityType.USER), WIDGETS_BUNDLE(EntityType.WIDGETS_BUNDLE), WIDGET_TYPE(EntityType.WIDGET_TYPE), - OAUTH2_CONFIGURATION_INFO(EntityType.OAUTH2_CLIENT_REGISTRATION_INFO), - OAUTH2_CONFIGURATION_TEMPLATE(EntityType.OAUTH2_CLIENT_REGISTRATION_TEMPLATE), + OAUTH2_CONFIGURATION_INFO(), + OAUTH2_CONFIGURATION_TEMPLATE(), TENANT_PROFILE(EntityType.TENANT_PROFILE), DEVICE_PROFILE(EntityType.DEVICE_PROFILE); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java index 8113c41945..dc1589814f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java @@ -62,10 +62,6 @@ public class EntityIdFactory { return new WidgetsBundleId(uuid); case WIDGET_TYPE: return new WidgetTypeId(uuid); - case OAUTH2_CLIENT_REGISTRATION_INFO: - return new OAuth2ClientRegistrationInfoId(uuid); - case OAUTH2_CLIENT_REGISTRATION_TEMPLATE: - return new OAuth2ClientRegistrationTemplateId(uuid); case DEVICE_PROFILE: return new DeviceProfileId(uuid); case TENANT_PROFILE: diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationId.java index 3e4ad56f19..e87d9c7b2e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationId.java @@ -17,11 +17,10 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -public class OAuth2ClientRegistrationId extends UUIDBased implements EntityId { +public class OAuth2ClientRegistrationId extends UUIDBased { @JsonCreator public OAuth2ClientRegistrationId(@JsonProperty("id") UUID id) { @@ -31,9 +30,4 @@ public class OAuth2ClientRegistrationId extends UUIDBased implements EntityId { public static OAuth2ClientRegistrationId fromString(String clientRegistrationId) { return new OAuth2ClientRegistrationId(UUID.fromString(clientRegistrationId)); } - - @Override - public EntityType getEntityType() { - return EntityType.OAUTH2_CLIENT_REGISTRATION; - } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationInfoId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationInfoId.java index 999f0d5816..87a2109909 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationInfoId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationInfoId.java @@ -17,11 +17,10 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -public class OAuth2ClientRegistrationInfoId extends UUIDBased implements EntityId { +public class OAuth2ClientRegistrationInfoId extends UUIDBased { @JsonCreator public OAuth2ClientRegistrationInfoId(@JsonProperty("id") UUID id) { @@ -31,9 +30,4 @@ public class OAuth2ClientRegistrationInfoId extends UUIDBased implements EntityI public static OAuth2ClientRegistrationInfoId fromString(String clientRegistrationInfoId) { return new OAuth2ClientRegistrationInfoId(UUID.fromString(clientRegistrationInfoId)); } - - @Override - public EntityType getEntityType() { - return EntityType.OAUTH2_CLIENT_REGISTRATION_INFO; - } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationTemplateId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationTemplateId.java index 03edd5054b..cc6ee3a795 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationTemplateId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationTemplateId.java @@ -17,11 +17,10 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -public class OAuth2ClientRegistrationTemplateId extends UUIDBased implements EntityId { +public class OAuth2ClientRegistrationTemplateId extends UUIDBased { @JsonCreator public OAuth2ClientRegistrationTemplateId(@JsonProperty("id") UUID id) { @@ -31,9 +30,4 @@ public class OAuth2ClientRegistrationTemplateId extends UUIDBased implements Ent public static OAuth2ClientRegistrationTemplateId fromString(String clientRegistrationTemplateId) { return new OAuth2ClientRegistrationTemplateId(UUID.fromString(clientRegistrationTemplateId)); } - - @Override - public EntityType getEntityType() { - return EntityType.OAUTH2_CLIENT_REGISTRATION_TEMPLATE; - } } From 6df92197900df6cb750920100eebc0b1d2265354 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Thu, 8 Oct 2020 16:13:45 +0300 Subject: [PATCH 143/463] Improvements for provisioning --- .../device/DeviceProvisionServiceImpl.java | 15 +++++++++------ .../transport/mqtt/MqttTransportHandler.java | 11 +---------- .../common/transport/adaptor/JsonConverter.java | 6 ++---- .../common/transport/adaptor/ProtoConverter.java | 3 --- 4 files changed, 12 insertions(+), 23 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 6f88cec33b..e03f5e9493 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -38,7 +38,6 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceType; @@ -134,9 +133,9 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { Device targetDevice = deviceDao.findDeviceByTenantIdAndName(targetProfile.getTenantId().getId(), provisionRequest.getDeviceName()).orElse(null); - switch(targetProfile.getProvisionType()) { + switch (targetProfile.getProvisionType()) { case ALLOW_CREATE_NEW_DEVICES: - if (((AllowCreateNewDevicesDeviceProfileProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration()).getProvisionDeviceSecret().equals(provisionRequestSecret)){ + if (((AllowCreateNewDevicesDeviceProfileProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration()).getProvisionDeviceSecret().equals(provisionRequestSecret)) { if (targetDevice != null) { log.warn("[{}] The device is present and could not be provisioned once more!", targetDevice.getName()); notify(targetDevice, provisionRequest, DataConstants.PROVISION_FAILURE, false); @@ -170,8 +169,8 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { break; case MQTT_BASIC: if (StringUtils.isEmpty(provisionRequest.getCredentialsData().getClientId()) || - StringUtils.isEmpty(provisionRequest.getCredentialsData().getUsername()) || - StringUtils.isEmpty(provisionRequest.getCredentialsData().getPassword())) { + StringUtils.isEmpty(provisionRequest.getCredentialsData().getUsername()) || + StringUtils.isEmpty(provisionRequest.getCredentialsData().getPassword())) { log.error("Failed to get basic mqtt credentials from credentials data!"); return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); } @@ -259,7 +258,11 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { device.setType(profile.getName()); device.setTenantId(profile.getTenantId()); Device savedDevice = deviceService.saveDevice(device); - if (provisionRequest.getCredentialsType() != null) { + if (!StringUtils.isEmpty(provisionRequest.getCredentialsData().getToken()) || + !StringUtils.isEmpty(provisionRequest.getCredentialsData().getHash()) || + !StringUtils.isEmpty(provisionRequest.getCredentialsData().getUsername()) || + !StringUtils.isEmpty(provisionRequest.getCredentialsData().getPassword()) || + !StringUtils.isEmpty(provisionRequest.getCredentialsData().getClientId())) { DeviceCredentials deviceCredentials = new DeviceCredentials(); deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType()); switch (provisionRequest.getCredentialsType()) { diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 2ef4085ac0..fab0579bb2 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -154,13 +154,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement if (topicName.equals(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC)) { try { TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = deviceSessionCtx.getContext().getJsonMqttAdaptor().convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg); - validateProvisionMessage(provisionRequestMsg); transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg)); log.trace("[{}][{}] Processing provision publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId); } catch (Exception e) { if (e instanceof JsonParseException || (e.getCause() != null && e.getCause() instanceof JsonParseException)) { TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg); - validateProvisionMessage(provisionRequestMsg); transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg)); deviceSessionCtx.setProvisionPayloadType(TransportPayloadType.PROTOBUF); log.trace("[{}][{}] Processing provision publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId); @@ -187,13 +185,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } - private void validateProvisionMessage(TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg) { - if (provisionRequestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceKey() != null && - provisionRequestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret() != null && - provisionRequestMsg.getDeviceName() != null) - throw new RuntimeException("Wrong credentials!"); - } - private void processRegularSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) { switch (msg.fixedHeader().messageType()) { case PUBLISH: @@ -493,7 +484,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate cert) { try { - if(!context.isSkipValidityCheckForClientCert()){ + if (!context.isSkipValidityCheckForClientCert()) { cert.checkValidity(); } String strCert = SslUtil.getX509CertificateString(cert); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index a2cba83360..dd6938e17f 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -37,7 +37,6 @@ import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; -import org.thingsboard.server.gen.transport.TransportProtos.CredentialsDataProto.Builder; import org.thingsboard.server.gen.transport.TransportProtos.CredentialsType; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto; @@ -52,7 +51,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCre import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; -import javax.xml.crypto.Data; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -560,7 +558,7 @@ public class JsonConverter { private static TransportProtos.ProvisionDeviceRequestMsg buildProvisionRequestMsg(JsonObject jo) { return TransportProtos.ProvisionDeviceRequestMsg.newBuilder() .setDeviceName(getStrValue(jo, DataConstants.DEVICE_NAME, true)) - .setCredentialsType(TransportProtos.CredentialsType.valueOf(getStrValue(jo, DataConstants.CREDENTIALS_TYPE, false))) + .setCredentialsType(jo.get(DataConstants.CREDENTIALS_TYPE) != null ? TransportProtos.CredentialsType.valueOf(getStrValue(jo, DataConstants.CREDENTIALS_TYPE, false)) : CredentialsType.ACCESS_TOKEN) .setCredentialsDataProto(TransportProtos.CredentialsDataProto.newBuilder() .setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken(getStrValue(jo, DataConstants.TOKEN, false)).build()) .setValidateBasicMqttCredRequestMsg(ValidateBasicMqttCredRequestMsg.newBuilder() @@ -570,7 +568,7 @@ public class JsonConverter { .build()) .setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder() .setHash(getStrValue(jo, DataConstants.PASSWORD, false)).build()) - .build()) + .build()) .setProvisionDeviceCredentialsMsg(buildProvisionDeviceCredentialsMsg( getStrValue(jo, DataConstants.PROVISION_KEY, true), getStrValue(jo, DataConstants.PROVISION_SECRET, true))) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java index 26e7a9cf9e..2c8ca7e3b5 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java @@ -15,10 +15,7 @@ */ package org.thingsboard.server.common.transport.adaptor; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import com.google.gson.JsonSyntaxException; import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; import org.springframework.util.CollectionUtils; From aa87ab024e3ea513921f2be584d61137850e2cc2 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Thu, 8 Oct 2020 16:21:04 +0300 Subject: [PATCH 144/463] Change widget json config --- .../system/widget_bundles/input_widgets.json | 4 ++-- .../lib/multiple-input-widget.component.html | 4 ++-- .../lib/multiple-input-widget.component.ts | 23 ++++++++----------- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/input_widgets.json b/application/src/main/data/json/system/widget_bundles/input_widgets.json index 1d5b28cba6..ac3486223a 100644 --- a/application/src/main/data/json/system/widget_bundles/input_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/input_widgets.json @@ -32,8 +32,8 @@ "templateHtml": "\n", "templateCss": ".tb-toast {\n min-width: 0;\n font-size: 14px !important;\n}", "controllerScript": "self.onInit = function() {\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n self.ctx.$scope.multipleInputWidget.onDataUpdated();\r\n}\r\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"MultipleInput\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showActionButtons\":{\n \"title\":\"Show action buttons\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"updateAllValues\": {\n \"title\":\"Update all values, not only modified (only if action buttons are visible)\",\n \"type\":\"boolean\",\n \"default\": false\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"showGroupTitle\": {\n \"title\":\"Show title for group of fields, related to different entities\",\n \"type\":\"boolean\",\n \"default\": false\n },\n \"groupTitle\": {\n \"title\": \"Group title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"fieldsAlignment\": {\n \"title\": \"Fields alignment\",\n \"type\": \"string\",\n \"default\": \"row\"\n },\n \"fieldsInRow\": {\n \"title\": \"Number of fields in the row\",\n \"type\": \"number\",\n \"default\": \"2\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showActionButtons\",\n \"updateAllValues\",\n \"showResultMessage\",\n \"showGroupTitle\",\n \"groupTitle\",\n {\n \"key\": \"fieldsAlignment\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"row\",\n \"label\": \"Row (default)\"\n },\n {\n \"value\": \"column\",\n \"label\": \"Column\"\n }\n ]\n },\n \"fieldsInRow\"\n ]\n}", - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"dataKeyType\": {\n \"title\": \"Datakey type\",\n \"type\": \"string\",\n \"default\": \"server\"\n },\n \"dataKeyValueType\": {\n \"title\": \"Datakey value type\",\n \"type\": \"string\",\n \"default\": \"string\"\n },\n \"required\": {\n \"title\": \"Value is required\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"isEditable\": {\n \"title\": \"Ability to edit attribute\",\n \"type\": \"string\",\n \"default\": \"editable\"\n },\n \"disabledOnDataKey\": {\n \"title\": \"Disable on false value of another datakey (specify datakey name)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"dataKeyHidden\": {\n \"title\": \"Hide input field\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"step\": {\n \"title\": \"Step interval between values (only for numbers)\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"icon\": {\n \"title\": \"Icon to show before input cell\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"dataKeyType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"server\",\n \"label\": \"Server attribute (default)\"\n },\n {\n \"value\": \"shared\",\n \"label\": \"Shared attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Timeseries\"\n }\n ]\n },\n {\n \"key\": \"dataKeyValueType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"string\",\n \"label\": \"String\"\n },\n {\n \"value\": \"double\",\n \"label\": \"Double\"\n },\n {\n \"value\": \"integer\",\n \"label\": \"Integer\"\n },\n {\n \"value\": \"booleanCheckbox\",\n \"label\": \"Boolean (Checkbox)\"\n },\n {\n \"value\": \"booleanSwitch\",\n \"label\": \"Boolean (Switch)\"\n },\n {\n \"value\": \"dateTime\",\n \"label\": \"Date & Time\"\n },\n {\n \"value\": \"date\",\n \"label\": \"Date\"\n },\n {\n \"value\": \"time\",\n \"label\": \"Time\"\n }\n ]\n },\n \"required\",\n {\n \"key\": \"isEditable\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"editable\",\n \"label\": \"Editable (default)\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n },\n {\n \"value\": \"readonly\",\n \"label\": \"Read-only\"\n }\n ]\n },\n \"disabledOnDataKey\",\n \"dataKeyHidden\",\n \"step\",\n \"requiredErrorMessage\",\n\t\t{\n \t\t\"key\": \"icon\",\n\t\t\t\"type\": \"icon\"\n\t\t}\n ]\n}\n", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"MultipleInput\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showActionButtons\":{\n \"title\":\"Show action buttons\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"updateAllValues\": {\n \"title\":\"Update all values, not only modified\",\n \"type\":\"boolean\",\n \"default\": false\n },\n \"saveButtonLabel\": {\n \"title\": \"'SAVE' button label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"resetButtonLabel\": {\n \"title\": \"'UNDO' button label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"showGroupTitle\": {\n \"title\":\"Show title for group of fields, related to different entities\",\n \"type\":\"boolean\",\n \"default\": false\n },\n \"groupTitle\": {\n \"title\": \"Group title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"fieldsAlignment\": {\n \"title\": \"Fields alignment\",\n \"type\": \"string\",\n \"default\": \"row\"\n },\n \"fieldsInRow\": {\n \"title\": \"Number of fields in the row\",\n \"type\": \"number\",\n \"default\": \"2\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showActionButtons\",\n {\n \"key\": \"updateAllValues\",\n \"condition\": \"model.showActionButtons === true\"\n },\n {\n \"key\": \"saveButtonLabel\",\n \"condition\": \"model.showActionButtons === true\"\n },\n {\n \"key\": \"resetButtonLabel\",\n \"condition\": \"model.showActionButtons === true\"\n },\n \"showResultMessage\",\n \"showGroupTitle\",\n \"groupTitle\",\n {\n \"key\": \"fieldsAlignment\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"row\",\n \"label\": \"Row (default)\"\n },\n {\n \"value\": \"column\",\n \"label\": \"Column\"\n }\n ]\n },\n {\n \"key\": \"fieldsInRow\",\n \"condition\": \"model.fieldsAlignment === 'row'\"\n }\n ]\n}", + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"dataKeyType\": {\n \"title\": \"Datakey type\",\n \"type\": \"string\",\n \"default\": \"server\"\n },\n \"dataKeyValueType\": {\n \"title\": \"Datakey value type\",\n \"type\": \"string\",\n \"default\": \"string\"\n },\n \"step\": {\n \"title\": \"Step interval between values\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\"\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\"\n },\n \"required\": {\n \"title\": \"Value is required\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"minValueErrorMessage\": {\n \"title\": \"'Min Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValueErrorMessage\": {\n \"title\": \"'Max Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"invalidDateErrorMessage\": {\n \"title\": \"'Invalid Date' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"selectOptionsType\": {\n \"title\": \"Select options type\",\n \"type\": \"string\",\n \"default\": \"valueWithLabel\"\n },\n \"selectOptionsList\": {\n \"title\": \"Select options list\",\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"value\": {\n \"title\": \"Value\",\n \"type\": \"string\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n }\n },\n \"required\": [\"value\", \"label\"]\n }\n },\n \"isEditable\": {\n \"title\": \"Ability to edit attribute\",\n \"type\": \"string\",\n \"default\": \"editable\"\n },\n \"disabledOnDataKey\": {\n \"title\": \"Disable on false value of another datakey (specify datakey name)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"dataKeyHidden\": {\n \"title\": \"Hide input field\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"icon\": {\n \"title\": \"Icon to show before input cell\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"dataKeyType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"server\",\n \"label\": \"Server attribute (default)\"\n },\n {\n \"value\": \"shared\",\n \"label\": \"Shared attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Timeseries\"\n }\n ]\n },\n {\n \"key\": \"dataKeyValueType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"string\",\n \"label\": \"String\"\n },\n {\n \"value\": \"double\",\n \"label\": \"Double\"\n },\n {\n \"value\": \"integer\",\n \"label\": \"Integer\"\n },\n {\n \"value\": \"booleanCheckbox\",\n \"label\": \"Boolean (Checkbox)\"\n },\n {\n \"value\": \"booleanSwitch\",\n \"label\": \"Boolean (Switch)\"\n },\n {\n \"value\": \"dateTime\",\n \"label\": \"Date & Time\"\n },\n {\n \"value\": \"date\",\n \"label\": \"Date\"\n },\n {\n \"value\": \"time\",\n \"label\": \"Time\"\n },\n {\n \"value\": \"selectOption\",\n \"label\": \"Selectable option\"\n }\n ]\n },\n {\n \"key\": \"selectOptionsType\",\n \"condition\": \"model.dataKeyValueType === 'selectOption'\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"valueWithLabel\",\n \"label\": \"Values with labels\"\n },\n {\n \"value\": \"rawValue\",\n \"label\": \"Raw values\"\n }\n ]\n },\n {\n \"key\": \"selectOptionsList\",\n \"type\": \"array\",\n \"condition\": \"model.dataKeyValueType === 'selectOption'\",\n \"items\": [\n \"selectOptionsList[].value\",\n {\n \"key\": \"selectOptionsList[].label\",\n \"condition\": \"model.selectOptionsType === 'valueWithLabel'\"\n }\n ]\n },\n {\n \"key\": \"step\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"minValue\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"maxValue\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n \"required\",\n {\n \"key\": \"requiredErrorMessage\",\n \"condition\": \"model.required === true\"\n },\n {\n \"key\": \"invalidDateErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'dateTime' || model.dataKeyValueType === 'date' || model.dataKeyValueType === 'time'\"\n },\n {\n \"key\": \"minValueErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"maxValueErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"isEditable\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"editable\",\n \"label\": \"Editable (default)\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n },\n {\n \"value\": \"readonly\",\n \"label\": \"Read-only\"\n }\n ]\n },\n \"disabledOnDataKey\",\n \"dataKeyHidden\",\n\t\t{\n \t\t\"key\": \"icon\",\n\t\t\t\"type\": \"icon\"\n\t\t}\n ]\n}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update Multiple Attributes\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } }, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html index 6d2b4d4a30..11666f82f6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html @@ -116,12 +116,12 @@
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts index 361b1a06e9..63b461cc24 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts @@ -111,7 +111,8 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni isVerticalAlignment: boolean; inputWidthSettings: string; changeAlignment: boolean; - smallWidthContainer: boolean; + saveButtonLabel: string; + resetButtonLabel: string; entityDetected = false; isAllParametersValid = true; @@ -165,14 +166,14 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni this.settings.groupTitle = this.settings.groupTitle || '${entityName}'; if (this.settings.saveButtonLabel && this.settings.saveButtonLabel.length) { - this.settings.saveButtonLabel = this.utils.customTranslation(this.settings.saveButtonLabel, this.settings.saveButtonLabel); + this.saveButtonLabel = this.utils.customTranslation(this.settings.saveButtonLabel, this.settings.saveButtonLabel); } else { - this.settings.saveButtonLabel = this.translate.instant('action.save'); + this.saveButtonLabel = this.translate.instant('action.save'); } if (this.settings.resetButtonLabel && this.settings.resetButtonLabel.length) { - this.settings.resetButtonLabel = this.utils.customTranslation(this.settings.resetButtonLabel, this.settings.resetButtonLabel); + this.resetButtonLabel = this.utils.customTranslation(this.settings.resetButtonLabel, this.settings.resetButtonLabel); } else { - this.settings.resetButtonLabel = this.translate.instant('action.undo'); + this.resetButtonLabel = this.translate.instant('action.undo'); } // For backward compatibility @@ -346,17 +347,13 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni } public onDataUpdated() { - // this.ngZone.run(() => { - this.updateWidgetData(this.subscription.data); - this.ctx.detectChanges(); - // }); + this.updateWidgetData(this.subscription.data); + this.ctx.detectChanges(); } private resize() { - // this.ngZone.run(() => { - this.updateWidgetDisplaying(); - this.ctx.detectChanges(); - // }); + this.updateWidgetDisplaying(); + this.ctx.detectChanges(); } public getGroupTitle(datasource: Datasource): string { From 17ca3cfab4e5f9cf47949318d4e3fe6626eed73c Mon Sep 17 00:00:00 2001 From: zbeacon Date: Thu, 8 Oct 2020 16:51:28 +0300 Subject: [PATCH 145/463] Refactoring --- .../server/service/device/DeviceProvisionServiceImpl.java | 3 ++- .../server/service/transport/DefaultTransportApiService.java | 3 +-- common/queue/src/main/proto/queue.proto | 2 +- .../server/common/transport/adaptor/JsonConverter.java | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index e03f5e9493..b6a94bf9b3 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -263,7 +263,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { !StringUtils.isEmpty(provisionRequest.getCredentialsData().getUsername()) || !StringUtils.isEmpty(provisionRequest.getCredentialsData().getPassword()) || !StringUtils.isEmpty(provisionRequest.getCredentialsData().getClientId())) { - DeviceCredentials deviceCredentials = new DeviceCredentials(); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getTenantId(), savedDevice.getId()); deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType()); switch (provisionRequest.getCredentialsType()) { case ACCESS_TOKEN: @@ -281,6 +281,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { deviceCredentials.setCredentialsValue(provisionRequest.getCredentialsData().getHash()); break; } + deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType()); deviceCredentialsService.updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials); } return savedDevice; diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index b2c93a0ecb..eb2fdbc65e 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -315,8 +315,7 @@ public class DefaultTransportApiService implements TransportApiService { return TransportProtos.DeviceCredentialsProto.newBuilder() .setDeviceIdMSB(deviceCredentials.getDeviceId().getId().getMostSignificantBits()) .setDeviceIdLSB(deviceCredentials.getDeviceId().getId().getLeastSignificantBits()) - .setCredentialsType(deviceCredentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN ? - CredentialsType.ACCESS_TOKEN : CredentialsType.X509_CERTIFICATE) + .setCredentialsType(TransportProtos.CredentialsType.valueOf(deviceCredentials.getCredentialsType().name())) .setCredentialsId(deviceCredentials.getCredentialsId()) .setCredentialsValue(deviceCredentials.getCredentialsValue() != null ? deviceCredentials.getCredentialsValue() : "") .build(); diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index e6dc171550..7c2ae80f9b 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -76,7 +76,7 @@ enum KeyValueType { enum CredentialsType { ACCESS_TOKEN = 0; X509_CERTIFICATE = 1; - BASIC_MQTT = 2; + MQTT_BASIC = 2; } message KeyValueProto { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index dd6938e17f..321ed22baa 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -567,7 +567,7 @@ public class JsonConverter { .setPassword(getStrValue(jo, DataConstants.PASSWORD, false)) .build()) .setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder() - .setHash(getStrValue(jo, DataConstants.PASSWORD, false)).build()) + .setHash(getStrValue(jo, DataConstants.HASH, false)).build()) .build()) .setProvisionDeviceCredentialsMsg(buildProvisionDeviceCredentialsMsg( getStrValue(jo, DataConstants.PROVISION_KEY, true), From 6b72e82104a2cb0f20efc3e036bd72d804e41f4e Mon Sep 17 00:00:00 2001 From: zbeacon Date: Thu, 8 Oct 2020 17:30:35 +0300 Subject: [PATCH 146/463] Improvement --- .../server/service/device/DeviceProvisionServiceImpl.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index b6a94bf9b3..5a007d27ca 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -161,12 +161,6 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private ListenableFuture validateCredentials(ProvisionRequest provisionRequest) { switch (provisionRequest.getCredentialsType()) { - case ACCESS_TOKEN: - if (StringUtils.isEmpty(provisionRequest.getCredentialsData().getToken())) { - log.error("Failed to get token from credentials data!"); - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); - } - break; case MQTT_BASIC: if (StringUtils.isEmpty(provisionRequest.getCredentialsData().getClientId()) || StringUtils.isEmpty(provisionRequest.getCredentialsData().getUsername()) || From ac055397b01341838a14754efeec46f6d63a71a0 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Thu, 8 Oct 2020 18:22:16 +0300 Subject: [PATCH 147/463] Added GITHUB mapper type --- .../github_config.json | 5 +- .../upgrade/3.1.1/schema_update_before.sql | 1 + .../auth/oauth2/BasicMapperUtils.java | 78 ++++++++++++++++ .../auth/oauth2/BasicOAuth2ClientMapper.java | 53 +---------- .../auth/oauth2/GithubOAuth2ClientMapper.java | 91 +++++++++++++++++++ .../oauth2/OAuth2ClientMapperProvider.java | 6 ++ .../src/main/resources/thingsboard.yml | 2 + .../server/common/data/oauth2/MapperType.java | 2 +- .../OAuth2ClientRegistrationTemplate.java | 2 + ...actOAuth2ClientRegistrationInfoEntity.java | 2 +- ...Auth2ClientRegistrationTemplateEntity.java | 5 + .../dao/oauth2/OAuth2Configuration.java | 4 +- .../server/dao/oauth2/OAuth2ServiceImpl.java | 16 ++++ .../resources/sql/schema-entities-hsql.sql | 1 + .../main/resources/sql/schema-entities.sql | 1 + .../BaseOAuth2ConfigTemplateServiceTest.java | 2 + 16 files changed, 216 insertions(+), 55 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicMapperUtils.java create mode 100644 application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java diff --git a/application/src/main/data/json/system/oauth2_config_templates/github_config.json b/application/src/main/data/json/system/oauth2_config_templates/github_config.json index 58812fdc1e..fff9fcf3a1 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/github_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/github_config.json @@ -7,7 +7,10 @@ "userInfoUri": "https://api.github.com/user", "clientAuthenticationMethod": "BASIC", "userNameAttributeName": "login", - "basic": {}, + "basic": { + "lastNameAttributeKey": "name", + "tenantNameStrategy": "DOMAIN" + }, "comment": "In order to log into ThingsBoard you need to have user's email. You may configure and use Custom OAuth2 Mapper to get email information. Please refer to Github Documentation", "loginButtonIcon": "mdi:github", "loginButtonLabel": "Github", diff --git a/application/src/main/data/upgrade/3.1.1/schema_update_before.sql b/application/src/main/data/upgrade/3.1.1/schema_update_before.sql index fe8ee4531b..871f55d694 100644 --- a/application/src/main/data/upgrade/3.1.1/schema_update_before.sql +++ b/application/src/main/data/upgrade/3.1.1/schema_update_before.sql @@ -67,6 +67,7 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( user_name_attribute_name varchar(255), jwk_set_uri varchar(255), client_authentication_method varchar(255), + type varchar(31), basic_email_attribute_key varchar(31), basic_first_name_attribute_key varchar(31), basic_last_name_attribute_key varchar(31), diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicMapperUtils.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicMapperUtils.java new file mode 100644 index 0000000000..246e74245d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicMapperUtils.java @@ -0,0 +1,78 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.security.auth.oauth2; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.text.StrSubstitutor; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; +import org.thingsboard.server.dao.oauth2.OAuth2User; + +import java.util.Map; + +@Slf4j +public class BasicMapperUtils { + private static final String START_PLACEHOLDER_PREFIX = "%{"; + private static final String END_PLACEHOLDER_PREFIX = "}"; + + public static OAuth2User getOAuth2User(String email, Map attributes, OAuth2MapperConfig config) { + OAuth2User oauth2User = new OAuth2User(); + oauth2User.setEmail(email); + oauth2User.setTenantName(getTenantName(email, attributes, config)); + if (!StringUtils.isEmpty(config.getBasic().getLastNameAttributeKey())) { + String lastName = getStringAttributeByKey(attributes, config.getBasic().getLastNameAttributeKey()); + oauth2User.setLastName(lastName); + } + if (!StringUtils.isEmpty(config.getBasic().getFirstNameAttributeKey())) { + String firstName = getStringAttributeByKey(attributes, config.getBasic().getFirstNameAttributeKey()); + oauth2User.setFirstName(firstName); + } + if (!StringUtils.isEmpty(config.getBasic().getCustomerNamePattern())) { + StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX); + String customerName = sub.replace(config.getBasic().getCustomerNamePattern()); + oauth2User.setCustomerName(customerName); + } + oauth2User.setAlwaysFullScreen(config.getBasic().isAlwaysFullScreen()); + if (!StringUtils.isEmpty(config.getBasic().getDefaultDashboardName())) { + oauth2User.setDefaultDashboardName(config.getBasic().getDefaultDashboardName()); + } + return oauth2User; + } + + public static String getTenantName(String email, Map attributes, OAuth2MapperConfig config) { + switch (config.getBasic().getTenantNameStrategy()) { + case EMAIL: + return email; + case DOMAIN: + return email.substring(email .indexOf("@") + 1); + case CUSTOM: + StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX); + return sub.replace(config.getBasic().getTenantNamePattern()); + default: + throw new RuntimeException("Tenant Name Strategy with type " + config.getBasic().getTenantNameStrategy() + " is not supported!"); + } + } + + public static String getStringAttributeByKey(Map attributes, String key) { + String result = null; + try { + result = (String) attributes.get(key); + } catch (Exception e) { + log.warn("Can't convert attribute to String by key " + key); + } + return result; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java index 7412f2199c..73da9e539f 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java @@ -16,10 +16,8 @@ package org.thingsboard.server.service.security.auth.oauth2; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.text.StrSubstitutor; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.service.security.model.SecurityUser; @@ -30,59 +28,12 @@ import java.util.Map; @Slf4j public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { - private static final String START_PLACEHOLDER_PREFIX = "%{"; - private static final String END_PLACEHOLDER_PREFIX = "}"; - @Override public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) { - OAuth2User oauth2User = new OAuth2User(); Map attributes = token.getPrincipal().getAttributes(); - String email = getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); - oauth2User.setEmail(email); - oauth2User.setTenantName(getTenantName(attributes, config)); - if (!StringUtils.isEmpty(config.getBasic().getLastNameAttributeKey())) { - String lastName = getStringAttributeByKey(attributes, config.getBasic().getLastNameAttributeKey()); - oauth2User.setLastName(lastName); - } - if (!StringUtils.isEmpty(config.getBasic().getFirstNameAttributeKey())) { - String firstName = getStringAttributeByKey(attributes, config.getBasic().getFirstNameAttributeKey()); - oauth2User.setFirstName(firstName); - } - if (!StringUtils.isEmpty(config.getBasic().getCustomerNamePattern())) { - StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX); - String customerName = sub.replace(config.getBasic().getCustomerNamePattern()); - oauth2User.setCustomerName(customerName); - } - oauth2User.setAlwaysFullScreen(config.getBasic().isAlwaysFullScreen()); - if (!StringUtils.isEmpty(config.getBasic().getDefaultDashboardName())) { - oauth2User.setDefaultDashboardName(config.getBasic().getDefaultDashboardName()); - } + String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); + OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser()); } - - private String getTenantName(Map attributes, OAuth2MapperConfig config) { - switch (config.getBasic().getTenantNameStrategy()) { - case EMAIL: - return getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); - case DOMAIN: - String email = getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); - return email.substring(email .indexOf("@") + 1); - case CUSTOM: - StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX); - return sub.replace(config.getBasic().getTenantNamePattern()); - default: - throw new RuntimeException("Tenant Name Strategy with type " + config.getBasic().getTenantNameStrategy() + " is not supported!"); - } - } - - private String getStringAttributeByKey(Map attributes, String key) { - String result = null; - try { - result = (String) attributes.get(key); - } catch (Exception e) { - log.warn("Can't convert attribute to String by key " + key); - } - return result; - } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java new file mode 100644 index 0000000000..dcca2b71a1 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java @@ -0,0 +1,91 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.security.auth.oauth2; + +import lombok.Data; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; +import org.thingsboard.server.dao.oauth2.OAuth2Configuration; +import org.thingsboard.server.dao.oauth2.OAuth2User; +import org.thingsboard.server.service.security.model.SecurityUser; + +import java.util.ArrayList; +import java.util.Map; +import java.util.Optional; + +@Service(value = "githubOAuth2ClientMapper") +@Slf4j +public class GithubOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { + private static final String EMAIL_URL_KEY = "emailUrl"; + + private static final String AUTHORIZATION = "Authorization"; + + private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); + + @Autowired + private OAuth2Configuration oAuth2Configuration; + + @Override + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) { + Map githubMapperConfig = oAuth2Configuration.getGithubMapper(); + String email = getEmail(githubMapperConfig.get(EMAIL_URL_KEY), providerAccessToken); + Map attributes = token.getPrincipal().getAttributes(); + OAuth2User oAuth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); + return getOrCreateSecurityUserFromOAuth2User(oAuth2User, config.isAllowUserCreation(), config.isActivateUser()); + } + + private synchronized String getEmail(String emailUrl, String oauth2Token) { + restTemplateBuilder = restTemplateBuilder.defaultHeader(AUTHORIZATION, "token " + oauth2Token); + + RestTemplate restTemplate = restTemplateBuilder.build(); + GithubEmailsResponse githubEmailsResponse; + try { + githubEmailsResponse = restTemplate.getForEntity(emailUrl, GithubEmailsResponse.class).getBody(); + if (githubEmailsResponse == null){ + throw new RuntimeException("Empty Github response!"); + } + } catch (Exception e) { + log.error("There was an error during connection to Github API", e); + throw new RuntimeException("Unable to login. Please contact your Administrator!"); + } + Optional emailOpt = githubEmailsResponse.stream() + .filter(GithubEmailResponse::isPrimary) + .map(GithubEmailResponse::getEmail) + .findAny(); + if (emailOpt.isPresent()){ + return emailOpt.get(); + } else { + log.error("Could not find primary email from {}.", githubEmailsResponse); + throw new RuntimeException("Unable to login. Please contact your Administrator!"); + } + } + private static class GithubEmailsResponse extends ArrayList {} + + @Data + @ToString + private static class GithubEmailResponse { + private String email; + private boolean verified; + private boolean primary; + private String visibility; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java index 64bdf40970..2e764fe223 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java @@ -33,12 +33,18 @@ public class OAuth2ClientMapperProvider { @Qualifier("customOAuth2ClientMapper") private OAuth2ClientMapper customOAuth2ClientMapper; + @Autowired + @Qualifier("githubOAuth2ClientMapper") + private OAuth2ClientMapper githubOAuth2ClientMapper; + public OAuth2ClientMapper getOAuth2ClientMapperByType(MapperType oauth2MapperType) { switch (oauth2MapperType) { case CUSTOM: return customOAuth2ClientMapper; case BASIC: return basicOAuth2ClientMapper; + case GITHUB: + return githubOAuth2ClientMapper; default: throw new RuntimeException("OAuth2ClientRegistrationMapper with type " + oauth2MapperType + " is not supported!"); } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index c81782a0a5..d0ddeb611a 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -115,6 +115,8 @@ security: oauth2: # Redirect URL where access code from external user management system will be processed loginProcessingUrl: "${SECURITY_OAUTH2_LOGIN_PROCESSING_URL:/login/oauth2/code/}" + githubMapper: + emailUrl: "${SECURITY_OAUTH2_GITHUB_MAPPER_EMAIL_URL_KEY:https://api.github.com/user/emails}" # Dashboard parameters dashboard: diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java index 48f0a77a41..25faad187e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java @@ -16,5 +16,5 @@ package org.thingsboard.server.common.data.oauth2; public enum MapperType { - BASIC, CUSTOM; + BASIC, CUSTOM, GITHUB; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java index 5c2f70874a..897472f154 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java @@ -34,6 +34,7 @@ import java.util.List; public class OAuth2ClientRegistrationTemplate extends SearchTextBasedWithAdditionalInfo implements HasName { private String providerId; + private MapperType mapperType; private OAuth2BasicMapperConfig basic; private String authorizationUri; private String accessTokenUri; @@ -50,6 +51,7 @@ public class OAuth2ClientRegistrationTemplate extends SearchTextBasedWithAdditio public OAuth2ClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { super(clientRegistrationTemplate); this.providerId = clientRegistrationTemplate.providerId; + this.mapperType = clientRegistrationTemplate.mapperType; this.basic = clientRegistrationTemplate.basic; this.authorizationUri = clientRegistrationTemplate.authorizationUri; this.accessTokenUri = clientRegistrationTemplate.accessTokenUri; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractOAuth2ClientRegistrationInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractOAuth2ClientRegistrationInfoEntity.java index efa9a563ec..6e9ed4822a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractOAuth2ClientRegistrationInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractOAuth2ClientRegistrationInfoEntity.java @@ -190,7 +190,7 @@ public abstract class AbstractOAuth2ClientRegistrationInfoEntity githubMapper; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 2ad0ad4f20..9fda0e3c2f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -184,6 +184,22 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se throw new DataValidationException("Tenant name pattern should be specified!"); } } + if (mapperConfig.getType() == MapperType.GITHUB) { + OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic(); + if (basicConfig == null) { + throw new DataValidationException("Basic config should be specified!"); + } + if (!StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) { + throw new DataValidationException("Email attribute key cannot be configured for GITHUB mapper type!"); + } + if (basicConfig.getTenantNameStrategy() == null) { + throw new DataValidationException("Tenant name strategy should be specified!"); + } + if (basicConfig.getTenantNameStrategy() == TenantNameStrategyType.CUSTOM + && StringUtils.isEmpty(basicConfig.getTenantNamePattern())) { + throw new DataValidationException("Tenant name pattern should be specified!"); + } + } if (mapperConfig.getType() == MapperType.CUSTOM) { OAuth2CustomMapperConfig customConfig = mapperConfig.getCustom(); if (customConfig == null) { diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index 8c78d3392a..74f9679b11 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -386,6 +386,7 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( user_name_attribute_name varchar(255), jwk_set_uri varchar(255), client_authentication_method varchar(255), + type varchar(31), basic_email_attribute_key varchar(31), basic_first_name_attribute_key varchar(31), basic_last_name_attribute_key varchar(31), diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 5e1a7dbddc..6056ba8895 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -412,6 +412,7 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( user_name_attribute_name varchar(255), jwk_set_uri varchar(255), client_authentication_method varchar(255), + type varchar(31), basic_email_attribute_key varchar(31), basic_first_name_attribute_key varchar(31), basic_last_name_attribute_key varchar(31), diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java index 3d828d1ac5..3c32af73a3 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java @@ -21,6 +21,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.oauth2.MapperType; import org.thingsboard.server.common.data.oauth2.OAuth2BasicMapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import org.thingsboard.server.dao.exception.DataValidationException; @@ -105,6 +106,7 @@ public class BaseOAuth2ConfigTemplateServiceTest extends AbstractServiceTest { OAuth2ClientRegistrationTemplate clientRegistrationTemplate = new OAuth2ClientRegistrationTemplate(); clientRegistrationTemplate.setProviderId(providerId); clientRegistrationTemplate.setAdditionalInfo(mapper.createObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString())); + clientRegistrationTemplate.setMapperType(MapperType.BASIC); clientRegistrationTemplate.setBasic( OAuth2BasicMapperConfig.builder() .firstNameAttributeKey("firstName") From e2b955299e8f12863810f01f6d9ca87347132c85 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Thu, 8 Oct 2020 19:58:39 +0300 Subject: [PATCH 148/463] Add missed custom translations for key labels --- ui-ngx/src/app/core/api/widget-subscription.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index c574fd426a..f7017652d8 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -1241,6 +1241,7 @@ export class WidgetSubscription implements IWidgetSubscription { return datasource.dataKeys.map((dataKey, keyIndex) => { dataKey.hidden = !!dataKey.settings.hideDataByDefault; dataKey.inLegend = !dataKey.settings.removeFromLegend; + dataKey.label = this.ctx.utils.customTranslation(dataKey.label, dataKey.label); if (this.comparisonEnabled && dataKey.isAdditional && dataKey.settings.comparisonSettings.comparisonValuesLabel) { dataKey.label = createLabelFromDatasource(datasource, dataKey.settings.comparisonSettings.comparisonValuesLabel); } else { From d032b0a3f7a5394cfe3bbefb54a5e1ff96ce7f3e Mon Sep 17 00:00:00 2001 From: zbeacon Date: Fri, 9 Oct 2020 09:28:04 +0300 Subject: [PATCH 149/463] Adding tests --- .../AbstractMqttProvisionJsonDeviceTest.java | 115 +++++++++++++-- .../AbstractMqttProvisionProtoDeviceTest.java | 138 ++++++++++++++++-- 2 files changed, 235 insertions(+), 18 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java index 26d7e2afce..7c341029b6 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java @@ -29,12 +29,15 @@ import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest; import java.util.concurrent.CountDownLatch; @@ -65,8 +68,23 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn } @Test - public void testProvisioningCreateNewDevice() throws Exception { - processTestProvisioningCreateNewDevice(); + public void testProvisioningCreateNewDeviceWithoutCredentials() throws Exception { + processTestProvisioningCreateNewDeviceWithoutCredentials(); + } + + @Test + public void testProvisioningCreateNewDeviceWithAccessToken() throws Exception { + processTestProvisioningCreateNewDeviceWithAccessToken(); + } + + @Test + public void testProvisioningCreateNewDeviceWithCert() throws Exception { + processTestProvisioningCreateNewDeviceWithCert(); + } + + @Test + public void testProvisioningCreateNewDeviceWithMqttBasic() throws Exception { + processTestProvisioningCreateNewDeviceWithMqttBasic(); } @Test @@ -84,7 +102,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn } - protected void processTestProvisioningCreateNewDevice() throws Exception { + protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); @@ -101,6 +119,83 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); } + + protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + String requestCredentials = ",\"credentialsType\": \"ACCESS_TOKEN\",\"token\": \"test_token\""; + byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString()); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "ACCESS_TOKEN"); + Assert.assertEquals(deviceCredentials.getCredentialsId(), "test_token"); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + } + + + protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception { + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + String requestCredentials = ",\"credentialsType\": \"X509_CERTIFICATE\",\"hash\": \"testHash\""; + byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString()); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "X509_CERTIFICATE"); + + String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); + String sha3Hash = EncryptionUtil.getSha3Hash(cert); + + Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash); + + Assert.assertEquals(deviceCredentials.getCredentialsValue(), "testHash"); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + } + + + protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception { + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + String requestCredentials = ",\"credentialsType\": \"MQTT_BASIC\",\"clientId\": \"test_clientId\",\"username\": \"test_username\",\"password\": \"test_password\""; + byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString()); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "MQTT_BASIC"); + Assert.assertEquals(deviceCredentials.getCredentialsId(), EncryptionUtil.getSha3Hash("|", "test_clientId", "test_username")); + + BasicMqttCredentials mqttCredentials = new BasicMqttCredentials(); + mqttCredentials.setClientId("test_clientId"); + mqttCredentials.setUserName("test_username"); + mqttCredentials.setPassword("test_password"); + + Assert.assertEquals(deviceCredentials.getCredentialsValue(), JacksonUtil.toString(mqttCredentials)); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + } + protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); byte[] result = createMqttClientAndPublish().getPayloadBytes(); @@ -122,8 +217,12 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("provisionDeviceStatus").getAsString()); } - protected TestMqttCallback createMqttClientAndPublish() throws Exception{ - String provisionRequestMsg = createTestProvisionMessage(); + protected TestMqttCallback createMqttClientAndPublish() throws Exception { + return createMqttClientAndPublish(""); + } + + protected TestMqttCallback createMqttClientAndPublish(String deviceCredentials) throws Exception { + String provisionRequestMsg = createTestProvisionMessage(deviceCredentials); MqttAsyncClient client = getMqttAsyncClient("provision"); TestMqttCallback onProvisionCallback = getTestMqttCallback(); client.setCallback(onProvisionCallback); @@ -180,9 +279,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn } } - - protected String createTestProvisionMessage() { - return "{\"deviceName\":\"Test Provision device\",\"provisionDeviceKey\":\"testProvisionKey\", \"provisionDeviceSecret\":\"testProvisionSecret\"}"; + protected String createTestProvisionMessage(String deviceCredentials) { + return "{\"deviceName\":\"Test Provision device\",\"provisionDeviceKey\":\"testProvisionKey\", \"provisionDeviceSecret\":\"testProvisionSecret\"" + deviceCredentials + "}"; } - } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java index 54e99077ea..c5becedbbd 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java @@ -30,16 +30,25 @@ import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.CredentialsDataProto; +import org.thingsboard.server.gen.transport.TransportProtos.CredentialsType; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceCredentialsMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest; import java.util.UUID; @@ -71,8 +80,23 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } @Test - public void testProvisioningCreateNewDevice() throws Exception { - processTestProvisioningCreateNewDevice(); + public void testProvisioningCreateNewDeviceWithoutCredentials() throws Exception { + processTestProvisioningCreateNewDeviceWithoutCredentials(); + } + + @Test + public void testProvisioningCreateNewDeviceWithAccessToken() throws Exception { + processTestProvisioningCreateNewDeviceWithAccessToken(); + } + + @Test + public void testProvisioningCreateNewDeviceWithCert() throws Exception { + processTestProvisioningCreateNewDeviceWithCert(); + } + + @Test + public void testProvisioningCreateNewDeviceWithMqttBasic() throws Exception { + processTestProvisioningCreateNewDeviceWithMqttBasic(); } @Test @@ -88,10 +112,79 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getProvisionResponseStatus().toString()); } + protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + } - protected void processTestProvisioningCreateNewDevice() throws Exception { + protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); - ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); + CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken("test_token").build()).build(); + + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.ACCESS_TOKEN, requestCredentials)).getPayloadBytes()); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); + Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.ACCESS_TOKEN); + Assert.assertEquals(deviceCredentials.getCredentialsId(), "test_token"); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + } + + protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception { + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder().setHash("testHash").build()).build(); + + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.X509_CERTIFICATE, requestCredentials)).getPayloadBytes()); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); + Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.X509_CERTIFICATE); + + String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); + String sha3Hash = EncryptionUtil.getSha3Hash(cert); + + Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash); + + Assert.assertEquals(deviceCredentials.getCredentialsValue(), "testHash"); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + } + + protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception { + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateBasicMqttCredRequestMsg( + ValidateBasicMqttCredRequestMsg.newBuilder() + .setClientId("test_clientId") + .setUserName("test_username") + .setPassword("test_password") + .build() + ).build(); + + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.MQTT_BASIC, requestCredentials)).getPayloadBytes()); Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); @@ -102,11 +195,21 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); + Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.MQTT_BASIC); + Assert.assertEquals(deviceCredentials.getCredentialsId(), EncryptionUtil.getSha3Hash("|", "test_clientId", "test_username")); + + BasicMqttCredentials mqttCredentials = new BasicMqttCredentials(); + mqttCredentials.setClientId("test_clientId"); + mqttCredentials.setUserName("test_username"); + mqttCredentials.setPassword("test_password"); + + Assert.assertEquals(deviceCredentials.getCredentialsValue(), JacksonUtil.toString(mqttCredentials)); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); } protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); Assert.assertEquals(savedDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); @@ -118,13 +221,17 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } protected void processTestProvisioningWithBadKeyDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getProvisionResponseStatus().toString()); } - protected TestMqttCallback createMqttClientAndPublish() throws Exception{ + protected TestMqttCallback createMqttClientAndPublish() throws Exception { byte[] provisionRequestMsg = createTestProvisionMessage(); + return createMqttClientAndPublish(provisionRequestMsg); + } + + protected TestMqttCallback createMqttClientAndPublish(byte[] provisionRequestMsg) throws Exception { MqttAsyncClient client = getMqttAsyncClient("provision"); TestMqttCallback onProvisionCallback = getTestMqttCallback(); client.setCallback(onProvisionCallback); @@ -181,9 +288,22 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } } + protected byte[] createTestsProvisionMessage(CredentialsType credentialsType, CredentialsDataProto credentialsData) throws Exception { + return ProvisionDeviceRequestMsg.newBuilder() + .setDeviceName("Test Provision device") + .setCredentialsType(credentialsType != null ? credentialsType : CredentialsType.ACCESS_TOKEN) + .setCredentialsDataProto(credentialsData != null ? credentialsData: CredentialsDataProto.newBuilder().build()) + .setProvisionDeviceCredentialsMsg( + ProvisionDeviceCredentialsMsg.newBuilder() + .setProvisionDeviceKey("testProvisionKey") + .setProvisionDeviceSecret("testProvisionSecret") + ).build() + .toByteArray(); + } + - protected byte[] createTestProvisionMessage() { - return ProvisionDeviceRequestMsg.newBuilder().setDeviceName("Test Provision device").setProvisionDeviceCredentialsMsg(ProvisionDeviceCredentialsMsg.newBuilder().setProvisionDeviceKey("testProvisionKey").setProvisionDeviceSecret("testProvisionSecret")).build().toByteArray(); + protected byte[] createTestProvisionMessage() throws Exception { + return createTestsProvisionMessage(null, null); } } From 2db74b25a428629632595b790aacafe3eeecb4a9 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Fri, 9 Oct 2020 10:37:28 +0300 Subject: [PATCH 150/463] Removed 'name' from Github OAuth2 template --- .../data/json/system/oauth2_config_templates/github_config.json | 1 - 1 file changed, 1 deletion(-) diff --git a/application/src/main/data/json/system/oauth2_config_templates/github_config.json b/application/src/main/data/json/system/oauth2_config_templates/github_config.json index fff9fcf3a1..6c3a51723f 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/github_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/github_config.json @@ -8,7 +8,6 @@ "clientAuthenticationMethod": "BASIC", "userNameAttributeName": "login", "basic": { - "lastNameAttributeKey": "name", "tenantNameStrategy": "DOMAIN" }, "comment": "In order to log into ThingsBoard you need to have user's email. You may configure and use Custom OAuth2 Mapper to get email information. Please refer to Github Documentation", From 6de23fe3659a55b8c2c7ab90c5f1deb06f0fbc50 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Fri, 9 Oct 2020 11:07:10 +0300 Subject: [PATCH 151/463] Refactoring --- .../lib/multiple-input-widget.component.html | 16 ++++----- .../lib/multiple-input-widget.component.ts | 35 ++++++++++++++++++- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html index 11666f82f6..6a964fc524 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html @@ -39,7 +39,7 @@ (blur)="key.isFocused = false; inputChanged(source, key)"> {{key.settings.icon}} - {{ getErrorMessageText(key.settings.requiredErrorMessage, '') }} + {{ getErrorMessageText(key.settings, 'required') }}
@@ -59,17 +59,13 @@ (blur)="key.isFocused = false; inputChanged(source, key)"> {{key.settings.icon}} - {{ getErrorMessageText(key.settings.requiredErrorMessage, '') }} + {{ getErrorMessageText(key.settings,'required') }} - {{ getErrorMessageText(key.settings.minValueErrorMessage, - 'widgets.input-widgets.min-value-error', - {value: key.settings.minValue}) }} + {{ getErrorMessageText(key.settings,'min') }} - {{ getErrorMessageText(key.settings.maxValueErrorMessage, - 'widgets.input-widgets.max-value-error', - {value: key.settings.maxValue}) }} + {{ getErrorMessageText(key.settings,'max') }}
@@ -101,10 +97,10 @@ (blur)="key.isFocused = false;" (dateChange)="inputChanged(source, key)"> - {{ getErrorMessageText(key.settings.requiredErrorMessage, '') }} + {{ getErrorMessageText(key.settings, 'required') }} - {{ getErrorMessageText(key.settings.invalidDateErrorMessage, 'widgets.input-widgets.invalid-date') }} + {{ getErrorMessageText(key.settings, 'invalidDate') }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts index 63b461cc24..0403f2844c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts @@ -361,7 +361,40 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni return this.utils.customTranslation(groupTitle, groupTitle); } - public getErrorMessageText(errorMessage, defaultMessage, messageValues?): string { + public getErrorMessageText(keySettings: MultipleInputWidgetDataKeySettings, errorType: string): string { + let errorMessage; + let defaultMessage; + let messageValues; + switch (errorType) { + case 'required': + errorMessage = keySettings.requiredErrorMessage; + defaultMessage = ''; + break; + case 'min': + errorMessage = keySettings.minValueErrorMessage; + defaultMessage = 'widgets.input-widgets.min-value-error'; + messageValues = { + value: keySettings.minValue + }; + break; + case 'max': + errorMessage = keySettings.maxValueErrorMessage; + defaultMessage = 'widgets.input-widgets.max-value-error'; + messageValues = { + value: keySettings.maxValue + }; + break; + case 'invalidDate': + errorMessage = keySettings.invalidDateErrorMessage; + defaultMessage = 'widgets.input-widgets.invalid-date'; + break; + default: + return ''; + } + return this.getTranslatedErrorText(errorMessage, defaultMessage, messageValues); + } + + public getTranslatedErrorText(errorMessage: string, defaultMessage: string, messageValues?: object): string { let messageText; if (errorMessage && errorMessage.length) { messageText = this.utils.customTranslation(errorMessage, errorMessage); From c7b57ca543f36e4691efae2e01d8e262afc04426 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 9 Oct 2020 17:19:51 +0300 Subject: [PATCH 152/463] Add Device Wizard improvements --- .../add-device-profile-dialog.component.html | 2 +- .../device-profile-autocomplete.component.ts | 19 +- .../device-profile-data.component.html | 2 +- .../device-wizard-dialog.component.html | 122 ++++++------ .../device-wizard-dialog.component.scss | 46 ++++- .../wizard/device-wizard-dialog.component.ts | 175 +++++++++++------- .../device/devices-table-config.resolver.ts | 8 +- ui-ngx/src/app/shared/models/device.models.ts | 8 + .../assets/locale/locale.constant-en_US.json | 14 +- 9 files changed, 250 insertions(+), 146 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html index d7f119a3b7..df70ea4e8f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html @@ -85,7 +85,7 @@
- {{'device-profile.alarm-rules' | translate: + {{'device-profile.alarm-rules-with-count' | translate: {count: alarmRulesFormGroup.get('alarms').value ? alarmRulesFormGroup.get('alarms').value.length : 0} }} { - if (profile) { + if (profile && !this.transportType || (profile.transportType === this.transportType)) { this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(profile, {emitEvent: false}); this.updateView(profile); } diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html index daae838285..2cc3ccd9e2 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html @@ -42,7 +42,7 @@ -
{{'device-profile.alarm-rules' | translate: +
{{'device-profile.alarm-rules-with-count' | translate: {count: deviceProfileDataFormGroup.get('alarms').value ? deviceProfileDataFormGroup.get('alarms').value.length : 0} }}
diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index c391bc848a..6cf1e687e3 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+

device.add-device-text

@@ -29,7 +29,7 @@
- + check @@ -48,60 +48,60 @@ device.label - + device-profile.transport-type {{deviceTransportTypeTranslations.get(type) | translate}} + + {{deviceTransportTypeHints.get(deviceWizardFormGroup.get('transportType').value) | translate}} + {{ 'device-profile.transport-type-required' | translate }} - - {{ 'device.is-gateway' | translate }} - - - device.description - - -
- - - -
- {{ 'device.wizard.profile-configuration' | translate}} - - -
- device.wizard.existing-device-profile +
+ + + device.wizard.existing-device-profile + + + device.wizard.new-device-profile + + +
-
-
- -
- device.wizard.new-device-profile - device-profile.device-profile + device-profile.new-device-profile-name - - {{ 'device-profile.device-profile-required' | translate }} + [required]="createProfile"> + + {{ 'device-profile.new-device-profile-name-required' | translate }} -
-
-
+ + + + {{ 'device.is-gateway' | translate }} + + + device.description + + +
- +
{{ 'device-profile.transport-configuration' | translate }}
- +
- {{'device-profile.alarm-rules' | translate: + {{'device-profile.alarm-rules-with-count' | translate: {count: alarmRulesFormGroup.get('alarms').value ? alarmRulesFormGroup.get('alarms').value.length : 0} }}
- - {{ 'device.wizard.specific-configuration' | translate }} -
+ + {{ 'device.credentials' | translate }} + + {{ 'device.wizard.add-credential' | translate }} + + + +
+ + {{ 'customer.customer' | translate }} +
- {{ 'device.wizard.add-credential' | translate }} - -
-
- - -
+
+
+ +
+
- + +
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss index cafcce74b6..1426e0f201 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss @@ -13,25 +13,51 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -:host { - .mat-dialog-content { - display: flex; - flex-direction: column; - overflow: hidden; - .mat-stepper-horizontal { - display: flex; - flex-direction: column; - overflow: hidden; +@import "../../../../../scss/constants"; + +:host-context(.tb-fullscreen-dialog .mat-dialog-container) { + @media #{$mat-lt-sm} { + .mat-dialog-content { + max-height: 75vh; } } } :host ::ng-deep { .mat-dialog-content { + display: flex; + flex-direction: column; + height: 100%; + .mat-stepper-horizontal { + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; + @media #{$mat-lt-sm} { + .mat-step-label { + white-space: normal; + overflow: visible; + .mat-step-text-label { + overflow: visible; + } + } + } .mat-horizontal-content-container { - overflow: auto; + height: 450px; + max-height: 100%; + width: 100%;; + overflow-y: auto; + @media #{$mat-gt-sm} { + min-width: 800px; + } + } + .mat-horizontal-stepper-content[aria-expanded=true] { + height: 100%; + form { + height: 100%; + } } } } diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts index 15efa868b2..9def61c4b6 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts @@ -26,7 +26,7 @@ import { createDeviceProfileTransportConfiguration, DeviceProfile, DeviceProfileType, - DeviceTransportType, + DeviceTransportType, deviceTransportTypeHintMap, deviceTransportTypeTranslationMap } from '@shared/models/device.models'; import { MatHorizontalStepper } from '@angular/material/stepper'; @@ -35,11 +35,13 @@ import { BaseData, HasId } from '@shared/models/base-data'; import { EntityType } from '@shared/models/entity-type.models'; import { DeviceProfileService } from '@core/http/device-profile.service'; import { EntityId } from '@shared/models/id/entity-id'; -import { BehaviorSubject, Observable, of, Subscription } from 'rxjs'; +import { Observable, of, Subscription } from 'rxjs'; import { map, mergeMap, tap } from 'rxjs/operators'; import { DeviceService } from '@core/http/device.service'; import { ErrorStateMatcher } from '@angular/material/core'; import { StepperSelectionEvent } from '@angular/cdk/stepper'; +import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; +import { MediaBreakpoints } from '@shared/models/constants'; @Component({ selector: 'tb-device-wizard', @@ -54,9 +56,10 @@ export class DeviceWizardDialogComponent extends selectedIndex = 0; - nextStepButtonLabel$ = new BehaviorSubject('action.continue'); + showNext = true; - createdProfile = false; + createProfile = false; + createTransportConfiguration = false; entityType = EntityType; @@ -64,15 +67,19 @@ export class DeviceWizardDialogComponent extends deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; - deviceWizardFormGroup: FormGroup; + deviceTransportTypeHints = deviceTransportTypeHintMap; - profileConfigFormGroup: FormGroup; + deviceWizardFormGroup: FormGroup; transportConfigFormGroup: FormGroup; alarmRulesFormGroup: FormGroup; - specificConfigFormGroup: FormGroup; + credentialsFormGroup: FormGroup; + + customerFormGroup: FormGroup; + + labelPosition = 'end'; private subscriptions: Subscription[] = []; @@ -83,6 +90,7 @@ export class DeviceWizardDialogComponent extends public dialogRef: MatDialogRef, private deviceProfileService: DeviceProfileService, private deviceService: DeviceService, + private breakpointObserver: BreakpointObserver, private fb: FormBuilder) { super(store, router, dialogRef); this.deviceWizardFormGroup = this.fb.group({ @@ -90,33 +98,32 @@ export class DeviceWizardDialogComponent extends label: [''], gateway: [false], transportType: [DeviceTransportType.DEFAULT, Validators.required], - description: [''] - } - ); - - this.profileConfigFormGroup = this.fb.group({ addProfileType: [0], deviceProfileId: [null, Validators.required], - newDeviceProfileTitle: [{value: null, disabled: true}] + newDeviceProfileTitle: [{value: null, disabled: true}], + description: [''] } ); - this.subscriptions.push(this.profileConfigFormGroup.get('addProfileType').valueChanges.subscribe( + this.subscriptions.push(this.deviceWizardFormGroup.get('addProfileType').valueChanges.subscribe( (addProfileType: number) => { if (addProfileType === 0) { - this.profileConfigFormGroup.get('deviceProfileId').setValidators([Validators.required]); - this.profileConfigFormGroup.get('deviceProfileId').enable(); - this.profileConfigFormGroup.get('newDeviceProfileTitle').setValidators(null); - this.profileConfigFormGroup.get('newDeviceProfileTitle').disable(); - this.profileConfigFormGroup.updateValueAndValidity(); - this.createdProfile = false; + this.deviceWizardFormGroup.get('deviceProfileId').setValidators([Validators.required]); + this.deviceWizardFormGroup.get('deviceProfileId').enable(); + this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators(null); + this.deviceWizardFormGroup.get('newDeviceProfileTitle').disable(); + this.deviceWizardFormGroup.updateValueAndValidity(); + this.createProfile = false; + this.createTransportConfiguration = false; } else { - this.profileConfigFormGroup.get('deviceProfileId').setValidators(null); - this.profileConfigFormGroup.get('deviceProfileId').disable(); - this.profileConfigFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]); - this.profileConfigFormGroup.get('newDeviceProfileTitle').enable(); - this.profileConfigFormGroup.updateValueAndValidity(); - this.createdProfile = true; + this.deviceWizardFormGroup.get('deviceProfileId').setValidators(null); + this.deviceWizardFormGroup.get('deviceProfileId').disable(); + this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]); + this.deviceWizardFormGroup.get('newDeviceProfileTitle').enable(); + this.deviceWizardFormGroup.updateValueAndValidity(); + this.createProfile = true; + this.createTransportConfiguration = this.deviceWizardFormGroup.get('transportType').value && + DeviceTransportType.DEFAULT !== this.deviceWizardFormGroup.get('transportType').value; } } )); @@ -135,20 +142,37 @@ export class DeviceWizardDialogComponent extends } ); - this.specificConfigFormGroup = this.fb.group({ - customerId: [null], + this.credentialsFormGroup = this.fb.group({ setCredential: [false], credential: [{value: null, disabled: true}] } ); - this.subscriptions.push(this.specificConfigFormGroup.get('setCredential').valueChanges.subscribe((value) => { + this.subscriptions.push(this.credentialsFormGroup.get('setCredential').valueChanges.subscribe((value) => { if (value) { - this.specificConfigFormGroup.get('credential').enable(); + this.credentialsFormGroup.get('credential').enable(); } else { - this.specificConfigFormGroup.get('credential').disable(); + this.credentialsFormGroup.get('credential').disable(); } })); + + this.customerFormGroup = this.fb.group({ + customerId: [null] + } + ); + + this.labelPosition = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']) ? 'end' : 'bottom'; + + this.subscriptions.push(this.breakpointObserver + .observe(MediaBreakpoints['gt-sm']) + .subscribe((state: BreakpointState) => { + if (state.matches) { + this.labelPosition = 'end'; + } else { + this.labelPosition = 'bottom'; + } + } + )); } ngOnDestroy() { @@ -171,26 +195,28 @@ export class DeviceWizardDialogComponent extends } nextStep(): void { - if (this.selectedIndex < this.maxStepperIndex) { - this.addDeviceWizardStepper.next(); - } else { - this.add(); - } + this.addDeviceWizardStepper.next(); } - get selectedForm(): FormGroup { - const index = !this.createdProfile && this.selectedIndex === this.maxStepperIndex ? 4 : this.selectedIndex; + getFormLabel(index: number): string { + if (index > 0) { + if (!this.createProfile) { + index += 2; + } else if (!this.createTransportConfiguration) { + index += 1; + } + } switch (index) { case 0: - return this.deviceWizardFormGroup; + return 'device.wizard.device-details'; case 1: - return this.profileConfigFormGroup; + return 'device-profile.transport-configuration'; case 2: - return this.transportConfigFormGroup; + return 'device-profile.alarm-rules'; case 3: - return this.alarmRulesFormGroup; + return 'device.credentials'; case 4: - return this.specificConfigFormGroup; + return 'customer.customer'; } } @@ -201,23 +227,27 @@ export class DeviceWizardDialogComponent extends private deviceProfileTransportTypeChanged(deviceTransportType: DeviceTransportType): void { this.transportConfigFormGroup.patchValue( {transportConfiguration: createDeviceProfileTransportConfiguration(deviceTransportType)}); + this.createTransportConfiguration = this.createProfile && deviceTransportType && + DeviceTransportType.DEFAULT !== deviceTransportType; } - private add(): void { - this.creatProfile().pipe( - mergeMap(profileId => this.createdDevice(profileId)), - mergeMap(device => this.saveCredential(device)) - ).subscribe( - (created) => { - this.dialogRef.close(created); - } - ); + add(): void { + if (this.allValid()) { + this.createDeviceProfile().pipe( + mergeMap(profileId => this.createDevice(profileId)), + mergeMap(device => this.saveCredentials(device)) + ).subscribe( + (created) => { + this.dialogRef.close(created); + } + ); + } } - private creatProfile(): Observable { - if (this.profileConfigFormGroup.get('addProfileType').value) { + private createDeviceProfile(): Observable { + if (this.deviceWizardFormGroup.get('addProfileType').value) { const deviceProfile: DeviceProfile = { - name: this.profileConfigFormGroup.get('newDeviceProfileTitle').value, + name: this.deviceWizardFormGroup.get('newDeviceProfileTitle').value, type: DeviceProfileType.DEFAULT, transportType: this.deviceWizardFormGroup.get('transportType').value, profileData: { @@ -229,11 +259,10 @@ export class DeviceWizardDialogComponent extends return this.deviceProfileService.saveDeviceProfile(deviceProfile).pipe( map(profile => profile.id), tap((profileId) => { - this.profileConfigFormGroup.patchValue({ + this.deviceWizardFormGroup.patchValue({ deviceProfileId: profileId, addProfileType: 0 }); - this.addDeviceWizardStepper.selectedIndex = 2; }) ); } else { @@ -241,7 +270,7 @@ export class DeviceWizardDialogComponent extends } } - private createdDevice(profileId: EntityId = this.profileConfigFormGroup.get('deviceProfileId').value): Observable> { + private createDevice(profileId: EntityId = this.deviceWizardFormGroup.get('deviceProfileId').value): Observable> { const device = { name: this.deviceWizardFormGroup.get('name').value, label: this.deviceWizardFormGroup.get('label').value, @@ -252,21 +281,21 @@ export class DeviceWizardDialogComponent extends }, customerId: null }; - if (this.specificConfigFormGroup.get('customerId').value) { + if (this.customerFormGroup.get('customerId').value) { device.customerId = { entityType: EntityType.CUSTOMER, - id: this.specificConfigFormGroup.get('customerId').value + id: this.customerFormGroup.get('customerId').value }; } return this.data.entitiesTableConfig.saveEntity(device); } - private saveCredential(device: BaseData): Observable { - if (this.specificConfigFormGroup.get('setCredential').value) { + private saveCredentials(device: BaseData): Observable { + if (this.credentialsFormGroup.get('setCredential').value) { return this.deviceService.getDeviceCredentials(device.id.id).pipe( mergeMap( (deviceCredentials) => { - const deviceCredentialsValue = {...deviceCredentials, ...this.specificConfigFormGroup.value.credential}; + const deviceCredentialsValue = {...deviceCredentials, ...this.credentialsFormGroup.value.credential}; return this.deviceService.saveDeviceCredentials(deviceCredentialsValue); } ), @@ -275,12 +304,28 @@ export class DeviceWizardDialogComponent extends return of(true); } + allValid(): boolean { + if (this.addDeviceWizardStepper.steps.find((item, index) => { + if (item.stepControl.invalid) { + item.interacted = true; + this.addDeviceWizardStepper.selectedIndex = index; + return true; + } else { + return false; + } + } )) { + return false; + } else { + return true; + } + } + changeStep($event: StepperSelectionEvent): void { this.selectedIndex = $event.selectedIndex; if (this.selectedIndex === this.maxStepperIndex) { - this.nextStepButtonLabel$.next('action.add'); + this.showNext = false; } else { - this.nextStepButtonLabel$.next('action.continue'); + this.showNext = true; } } } diff --git a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts index 3a7a7f9027..0aaf8e62e7 100644 --- a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts @@ -296,7 +296,7 @@ export class DevicesTableConfigResolver implements Resolve true, - onAction: ($event) => this.config.table.addEntity($event) + onAction: ($event) => this.deviceWizard($event) }, { name: this.translate.instant('device.import'), @@ -304,12 +304,6 @@ export class DevicesTableConfigResolver implements Resolve true, onAction: ($event) => this.importDevices($event) }, - { - name: this.translate.instant('device.wizard.device-wizard'), - icon: 'library_add', - isEnabled: () => true, - onAction: ($event) => this.deviceWizard($event) - }, ); } if (deviceScope === 'customer') { diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index addb726ed5..0bbc93eb12 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -72,6 +72,14 @@ export const deviceTransportTypeTranslationMap = new Map( + [ + [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default-hint'], + [DeviceTransportType.MQTT, 'device-profile.transport-type-mqtt-hint'], + [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m-hint'] + ] +); + export const mqttTransportPayloadTypeTranslationMap = new Map( [ [MqttTransportPayloadType.JSON, 'device-profile.mqtt-device-payload-type-json'], diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 2a9edbb6e4..08aa57b61a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -54,7 +54,8 @@ "share-via": "Share via {{provider}}", "continue": "Continue", "discard-changes": "Discard Changes", - "download": "Download" + "download": "Download", + "next-with-label": "Next: {{label}}" }, "aggregation": { "aggregation": "Aggregation", @@ -760,8 +761,7 @@ "wizard": { "device-wizard": "Device Wizard", "device-details": "Device details", - "profile-configuration": "Profile configuration", - "new-device-profile": "New device profile", + "new-device-profile": "Create new device profile", "existing-device-profile": "Select existing device profile", "specific-configuration": "Specific configuration", "customer-to-assign-device": "Customer to assign the device", @@ -784,6 +784,8 @@ "set-default": "Make device profile default", "delete": "Delete device profile", "copyId": "Copy device profile Id", + "new-device-profile-name": "Device profile name", + "new-device-profile-name-required": "Device profile name is required.", "name": "Name", "name-required": "Name is required.", "type": "Profile type", @@ -792,8 +794,11 @@ "transport-type": "Transport type", "transport-type-required": "Transport type is required.", "transport-type-default": "Default", + "transport-type-default-hint": "Default transport type", "transport-type-mqtt": "MQTT", + "transport-type-mqtt-hint": "MQTT transport type", "transport-type-lwm2m": "LWM2M", + "transport-type-lwm2m-hint": "LWM2M transport type", "description": "Description", "default": "Default", "profile-configuration": "Profile configuration", @@ -824,7 +829,8 @@ "not-valid-multi-character": "Invalid use of a multi-level wildcard character", "single-level-wildcards-hint": "[+] is suitable for any topic filter level. Ex.: v1/devices/+/telemetry or +/devices/+/attributes.", "multi-level-wildcards-hint": "[#] can replace the topic filter itself and must be the last symbol of the topic. Ex.: # or v1/devices/me/#.", - "alarm-rules": "Alarm rules ({{count}})", + "alarm-rules": "Alarm rules", + "alarm-rules-with-count": "Alarm rules ({{count}})", "no-alarm-rules": "No alarm rules configured", "add-alarm-rule": "Add alarm rule", "edit-alarm-rule": "Edit alarm rule", From 93278f7236555b26f52af3a8e4fdebc80037cee1 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Fri, 9 Oct 2020 17:32:32 +0300 Subject: [PATCH 153/463] Refactoring according to comments --- .../device/DeviceProvisionServiceImpl.java | 105 ++++-------------- .../transport/DefaultTransportApiService.java | 46 ++++---- .../dao/device/DeviceProvisionService.java | 6 +- .../server/dao/device/DeviceService.java | 4 + .../provision/ProvisionFailedException.java | 22 ++++ .../provision/ProvisionResponseStatus.java | 1 - .../ProvisionDeviceCredentialsData.java | 2 +- .../DeviceProfileProvisionConfiguration.java | 4 +- common/queue/src/main/proto/queue.proto | 10 -- .../src/main/proto/transport.proto | 5 - .../server/dao/device/DeviceServiceImpl.java | 46 ++++++++ 11 files changed, 125 insertions(+), 126 deletions(-) create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionFailedException.java diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 5a007d27ca..ab333442c4 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.device; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -28,9 +29,6 @@ import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.audit.ActionType; -import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; -import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; -import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; @@ -49,6 +47,7 @@ import org.thingsboard.server.dao.device.DeviceDao; import org.thingsboard.server.dao.device.DeviceProfileDao; import org.thingsboard.server.dao.device.DeviceProvisionService; import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; @@ -115,81 +114,55 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { String provisionRequestSecret = provisionRequest.getCredentials().getProvisionDeviceSecret(); if (StringUtils.isEmpty(provisionRequestKey) || StringUtils.isEmpty(provisionRequestSecret)) { - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); - } - - if (provisionRequest.getCredentialsType() != null) { - ListenableFuture error = validateCredentials(provisionRequest); - if (error != null) { - return error; - } + throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); } DeviceProfile targetProfile = deviceProfileDao.findByProvisionDeviceKey(provisionRequestKey); - if (targetProfile == null) { - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); + if (targetProfile == null || targetProfile.getProfileData().getProvisionConfiguration() == null || + targetProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret() == null) { + throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); } Device targetDevice = deviceDao.findDeviceByTenantIdAndName(targetProfile.getTenantId().getId(), provisionRequest.getDeviceName()).orElse(null); switch (targetProfile.getProvisionType()) { case ALLOW_CREATE_NEW_DEVICES: - if (((AllowCreateNewDevicesDeviceProfileProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration()).getProvisionDeviceSecret().equals(provisionRequestSecret)) { + if (targetProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret().equals(provisionRequestSecret)) { if (targetDevice != null) { log.warn("[{}] The device is present and could not be provisioned once more!", targetDevice.getName()); notify(targetDevice, provisionRequest, DataConstants.PROVISION_FAILURE, false); - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } else { return createDevice(provisionRequest, targetProfile); } } break; case CHECK_PRE_PROVISIONED_DEVICES: - if (((CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration()).getProvisionDeviceSecret().equals(provisionRequestSecret)) { + if (targetProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret().equals(provisionRequestSecret)) { if (targetDevice != null && targetDevice.getDeviceProfileId().equals(targetProfile.getId())) { return processProvision(targetDevice, provisionRequest); } else { log.warn("[{}] Failed to find pre provisioned device!", provisionRequest.getDeviceName()); - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } } break; } - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); - } - - private ListenableFuture validateCredentials(ProvisionRequest provisionRequest) { - switch (provisionRequest.getCredentialsType()) { - case MQTT_BASIC: - if (StringUtils.isEmpty(provisionRequest.getCredentialsData().getClientId()) || - StringUtils.isEmpty(provisionRequest.getCredentialsData().getUsername()) || - StringUtils.isEmpty(provisionRequest.getCredentialsData().getPassword())) { - log.error("Failed to get basic mqtt credentials from credentials data!"); - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); - } - break; - case X509_CERTIFICATE: - if (StringUtils.isEmpty(provisionRequest.getCredentialsData().getHash())) { - log.error("Failed to get hash from credentials data!"); - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); - } - break; - } - return null; + throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); } private ListenableFuture processProvision(Device device, ProvisionRequest provisionRequest) { ListenableFuture> provisionStateFuture = attributesService.find(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, DEVICE_PROVISION_STATE); ListenableFuture provisionedFuture = Futures.transformAsync(provisionStateFuture, optionalAtr -> { - if (optionalAtr.isPresent()) { + if (optionalAtr != null && optionalAtr.isPresent()) { String state = optionalAtr.get().getValueAsString(); if (state.equals(PROVISIONED_STATE)) { return Futures.immediateFuture(true); } else { log.error("[{}][{}] Unknown provision state: {}!", device.getName(), DEVICE_PROVISION_STATE, state); - return Futures.immediateCancelledFuture(); + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } } return Futures.transform(saveProvisionStateAttribute(device), input -> false, MoreExecutors.directExecutor()); @@ -200,7 +173,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { return Futures.transform(provisionedFuture, provisioned -> { if (provisioned) { notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); - return new ProvisionResponse(null, ProvisionResponseStatus.FAILURE); + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } notify(device, provisionRequest, DataConstants.PROVISION_SUCCESS, true); return new ProvisionResponse(deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()), ProvisionResponseStatus.SUCCESS); @@ -224,7 +197,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private ListenableFuture processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName()); if (device == null) { - Device savedDevice = saveDevice(provisionRequest, profile); + Device savedDevice = deviceService.saveDevice(provisionRequest, profile); deviceStateService.onDeviceAdded(savedDevice); pushDeviceCreatedEventToRuleEngine(savedDevice); @@ -234,10 +207,11 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { new ProvisionResponse( getDeviceCredentials(savedDevice), ProvisionResponseStatus.SUCCESS), MoreExecutors.directExecutor()); + } else { + log.warn("[{}] The device is already provisioned!", device.getName()); + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } - log.warn("[{}] The device is already provisioned!", device.getName()); - notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); } private ListenableFuture> saveProvisionStateAttribute(Device device) { @@ -246,51 +220,16 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { System.currentTimeMillis()))); } - private Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { - Device device = new Device(); - device.setName(provisionRequest.getDeviceName()); - device.setType(profile.getName()); - device.setTenantId(profile.getTenantId()); - Device savedDevice = deviceService.saveDevice(device); - if (!StringUtils.isEmpty(provisionRequest.getCredentialsData().getToken()) || - !StringUtils.isEmpty(provisionRequest.getCredentialsData().getHash()) || - !StringUtils.isEmpty(provisionRequest.getCredentialsData().getUsername()) || - !StringUtils.isEmpty(provisionRequest.getCredentialsData().getPassword()) || - !StringUtils.isEmpty(provisionRequest.getCredentialsData().getClientId())) { - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getTenantId(), savedDevice.getId()); - deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType()); - switch (provisionRequest.getCredentialsType()) { - case ACCESS_TOKEN: - deviceCredentials.setDeviceId(savedDevice.getId()); - deviceCredentials.setCredentialsId(provisionRequest.getCredentialsData().getToken()); - break; - case MQTT_BASIC: - BasicMqttCredentials mqttCredentials = new BasicMqttCredentials(); - mqttCredentials.setClientId(provisionRequest.getCredentialsData().getClientId()); - mqttCredentials.setUserName(provisionRequest.getCredentialsData().getUsername()); - mqttCredentials.setPassword(provisionRequest.getCredentialsData().getPassword()); - deviceCredentials.setCredentialsValue(JacksonUtil.toString(mqttCredentials)); - break; - case X509_CERTIFICATE: - deviceCredentials.setCredentialsValue(provisionRequest.getCredentialsData().getHash()); - break; - } - deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType()); - deviceCredentialsService.updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials); - } - return savedDevice; - } - private DeviceCredentials getDeviceCredentials(Device device) { return deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); } private void pushProvisionEventToRuleEngine(ProvisionRequest request, Device device, String type) { try { - ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(request); - TbMsg msg = TbMsg.newMsg(type, device.getId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); + JsonNode entityNode = JacksonUtil.valueToTree(request); + TbMsg msg = TbMsg.newMsg(type, device.getId(), createTbMsgMetaData(device), JacksonUtil.toString(entityNode)); sendToRuleEngine(device.getTenantId(), msg, null); - } catch (JsonProcessingException | IllegalArgumentException e) { + } catch (IllegalArgumentException e) { log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), type, e); } } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index eb2fdbc65e..7791ec67a7 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -50,13 +50,11 @@ import org.thingsboard.server.dao.device.DeviceProvisionService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; -import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.gen.transport.TransportProtos.CredentialsType; import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; @@ -70,6 +68,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenR import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.state.DeviceStateService; @@ -276,30 +275,29 @@ public class DefaultTransportApiService implements TransportApiService { }, dbCallbackExecutorService); } - private ListenableFuture handle(ProvisionDeviceRequestMsg requestMsg) { ListenableFuture provisionResponseFuture = null; - provisionResponseFuture = deviceProvisionService.provisionDevice( - new ProvisionRequest( - requestMsg.getDeviceName(), - requestMsg.getCredentialsType() != null ? DeviceCredentialsType.valueOf(requestMsg.getCredentialsType().name()) : null, - new ProvisionDeviceCredentialsData(requestMsg.getCredentialsDataProto().getValidateDeviceTokenRequestMsg().getToken(), - requestMsg.getCredentialsDataProto().getValidateBasicMqttCredRequestMsg().getClientId(), - requestMsg.getCredentialsDataProto().getValidateBasicMqttCredRequestMsg().getUserName(), - requestMsg.getCredentialsDataProto().getValidateBasicMqttCredRequestMsg().getPassword(), - requestMsg.getCredentialsDataProto().getValidateDeviceX509CertRequestMsg().getHash()), - new ProvisionDeviceProfileCredentials( - requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceKey(), - requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret()))); - return Futures.transform(provisionResponseFuture, provisionResponse -> { - if (provisionResponse.getResponseStatus() == ProvisionResponseStatus.NOT_FOUND) { - return getTransportApiResponseMsg(TransportProtos.DeviceCredentialsProto.getDefaultInstance(), TransportProtos.ProvisionResponseStatus.NOT_FOUND); - } else if (provisionResponse.getResponseStatus() == ProvisionResponseStatus.FAILURE) { - return getTransportApiResponseMsg(TransportProtos.DeviceCredentialsProto.getDefaultInstance(), TransportProtos.ProvisionResponseStatus.FAILURE); - } else { - return getTransportApiResponseMsg(getDeviceCredentials(provisionResponse.getDeviceCredentials()), TransportProtos.ProvisionResponseStatus.SUCCESS); - } - }, dbCallbackExecutorService); + try { + provisionResponseFuture = deviceProvisionService.provisionDevice( + new ProvisionRequest( + requestMsg.getDeviceName(), + requestMsg.getCredentialsType() != null ? DeviceCredentialsType.valueOf(requestMsg.getCredentialsType().name()) : null, + new ProvisionDeviceCredentialsData(requestMsg.getCredentialsDataProto().getValidateDeviceTokenRequestMsg().getToken(), + requestMsg.getCredentialsDataProto().getValidateBasicMqttCredRequestMsg().getClientId(), + requestMsg.getCredentialsDataProto().getValidateBasicMqttCredRequestMsg().getUserName(), + requestMsg.getCredentialsDataProto().getValidateBasicMqttCredRequestMsg().getPassword(), + requestMsg.getCredentialsDataProto().getValidateDeviceX509CertRequestMsg().getHash()), + new ProvisionDeviceProfileCredentials( + requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceKey(), + requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret()))); + } catch (ProvisionFailedException e) { + return Futures.immediateFuture(getTransportApiResponseMsg( + TransportProtos.DeviceCredentialsProto.getDefaultInstance(), + TransportProtos.ProvisionResponseStatus.valueOf(e.getMessage()))); + } + return Futures.transform(provisionResponseFuture, provisionResponse -> getTransportApiResponseMsg( + getDeviceCredentials(provisionResponse.getDeviceCredentials()), TransportProtos.ProvisionResponseStatus.SUCCESS), + dbCallbackExecutorService); } private TransportApiResponseMsg getTransportApiResponseMsg(TransportProtos.DeviceCredentialsProto deviceCredentials, TransportProtos.ProvisionResponseStatus status) { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java index 28456fb4fe..2a0d651801 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java @@ -16,9 +16,13 @@ package org.thingsboard.server.dao.device; import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; public interface DeviceProvisionService { - ListenableFuture provisionDevice(ProvisionRequest provisionRequest); + + ListenableFuture provisionDevice(ProvisionRequest provisionRequest) throws ProvisionFailedException; } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index ba2f09ae73..715313538d 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.device; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceInfo; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.id.CustomerId; @@ -26,6 +27,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.device.provision.ProvisionRequest; import java.util.List; @@ -83,4 +85,6 @@ public interface DeviceService { Device assignDeviceToTenant(TenantId tenantId, Device device); + Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile); + } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionFailedException.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionFailedException.java new file mode 100644 index 0000000000..04e8e00ce5 --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionFailedException.java @@ -0,0 +1,22 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.device.provision; + +public class ProvisionFailedException extends RuntimeException { + public ProvisionFailedException(String errorMsg) { + super(errorMsg); + } +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java index 46764f1b67..d4fbe65a97 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java @@ -20,5 +20,4 @@ public enum ProvisionResponseStatus { SUCCESS, NOT_FOUND, FAILURE - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/ProvisionDeviceCredentialsData.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/ProvisionDeviceCredentialsData.java index a3e70afd83..fe6cc2621a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/ProvisionDeviceCredentialsData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/ProvisionDeviceCredentialsData.java @@ -23,5 +23,5 @@ public class ProvisionDeviceCredentialsData { private final String clientId; private final String username; private final String password; - private final String hash; + private final String x509CertHash; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java index 140c7334ec..f892aabf76 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java @@ -33,7 +33,9 @@ import org.thingsboard.server.common.data.DeviceProfileProvisionType; @JsonSubTypes.Type(value = CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration.class, name = "CHECK_PRE_PROVISIONED_DEVICES")}) public interface DeviceProfileProvisionConfiguration { + String getProvisionDeviceSecret(); + @JsonIgnore DeviceProfileProvisionType getType(); -} \ No newline at end of file +} diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 7c2ae80f9b..166f94b864 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -268,11 +268,6 @@ message ProvisionDeviceRequestMsg { CredentialsDataProto credentialsDataProto = 4; } -message GatewayProvisionRequestMsg { - int32 requestId = 1; - ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 2; -} - message ProvisionDeviceCredentialsMsg { string provisionDeviceKey = 1; string provisionDeviceSecret = 2; @@ -283,11 +278,6 @@ message ProvisionDeviceResponseMsg { ProvisionResponseStatus provisionResponseStatus = 2; } -message GatewayProvisionResponseMsg { - int32 requestId = 1; - ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 2; -} - enum ProvisionResponseStatus { UNKNOWN = 0; SUCCESS = 1; diff --git a/common/transport/transport-api/src/main/proto/transport.proto b/common/transport/transport-api/src/main/proto/transport.proto index 5eff9313fa..b536c22198 100644 --- a/common/transport/transport-api/src/main/proto/transport.proto +++ b/common/transport/transport-api/src/main/proto/transport.proto @@ -72,11 +72,6 @@ message GatewayAttributesMsg { repeated AttributesMsg msg = 1; } -message GatewayProvisionResponseMsg { - string deviceName = 1; - transport.ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 2; -} - message GatewayRpcResponseMsg { string deviceName = 1; int32 id = 2; diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index ad66ed47fd..1df488ff63 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.device.DeviceSearchQuery; +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.DeviceData; @@ -57,6 +58,9 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.dao.customer.CustomerDao; +import org.thingsboard.server.dao.device.provision.ProvisionFailedException; +import org.thingsboard.server.dao.device.provision.ProvisionRequest; +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.event.EventService; @@ -64,6 +68,7 @@ import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.tenant.TenantDao; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; import javax.annotation.Nullable; import java.util.ArrayList; @@ -466,6 +471,47 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return doSaveDevice(device, null); } + @Override + @Transactional + public Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { + Device device = new Device(); + device.setName(provisionRequest.getDeviceName()); + device.setType(profile.getName()); + device.setTenantId(profile.getTenantId()); + Device savedDevice = saveDevice(device); + if (!StringUtils.isEmpty(provisionRequest.getCredentialsData().getToken()) || + !StringUtils.isEmpty(provisionRequest.getCredentialsData().getX509CertHash()) || + !StringUtils.isEmpty(provisionRequest.getCredentialsData().getUsername()) || + !StringUtils.isEmpty(provisionRequest.getCredentialsData().getPassword()) || + !StringUtils.isEmpty(provisionRequest.getCredentialsData().getClientId())) { + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getTenantId(), savedDevice.getId()); + deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType()); + switch (provisionRequest.getCredentialsType()) { + case ACCESS_TOKEN: + deviceCredentials.setDeviceId(savedDevice.getId()); + deviceCredentials.setCredentialsId(provisionRequest.getCredentialsData().getToken()); + break; + case MQTT_BASIC: + BasicMqttCredentials mqttCredentials = new BasicMqttCredentials(); + mqttCredentials.setClientId(provisionRequest.getCredentialsData().getClientId()); + mqttCredentials.setUserName(provisionRequest.getCredentialsData().getUsername()); + mqttCredentials.setPassword(provisionRequest.getCredentialsData().getPassword()); + deviceCredentials.setCredentialsValue(JacksonUtil.toString(mqttCredentials)); + break; + case X509_CERTIFICATE: + deviceCredentials.setCredentialsValue(provisionRequest.getCredentialsData().getX509CertHash()); + break; + } + deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType()); + try { + deviceCredentialsService.updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials); + } catch (Exception e) { + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); + } + } + return savedDevice; + } + private DataValidator deviceValidator = new DataValidator() { From 30a6a462da5d42a63c2a1e4f56f27f7f53cf1175 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 8 Oct 2020 20:43:08 +0300 Subject: [PATCH 154/463] UI: Improvement view device profile settings --- .../filter-predicate-value.component.html | 1 - .../filter-predicate-value.component.ts | 2 +- .../home/components/home-components.module.ts | 3 - .../add-device-profile-dialog.component.html | 2 +- .../add-device-profile-dialog.component.scss | 1 + .../profile/alarm/alarm-rule.component.html | 126 +++++++++--------- .../profile/alarm/alarm-rule.component.ts | 9 +- .../device-profile-alarms.component.scss | 4 + .../device-profile-data.component.html | 55 -------- .../profile/device-profile-data.component.ts | 110 --------------- .../profile/device-profile.component.html | 17 +-- .../profile/device-profile.component.ts | 6 +- ...evice-profile-configuration.component.html | 6 +- ...ile-transport-configuration.component.html | 6 +- ...ofile-transport-configuration.component.ts | 2 +- .../device-profile-tabs.component.html | 49 ++++++- .../device-profile-tabs.component.ts | 5 +- .../device-profiles-table-config.resolver.ts | 2 + ...evice-transport-configuration.component.ts | 2 +- ui-ngx/src/app/shared/models/device.models.ts | 34 ++--- 20 files changed, 152 insertions(+), 290 deletions(-) delete mode 100644 ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.ts diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html index 52f0b0eeea..524285ea3f 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html @@ -76,7 +76,6 @@ type="button" matTooltip="{{ (dynamicMode ? 'filter.switch-to-default-value' : 'filter.switch-to-dynamic-value') | translate }}" matTooltipPosition="above" - *ngIf="allow" (click)="dynamicMode = !dynamicMode"> diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.ts b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.ts index d12a0fe2f2..9c55647c51 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.ts @@ -54,7 +54,7 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn if (allow) { this.dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_USER); } else { - this.dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_DEVICE); + this.dynamicValueSourceTypes = [DynamicValueSourceType.CURRENT_DEVICE]; } } diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index e33960b417..9bbac4154d 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -90,7 +90,6 @@ import { TenantProfileDialogComponent } from './profile/tenant-profile-dialog.co import { TenantProfileDataComponent } from './profile/tenant-profile-data.component'; import { DefaultDeviceProfileConfigurationComponent } from './profile/device/default-device-profile-configuration.component'; import { DeviceProfileConfigurationComponent } from './profile/device/device-profile-configuration.component'; -import { DeviceProfileDataComponent } from './profile/device-profile-data.component'; import { DeviceProfileComponent } from './profile/device-profile.component'; import { DefaultDeviceProfileTransportConfigurationComponent } from './profile/device/default-device-profile-transport-configuration.component'; import { DeviceProfileTransportConfigurationComponent } from './profile/device/device-profile-transport-configuration.component'; @@ -195,7 +194,6 @@ import { DeviceCredentialsComponent } from './device/device-credentials.componen AlarmRuleConditionComponent, DeviceProfileAlarmComponent, DeviceProfileAlarmsComponent, - DeviceProfileDataComponent, DeviceProfileComponent, DeviceProfileDialogComponent, AddDeviceProfileDialogComponent, @@ -277,7 +275,6 @@ import { DeviceCredentialsComponent } from './device/device-credentials.componen AlarmRuleConditionComponent, DeviceProfileAlarmComponent, DeviceProfileAlarmsComponent, - DeviceProfileDataComponent, DeviceProfileComponent, DeviceProfileDialogComponent, AddDeviceProfileDialogComponent, diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html index df70ea4e8f..7a93c0487f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html @@ -45,7 +45,7 @@ labelText="device-profile.default-rule-chain" formControlName="defaultRuleChainId"> - + device-profile.type diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.scss b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.scss index cafcce74b6..69bf6b3e53 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.scss @@ -32,6 +32,7 @@ .mat-stepper-horizontal { .mat-horizontal-content-container { overflow: auto; + min-height: 260px; } } } diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html index 28c286f33c..66eb99f27e 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html @@ -21,74 +21,72 @@ -
-
- - device-profile.condition-type - - - {{ alarmConditionTypeTranslation.get(alarmConditionType) | translate }} +
+ + device-profile.condition-type + + + {{ alarmConditionTypeTranslation.get(alarmConditionType) | translate }} + + + + {{ 'device-profile.condition-type-required' | translate }} + + +
+ + + + + {{ 'device-profile.condition-duration-value-required' | translate }} + + + {{ 'device-profile.condition-duration-value-range' | translate }} + + + {{ 'device-profile.condition-duration-value-range' | translate }} + + + {{ 'device-profile.condition-duration-value-pattern' | translate }} + + + + + + + {{ timeUnitTranslations.get(timeUnit) | translate }} - - {{ 'device-profile.condition-type-required' | translate }} + + {{ 'device-profile.condition-duration-time-unit-required' | translate }} + + +
+
+ + + + + {{ 'device-profile.condition-repeating-value-required' | translate }} + + + {{ 'device-profile.condition-repeating-value-range' | translate }} + + + {{ 'device-profile.condition-repeating-value-range' | translate }} + + + {{ 'device-profile.condition-repeating-value-pattern' | translate }} -
- - - - - {{ 'device-profile.condition-duration-value-required' | translate }} - - - {{ 'device-profile.condition-duration-value-range' | translate }} - - - {{ 'device-profile.condition-duration-value-range' | translate }} - - - {{ 'device-profile.condition-duration-value-pattern' | translate }} - - - - - - - {{ timeUnitTranslations.get(timeUnit) | translate }} - - - - {{ 'device-profile.condition-duration-time-unit-required' | translate }} - - -
-
- - - - - {{ 'device-profile.condition-repeating-value-required' | translate }} - - - {{ 'device-profile.condition-repeating-value-range' | translate }} - - - {{ 'device-profile.condition-repeating-value-range' | translate }} - - - {{ 'device-profile.condition-repeating-value-pattern' | translate }} - - -
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts index 7b63660362..e9a6428533 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts @@ -29,6 +29,7 @@ import { AlarmConditionType, AlarmConditionTypeTranslationMap, AlarmRule } from import { MatDialog } from '@angular/material/dialog'; import { TimeUnit, timeUnitTranslationMap } from '@shared/models/time/time.models'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { isDefinedAndNotNull } from '@core/utils'; @Component({ selector: 'tb-alarm-rule', @@ -117,13 +118,11 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat writeValue(value: AlarmRule): void { this.modelValue = value; - if (this.modelValue?.condition?.spec === null) { - this.modelValue.condition.spec = { - type: AlarmConditionType.SIMPLE - }; + if (!isDefinedAndNotNull(this.modelValue?.condition?.spec)) { + this.modelValue = Object.assign({}, this.modelValue, {condition: {spec: {type: AlarmConditionType.SIMPLE}}}); } this.alarmRuleFormGroup.reset(this.modelValue || undefined, {emitEvent: false}); - this.updateValidators(this.modelValue?.condition?.spec?.type); + this.updateValidators(this.modelValue.condition.spec.type); } public validate(c: FormControl) { diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.scss index 4bb4c03b37..18883f943b 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.scss @@ -25,4 +25,8 @@ } } } + + .tb-prompt{ + margin: 30px 0; + } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html index 2cc3ccd9e2..e69de29bb2 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html @@ -1,55 +0,0 @@ - -
- - - - -
device-profile.profile-configuration
-
-
- - -
- - - -
device-profile.transport-configuration
-
-
- - -
- - - -
{{'device-profile.alarm-rules-with-count' | translate: - {count: deviceProfileDataFormGroup.get('alarms').value ? - deviceProfileDataFormGroup.get('alarms').value.length : 0} }}
-
-
- - -
-
-
diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.ts deleted file mode 100644 index 7d7fc55057..0000000000 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.ts +++ /dev/null @@ -1,110 +0,0 @@ -/// -/// Copyright © 2016-2020 The Thingsboard Authors -/// -/// Licensed under the Apache License, Version 2.0 (the "License"); -/// you may not use this file except in compliance with the License. -/// You may obtain a copy of the License at -/// -/// http://www.apache.org/licenses/LICENSE-2.0 -/// -/// Unless required by applicable law or agreed to in writing, software -/// distributed under the License is distributed on an "AS IS" BASIS, -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -/// See the License for the specific language governing permissions and -/// limitations under the License. -/// - -import { Component, forwardRef, Input, OnInit } from '@angular/core'; -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; -import { Store } from '@ngrx/store'; -import { AppState } from '@app/core/core.state'; -import { coerceBooleanProperty } from '@angular/cdk/coercion'; -import { - DeviceProfileData, - DeviceProfileType, - deviceProfileTypeConfigurationInfoMap, - DeviceTransportType, deviceTransportTypeConfigurationInfoMap -} from '@shared/models/device.models'; - -@Component({ - selector: 'tb-device-profile-data', - templateUrl: './device-profile-data.component.html', - styleUrls: [], - providers: [{ - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => DeviceProfileDataComponent), - multi: true - }] -}) -export class DeviceProfileDataComponent implements ControlValueAccessor, OnInit { - - deviceProfileDataFormGroup: FormGroup; - - private requiredValue: boolean; - get required(): boolean { - return this.requiredValue; - } - @Input() - set required(value: boolean) { - this.requiredValue = coerceBooleanProperty(value); - } - - @Input() - disabled: boolean; - - displayProfileConfiguration: boolean; - displayTransportConfiguration: boolean; - - private propagateChange = (v: any) => { }; - - constructor(private store: Store, - private fb: FormBuilder) { - } - - registerOnChange(fn: any): void { - this.propagateChange = fn; - } - - registerOnTouched(fn: any): void { - } - - ngOnInit() { - this.deviceProfileDataFormGroup = this.fb.group({ - configuration: [null, Validators.required], - transportConfiguration: [null, Validators.required], - alarms: [null] - }); - this.deviceProfileDataFormGroup.valueChanges.subscribe(() => { - this.updateModel(); - }); - } - - setDisabledState(isDisabled: boolean): void { - this.disabled = isDisabled; - if (this.disabled) { - this.deviceProfileDataFormGroup.disable({emitEvent: false}); - } else { - this.deviceProfileDataFormGroup.enable({emitEvent: false}); - } - } - - writeValue(value: DeviceProfileData | null): void { - const deviceProfileType = value?.configuration?.type; - this.displayProfileConfiguration = deviceProfileType && - deviceProfileTypeConfigurationInfoMap.get(deviceProfileType).hasProfileConfiguration; - const deviceTransportType = value?.transportConfiguration?.type; - this.displayTransportConfiguration = deviceTransportType && - deviceTransportTypeConfigurationInfoMap.get(deviceTransportType).hasProfileConfiguration; - this.deviceProfileDataFormGroup.patchValue({configuration: value?.configuration}, {emitEvent: false}); - this.deviceProfileDataFormGroup.patchValue({transportConfiguration: value?.transportConfiguration}, {emitEvent: false}); - this.deviceProfileDataFormGroup.patchValue({alarms: value?.alarms}, {emitEvent: false}); - } - - private updateModel() { - let deviceProfileData: DeviceProfileData = null; - if (this.deviceProfileDataFormGroup.valid) { - deviceProfileData = this.deviceProfileDataFormGroup.getRawValue(); - } - this.propagateChange(deviceProfileData); - } -} diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html index 8af17883c2..780d98adfe 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html @@ -53,7 +53,7 @@ labelText="device-profile.default-rule-chain" formControlName="defaultRuleChainId"> - + device-profile.type @@ -64,21 +64,6 @@ {{ 'device-profile.type-required' | translate }} - - device-profile.transport-type - - - {{deviceTransportTypeTranslations.get(type) | translate}} - - - - {{ 'device-profile.transport-type-required' | translate }} - - - - device-profile.description diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts index e3c441c8a2..d7bd195171 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts @@ -77,7 +77,11 @@ export class DeviceProfileComponent extends EntityComponent { name: [entity ? entity.name : '', [Validators.required]], type: [entity ? entity.type : null, [Validators.required]], transportType: [entity ? entity.transportType : null, [Validators.required]], - profileData: [entity && !this.isAdd ? entity.profileData : {}, []], + profileData: this.fb.group({ + configuration: [entity && !this.isAdd ? entity.profileData?.configuration : {}, Validators.required], + transportConfiguration: [entity && !this.isAdd ? entity.profileData?.transportConfiguration : {}, Validators.required], + alarms: [entity && !this.isAdd ? entity.profileData?.alarms : []] + }), defaultRuleChainId: [entity && entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null, []], description: [entity ? entity.description : '', []], } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/default-device-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/default-device-profile-configuration.component.html index 200d3d3623..796485d200 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/default-device-profile-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/default-device-profile-configuration.component.html @@ -16,9 +16,5 @@ -->
- - +
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/default-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/default-device-profile-transport-configuration.component.html index 94890f3673..061e72c4e4 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/default-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/default-device-profile-transport-configuration.component.html @@ -16,9 +16,5 @@ -->
- - +
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m-device-profile-transport-configuration.component.ts index ed81a143fd..7a7a1d285f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m-device-profile-transport-configuration.component.ts @@ -89,7 +89,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro let configuration: DeviceProfileTransportConfiguration = null; if (this.lwm2mDeviceProfileTransportConfigurationFormGroup.valid) { configuration = this.lwm2mDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration; - configuration.type = DeviceTransportType.LWM2M; + // configuration.type = DeviceTransportType.LWM2M; } this.propagateChange(configuration); } diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html index 40a9f55aa5..7531eed659 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html @@ -16,6 +16,49 @@ --> +
+ + device-profile.transport-type + + + {{deviceTransportTypeTranslations.get(type) | translate}} + + + + {{ 'device-profile.transport-type-required' | translate }} + + +
+ + +
+
+
+ +
+
+ +
+
+
+ +
+
+ + +
+
+
+ - - - diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.ts b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.ts index 9cf18c498e..6410e06950 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.ts @@ -18,7 +18,7 @@ import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityTabsComponent } from '../../components/entity/entity-tabs.component'; -import { DeviceProfile } from '@shared/models/device.models'; +import { DeviceProfile, DeviceTransportType, deviceTransportTypeTranslationMap } from '@shared/models/device.models'; @Component({ selector: 'tb-device-profile-tabs', @@ -27,6 +27,9 @@ import { DeviceProfile } from '@shared/models/device.models'; }) export class DeviceProfileTabsComponent extends EntityTabsComponent { + deviceTransportTypes = Object.keys(DeviceTransportType); + deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; + constructor(protected store: Store) { super(store); } diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts index d59c7225c5..60cb15c199 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts @@ -59,6 +59,8 @@ export class DeviceProfilesTableConfigResolver implements Resolve Date: Fri, 9 Oct 2020 16:05:23 +0300 Subject: [PATCH 155/463] UI: Improvement UI device profile alarm scheduler, added text select time interval --- .../profile/alarm/alarm-rule.component.ts | 8 +- .../alarm/alarm-schedule.component.html | 315 ++++++++++-------- .../alarm/alarm-schedule.component.scss | 20 ++ .../profile/alarm/alarm-schedule.component.ts | 51 ++- .../device-profile-tabs.component.html | 2 +- 5 files changed, 233 insertions(+), 163 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.scss diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts index e9a6428533..fe2916b854 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts @@ -29,7 +29,7 @@ import { AlarmConditionType, AlarmConditionTypeTranslationMap, AlarmRule } from import { MatDialog } from '@angular/material/dialog'; import { TimeUnit, timeUnitTranslationMap } from '@shared/models/time/time.models'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; -import { isDefinedAndNotNull } from '@core/utils'; +import { isUndefined } from '@core/utils'; @Component({ selector: 'tb-alarm-rule', @@ -118,11 +118,11 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat writeValue(value: AlarmRule): void { this.modelValue = value; - if (!isDefinedAndNotNull(this.modelValue?.condition?.spec)) { - this.modelValue = Object.assign({}, this.modelValue, {condition: {spec: {type: AlarmConditionType.SIMPLE}}}); + if (this.modelValue !== null && isUndefined(this.modelValue?.condition?.spec)) { + this.modelValue = Object.assign(this.modelValue, {condition: {spec: {type: AlarmConditionType.SIMPLE}}}); } this.alarmRuleFormGroup.reset(this.modelValue || undefined, {emitEvent: false}); - this.updateValidators(this.modelValue.condition.spec.type); + this.updateValidators(this.modelValue?.condition?.spec?.type); } public validate(c: FormControl) { diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.html index cad6e5cf03..03c0586bb3 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.html @@ -35,7 +35,7 @@
device-profile.schedule-days
-
+
{{ 'device-profile.schedule-day.monday' | translate }} @@ -64,158 +64,189 @@
device-profile.schedule-time
- - device-profile.schedule-time-from - - - - - - device-profile.schedule-time-to - - - - +
+ + device-profile.schedule-time-from + + + + + + device-profile.schedule-time-to + + + + +
+
+
+
+
device-profile.schedule-days
-
-
-
- - {{ 'device-profile.schedule-day.monday' | translate }} - -
- - device-profile.schedule-time-from - - - - - - device-profile.schedule-time-to - - - - -
+
+
+ + {{ 'device-profile.schedule-day.monday' | translate }} + +
+ + device-profile.schedule-time-from + + + + + + device-profile.schedule-time-to + + + +
-
- - {{ 'device-profile.schedule-day.tuesday' | translate }} - -
- - device-profile.schedule-time-from - - - - - - device-profile.schedule-time-to - - - - -
+
-
- - {{ 'device-profile.schedule-day.wednesday' | translate }} - -
- - device-profile.schedule-time-from - - - - - - device-profile.schedule-time-to - - - - -
+
+
+ + {{ 'device-profile.schedule-day.tuesday' | translate }} + +
+ + device-profile.schedule-time-from + + + + + + device-profile.schedule-time-to + + + +
-
- - {{ 'device-profile.schedule-day.thursday' | translate }} - -
- - device-profile.schedule-time-from - - - - - - device-profile.schedule-time-to - - - - -
+
-
-
- - {{ 'device-profile.schedule-day.friday' | translate }} - -
- - device-profile.schedule-time-from - - - - - - device-profile.schedule-time-to - - - - -
+
+ + {{ 'device-profile.schedule-day.wednesday' | translate }} + +
+ + device-profile.schedule-time-from + + + + + + device-profile.schedule-time-to + + + + +
+
+
+
+
+ + {{ 'device-profile.schedule-day.thursday' | translate }} + +
+ + device-profile.schedule-time-from + + + + + + device-profile.schedule-time-to + + + +
-
- - {{ 'device-profile.schedule-day.saturday' | translate }} - -
- - device-profile.schedule-time-from - - - - - - device-profile.schedule-time-to - - - - -
+
+
+
+
+ + {{ 'device-profile.schedule-day.friday' | translate }} + +
+ + device-profile.schedule-time-from + + + + + + device-profile.schedule-time-to + + + + +
+
+
+
+
+ + {{ 'device-profile.schedule-day.saturday' | translate }} + +
+ + device-profile.schedule-time-from + + + + + + device-profile.schedule-time-to + + + + +
+
+
+
+
+ + {{ 'device-profile.schedule-day.sunday' | translate }} + +
+ + device-profile.schedule-time-from + + + + + + device-profile.schedule-time-to + + + +
-
- - {{ 'device-profile.schedule-day.sunday' | translate }} - -
- - device-profile.schedule-time-from - - - - - - device-profile.schedule-time-to - - - - -
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.scss new file mode 100644 index 0000000000..1746a5fbf5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.scss @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +::ng-deep { + .nowrap { + white-space: nowrap; + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts index 8cf9dc8d30..0062687d5d 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts @@ -16,6 +16,7 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { + AbstractControl, ControlValueAccessor, FormArray, FormBuilder, @@ -35,6 +36,7 @@ import { MatCheckboxChange } from '@angular/material/checkbox'; @Component({ selector: 'tb-alarm-schedule', templateUrl: './alarm-schedule.component.html', + styleUrls: ['./alarm-schedule.component.scss'], providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AlarmScheduleComponent), @@ -79,7 +81,7 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i))) }); this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => { - this.alarmScheduleForm.reset({type, items: this.defaultItems}, {emitEvent: false}); + this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: this.defaultTimezone}, {emitEvent: false}); this.updateValidators(type, true); this.alarmScheduleForm.updateValueAndValidity(); }); @@ -131,13 +133,7 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, this.modelValue.items .sort((a, b) => a.dayOfWeek - b.dayOfWeek) .forEach((item, index) => { - if (item.enabled) { - this.itemsSchedulerForm.at(index).get('startsOn').enable({emitEvent: false}); - this.itemsSchedulerForm.at(index).get('endsOn').enable({emitEvent: false}); - } else { - this.itemsSchedulerForm.at(index).get('startsOn').disable({emitEvent: false}); - this.itemsSchedulerForm.at(index).get('endsOn').disable({emitEvent: false}); - } + this.disabledSelectedTime(item.enabled, index); alarmDays.push({ enabled: item.enabled, startsOn: this.timestampToTime(item.startsOn), @@ -206,15 +202,15 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, .filter(day => !!day); } if (isDefined(value.startsOn) && value.startsOn !== 0) { - value.startsOn = this.timeToTimestamp(value.startsOn); + value.startsOn = this.timeToTimestampUTC(value.startsOn); } if (isDefined(value.endsOn) && value.endsOn !== 0) { - value.endsOn = this.timeToTimestamp(value.endsOn); + value.endsOn = this.timeToTimestampUTC(value.endsOn); } if (isDefined(value.items)){ value.items = this.alarmScheduleForm.getRawValue().items; value.items = value.items.map((item) => { - return { ...item, startsOn: this.timeToTimestamp(item.startsOn), endsOn: this.timeToTimestamp(item.endsOn)}; + return { ...item, startsOn: this.timeToTimestampUTC(item.startsOn), endsOn: this.timeToTimestampUTC(item.endsOn)}; }); } this.modelValue = value; @@ -222,7 +218,7 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, } } - private timeToTimestamp(date: Date | number): number { + private timeToTimestampUTC(date: Date | number): number { if (typeof date === 'number' || date === null) { return 0; } @@ -244,16 +240,39 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, changeCustomScheduler($event: MatCheckboxChange, index: number) { const value = $event.checked; - if (value) { + this.disabledSelectedTime(value, index, true); + } + + private disabledSelectedTime(enable: boolean, index: number, emitEvent = false) { + if (enable) { this.itemsSchedulerForm.at(index).get('startsOn').enable({emitEvent: false}); - this.itemsSchedulerForm.at(index).get('endsOn').enable(); + this.itemsSchedulerForm.at(index).get('endsOn').enable({emitEvent}); } else { this.itemsSchedulerForm.at(index).get('startsOn').disable({emitEvent: false}); - this.itemsSchedulerForm.at(index).get('endsOn').disable(); + this.itemsSchedulerForm.at(index).get('endsOn').disable({emitEvent}); + } + } + + private timeToMoment(date: Date | number): _moment.Moment { + if (typeof date === 'number' || date === null) { + return _moment([1970, 0, 1, 0, 0, 0, 0]); + } + return _moment([1970, 0, 1, date.getHours(), date.getMinutes(), 0, 0]); + } + + getSchedulerRangeText(control: FormGroup | AbstractControl): string { + const start = this.timeToMoment(control.get('startsOn').value); + const end = this.timeToMoment(control.get('endsOn').value); + if (start < end) { + return `${start.format('hh:mm A')} – ${end.format('hh:mm A')}`; + } else if (start.valueOf() === 0 && end.valueOf() === 0 || start.isSame(_moment([1970, 0])) && end.isSame(_moment([1970, 0]))) { + return '12:00 AM – 12:00 PM'; } + return `12:00 AM – ${end.format('hh:mm A')}` + + ` and ${start.format('hh:mm A')} – 12:00 PM`; } - private get itemsSchedulerForm(): FormArray { + get itemsSchedulerForm(): FormArray { return this.alarmScheduleForm.get('items') as FormArray; } } diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html index 7531eed659..9a79af3202 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html @@ -39,7 +39,7 @@
From 8c9328f9c5f3e2025a30e6ae59730b7aabd1656b Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 9 Oct 2020 18:06:22 +0300 Subject: [PATCH 156/463] UI: Improvement created device profile --- .../add-device-profile-dialog.component.html | 38 +++++--- .../add-device-profile-dialog.component.scss | 47 ++++++--- .../add-device-profile-dialog.component.ts | 79 +++++++++++---- .../alarm/create-alarm-rules.component.html | 4 +- .../alarm/create-alarm-rules.component.scss | 7 +- .../alarm/device-profile-alarm.component.html | 95 +++++++++---------- .../alarm/device-profile-alarm.component.scss | 14 ++- .../device-profile-tabs.component.html | 3 + .../device-profile-tabs.component.ts | 10 +- 9 files changed, 200 insertions(+), 97 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html index 7a93c0487f..5c353efffa 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html @@ -29,7 +29,7 @@
- +
{{ 'device-profile.device-profile-details' | translate }} @@ -63,7 +63,7 @@
- +
{{ 'device-profile.transport-configuration' | translate }} @@ -73,6 +73,9 @@ {{deviceTransportTypeTranslations.get(type) | translate}} + + {{deviceTransportTypeHints.get(transportConfigFormGroup.get('transportType').value) | translate}} + {{ 'device-profile.transport-type-required' | translate }} @@ -83,7 +86,7 @@
- +
{{'device-profile.alarm-rules-with-count' | translate: {count: alarmRulesFormGroup.get('alarms').value ? @@ -95,19 +98,28 @@
-
- - -
+
+
+ +
+
- + +
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.scss b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.scss index 69bf6b3e53..b8b811ca3d 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.scss @@ -13,26 +13,51 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -:host { - .mat-dialog-content { - display: flex; - flex-direction: column; - overflow: hidden; +@import "../../../../../scss/constants"; - .mat-stepper-horizontal { - display: flex; - flex-direction: column; - overflow: hidden; +:host-context(.tb-fullscreen-dialog .mat-dialog-container) { + @media #{$mat-lt-sm} { + .mat-dialog-content { + max-height: 75vh; } } } :host ::ng-deep { .mat-dialog-content { + display: flex; + flex-direction: column; + height: 100%; + padding: 24px 24px 8px !important; + .mat-stepper-horizontal { + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; + @media #{$mat-lt-sm} { + .mat-step-label { + white-space: normal; + overflow: visible; + .mat-step-text-label { + overflow: visible; + } + } + } .mat-horizontal-content-container { - overflow: auto; - min-height: 260px; + height: 350px; + max-height: 100%; + width: 100%;; + overflow-y: auto; + @media #{$mat-gt-sm} { + min-width: 800px; + } + } + .mat-horizontal-stepper-content[aria-expanded=true] { + height: 100%; + form { + height: 100%; + } } } } diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts index a88b250c7e..2ca077089a 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts @@ -36,13 +36,14 @@ import { DeviceProfile, DeviceProfileType, deviceProfileTypeTranslationMap, - DeviceTransportType, + DeviceTransportType, deviceTransportTypeHintMap, deviceTransportTypeTranslationMap } from '@shared/models/device.models'; import { DeviceProfileService } from '@core/http/device-profile.service'; import { EntityType } from '@shared/models/entity-type.models'; import { MatHorizontalStepper } from '@angular/material/stepper'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; +import { StepperSelectionEvent } from '@angular/cdk/stepper'; export interface AddDeviceProfileDialogData { deviceProfileName: string; @@ -62,12 +63,16 @@ export class AddDeviceProfileDialogComponent extends selectedIndex = 0; + showNext = true; + entityType = EntityType; deviceProfileTypes = Object.keys(DeviceProfileType); deviceProfileTypeTranslations = deviceProfileTypeTranslationMap; + deviceTransportTypeHints = deviceTransportTypeHintMap; + deviceTransportTypes = Object.keys(DeviceTransportType); deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; @@ -150,25 +155,63 @@ export class AddDeviceProfileDialogComponent extends } } - private add(): void { - const deviceProfile: DeviceProfile = { - name: this.deviceProfileDetailsFormGroup.get('name').value, - type: this.deviceProfileDetailsFormGroup.get('type').value, - transportType: this.transportConfigFormGroup.get('transportType').value, - description: this.deviceProfileDetailsFormGroup.get('description').value, - profileData: { - configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT), - transportConfiguration: this.transportConfigFormGroup.get('transportConfiguration').value, - alarms: this.alarmRulesFormGroup.get('alarms').value + add(): void { + if (this.allValid()) { + const deviceProfile: DeviceProfile = { + name: this.deviceProfileDetailsFormGroup.get('name').value, + type: this.deviceProfileDetailsFormGroup.get('type').value, + transportType: this.transportConfigFormGroup.get('transportType').value, + description: this.deviceProfileDetailsFormGroup.get('description').value, + profileData: { + configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT), + transportConfiguration: this.transportConfigFormGroup.get('transportConfiguration').value, + alarms: this.alarmRulesFormGroup.get('alarms').value + } + }; + if (this.deviceProfileDetailsFormGroup.get('defaultRuleChainId').value) { + deviceProfile.defaultRuleChainId = new RuleChainId(this.deviceProfileDetailsFormGroup.get('defaultRuleChainId').value); } - }; - if (this.deviceProfileDetailsFormGroup.get('defaultRuleChainId').value) { - deviceProfile.defaultRuleChainId = new RuleChainId(this.deviceProfileDetailsFormGroup.get('defaultRuleChainId').value); + this.deviceProfileService.saveDeviceProfile(deviceProfile).subscribe( + (savedDeviceProfile) => { + this.dialogRef.close(savedDeviceProfile); + } + ); + } + } + + getFormLabel(index: number): string { + switch (index) { + case 0: + return 'device-profile.device-profile-details'; + case 1: + return 'device-profile.transport-configuration'; + case 2: + return 'device-profile.alarm-rules'; + } + } + + changeStep($event: StepperSelectionEvent): void { + this.selectedIndex = $event.selectedIndex; + if (this.selectedIndex === this.maxStepperIndex) { + this.showNext = false; + } else { + this.showNext = true; } - this.deviceProfileService.saveDeviceProfile(deviceProfile).subscribe( - (savedDeviceProfile) => { - this.dialogRef.close(savedDeviceProfile); + } + + private get maxStepperIndex(): number { + return this.addDeviceProfileStepper?._steps?.length - 1; + } + + allValid(): boolean { + return !this.addDeviceProfileStepper.steps.find((item, index) => { + if (item.stepControl.invalid) { + item.interacted = true; + this.addDeviceProfileStepper.selectedIndex = index; + return true; + } else { + return false; } - ); + }); } } diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html index 6886e19152..e1883256bb 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html @@ -48,7 +48,7 @@
- device-profile.no-create-alarm-rules
@@ -57,7 +57,7 @@ (click)="addCreateAlarmRule()" matTooltip="{{ 'device-profile.add-create-alarm-rule' | translate }}" matTooltipPosition="above"> - add_circle_outline + add_circle_outline {{ 'device-profile.add-create-alarm-rule' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.scss index b823629076..eedaac2ca1 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.scss @@ -16,7 +16,7 @@ :host { .create-alarm-rule { - border: 1px groove rgba(0, 0, 0, .25); + border: 2px groove rgba(0, 0, 0, .45); border-radius: 4px; padding: 8px; } @@ -28,4 +28,9 @@ width: 160px; } } + .button-icon{ + font-size: 20px; + width: 20px; + height: 20px; + } } diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html index d4a72da751..7d2ad8b75d 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html @@ -18,24 +18,11 @@
- +
{{ alarmFormGroup.get('alarmType').value }}
- - {{'device-profile.alarm-type' | translate}} - - - {{ 'device-profile.alarm-type-required' | translate }} - - -
+
+ + + {{'device-profile.alarm-type' | translate}} + + + {{ 'device-profile.alarm-type-required' | translate }} + + + +
+ + + +
+
device-profile.advanced-settings
+
+
+
+ + {{ 'device-profile.propagate-alarm' | translate }} + +
+ + device-profile.alarm-rule-relation-types-list + + + {{key}} + close + + + + + +
+
device-profile.create-alarm-rules
@@ -68,7 +99,7 @@
- device-profile.no-clear-alarm-rule
- add_circle_outline + add_circle_outline {{ 'device-profile.add-clear-alarm-rule' | translate }}
- - - -
-
device-profile.advanced-settings
-
-
-
- - {{ 'device-profile.propagate-alarm' | translate }} - -
- - device-profile.alarm-rule-relation-types-list - - - {{key}} - close - - - - - -
-
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.scss index 9180f507da..ab28ba6829 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.scss @@ -17,7 +17,7 @@ :host { display: block; .clear-alarm-rule { - border: 1px groove rgba(0, 0, 0, .25); + border: 2px groove rgba(0, 0, 0, .45); border-radius: 4px; padding: 8px; } @@ -28,13 +28,16 @@ .mat-expansion-panel-header { padding: 0 24px 0 8px; &.mat-expanded { - height: 80px; + height: 48px; } } } &.advanced-settings { border: none; padding: 0; + .mat-expansion-panel-header { + padding: 0 8px; + } } } } @@ -43,7 +46,7 @@ .mat-expansion-panel { &.device-profile-alarm { .mat-expansion-panel-body { - padding: 0 8px; + padding: 0 8px 8px; } } &.advanced-settings { @@ -51,5 +54,10 @@ padding: 0; } } + .button-icon{ + font-size: 20px; + width: 20px; + height: 20px; + } } } diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html index 9a79af3202..437fc21c86 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html @@ -25,6 +25,9 @@ {{deviceTransportTypeTranslations.get(type) | translate}} + + {{deviceTransportTypeHints.get(detailsForm.get('transportType').value) | translate}} + {{ 'device-profile.transport-type-required' | translate }} diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.ts b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.ts index 6410e06950..cba34b4987 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.ts @@ -18,7 +18,12 @@ import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityTabsComponent } from '../../components/entity/entity-tabs.component'; -import { DeviceProfile, DeviceTransportType, deviceTransportTypeTranslationMap } from '@shared/models/device.models'; +import { + DeviceProfile, + DeviceTransportType, + deviceTransportTypeHintMap, + deviceTransportTypeTranslationMap +} from '@shared/models/device.models'; @Component({ selector: 'tb-device-profile-tabs', @@ -28,8 +33,11 @@ import { DeviceProfile, DeviceTransportType, deviceTransportTypeTranslationMap } export class DeviceProfileTabsComponent extends EntityTabsComponent { deviceTransportTypes = Object.keys(DeviceTransportType); + deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; + deviceTransportTypeHints = deviceTransportTypeHintMap; + constructor(protected store: Store) { super(store); } From d838f2a86782256665e2b61dfbd1e5dc300422db Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 9 Oct 2020 16:32:55 +0300 Subject: [PATCH 157/463] fix NPE in DefaultTbDeviceProfileCache --- .../server/service/profile/DefaultTbDeviceProfileCache.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java b/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java index b0b47fe886..b6af0d7496 100644 --- a/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java +++ b/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java @@ -74,6 +74,8 @@ public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache { if (device != null) { profileId = device.getDeviceProfileId(); devicesMap.put(deviceId, profileId); + } else { + return null; } } return get(tenantId, profileId); From 1423074485e2c94717f01dcbe98608da79c72a13 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 7 Oct 2020 19:15:48 +0300 Subject: [PATCH 158/463] fixed sending inactivity event after device creation. --- .../server/service/state/DefaultDeviceStateService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 9878bbf1a2..c6f771216b 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.service.state; +import com.datastax.driver.core.utils.UUIDs; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Function; import com.google.common.util.concurrent.FutureCallback; @@ -392,7 +393,7 @@ public class DefaultDeviceStateService implements DeviceStateService { if (stateData != null) { DeviceState state = stateData.getState(); state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout()); - if (!state.isActive() && (state.getLastInactivityAlarmTime() == 0L || state.getLastInactivityAlarmTime() < state.getLastActivityTime())) { + if (!state.isActive() && (state.getLastInactivityAlarmTime() == 0L || state.getLastInactivityAlarmTime() < state.getLastActivityTime()) && UUIDs.unixTimestamp(deviceId.getId()) + state.getInactivityTimeout() < ts) { state.setLastInactivityAlarmTime(ts); pushRuleEngineMessage(stateData, INACTIVITY_EVENT); save(deviceId, INACTIVITY_ALARM_TIME, ts); From 62748eef60b163ab439a6b1f81675e4769152790 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 8 Oct 2020 13:15:49 +0300 Subject: [PATCH 159/463] added deviceCreationTime to the DeviceStateData --- .../server/service/state/DefaultDeviceStateService.java | 3 ++- .../org/thingsboard/server/service/state/DeviceStateData.java | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index c6f771216b..9d5df549da 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -393,7 +393,7 @@ public class DefaultDeviceStateService implements DeviceStateService { if (stateData != null) { DeviceState state = stateData.getState(); state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout()); - if (!state.isActive() && (state.getLastInactivityAlarmTime() == 0L || state.getLastInactivityAlarmTime() < state.getLastActivityTime()) && UUIDs.unixTimestamp(deviceId.getId()) + state.getInactivityTimeout() < ts) { + if (!state.isActive() && (state.getLastInactivityAlarmTime() == 0L || state.getLastInactivityAlarmTime() < state.getLastActivityTime()) && stateData.getDeviceCreationTime() + state.getInactivityTimeout() < ts) { state.setLastInactivityAlarmTime(ts); pushRuleEngineMessage(stateData, INACTIVITY_EVENT); save(deviceId, INACTIVITY_ALARM_TIME, ts); @@ -480,6 +480,7 @@ public class DefaultDeviceStateService implements DeviceStateService { return DeviceStateData.builder() .tenantId(device.getTenantId()) .deviceId(device.getId()) + .deviceCreationTime(device.getCreatedTime()) .metaData(md) .state(deviceState).build(); } catch (Exception e) { diff --git a/application/src/main/java/org/thingsboard/server/service/state/DeviceStateData.java b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateData.java index 1973570f9e..3ace93b89a 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DeviceStateData.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateData.java @@ -30,8 +30,8 @@ class DeviceStateData { private final TenantId tenantId; private final DeviceId deviceId; - + private final long deviceCreationTime; private TbMsgMetaData metaData; private final DeviceState state; - + } From bec23b419064bf8ca382fc3be081c325f3db3030 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 8 Oct 2020 13:22:01 +0300 Subject: [PATCH 160/463] refactored --- .../server/service/state/DefaultDeviceStateService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 9d5df549da..dcc8d590b7 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.state; -import com.datastax.driver.core.utils.UUIDs; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Function; import com.google.common.util.concurrent.FutureCallback; From b3f6938cf0d95c653663cf8470b38a58274badfb Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 7 Oct 2020 12:08:05 +0300 Subject: [PATCH 161/463] fix duplicates --- .../thingsboard/server/actors/ruleChain/DefaultTbContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 4fef96c000..622d60d7bf 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -114,7 +114,7 @@ class DefaultTbContext implements TbContext { @Override public void enqueue(TbMsg tbMsg, Runnable onSuccess, Consumer onFailure) { - TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator()); + TopicPartitionInfo tpi = resolvePartition(tbMsg); enqueue(tpi, tbMsg, onFailure, onSuccess); } From 560b950a8faa522268a5a9e04ddac501dc626d47 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 7 Oct 2020 12:31:13 +0300 Subject: [PATCH 162/463] added null check for queueName --- .../server/actors/ruleChain/DefaultTbContext.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 622d60d7bf..dc4bf5eb1e 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.netty.channel.EventLoopGroup; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.util.StringUtils; import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.RuleEngineRpcService; @@ -43,6 +44,7 @@ import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.common.msg.queue.ServiceQueue; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.alarm.AlarmService; @@ -178,6 +180,9 @@ class DefaultTbContext implements TbContext { } private TopicPartitionInfo resolvePartition(TbMsg tbMsg, String queueName) { + if (StringUtils.isEmpty(queueName)) { + queueName = ServiceQueue.MAIN; + } return mainCtx.resolve(ServiceType.TB_RULE_ENGINE, queueName, getTenantId(), tbMsg.getOriginator()); } From b63be05ffad15be685bf5c5f26c82d8184bb3781 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 8 Oct 2020 13:19:49 +0300 Subject: [PATCH 163/463] revert the enqueue(root rule chain) method changes --- .../thingsboard/server/actors/ruleChain/DefaultTbContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index dc4bf5eb1e..e89daf4516 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -116,7 +116,7 @@ class DefaultTbContext implements TbContext { @Override public void enqueue(TbMsg tbMsg, Runnable onSuccess, Consumer onFailure) { - TopicPartitionInfo tpi = resolvePartition(tbMsg); + TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator()); enqueue(tpi, tbMsg, onFailure, onSuccess); } From fc92a8d5366cfe06668fd6cfe4ea45e785f999f7 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 7 Oct 2020 19:15:48 +0300 Subject: [PATCH 164/463] fixed sending inactivity event after device creation. --- .../server/service/state/DefaultDeviceStateService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 0772dd907b..9c54e19eed 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.service.state; +import com.datastax.driver.core.utils.UUIDs; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Function; import com.google.common.util.concurrent.FutureCallback; @@ -392,7 +393,7 @@ public class DefaultDeviceStateService implements DeviceStateService { if (stateData != null) { DeviceState state = stateData.getState(); state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout()); - if (!state.isActive() && (state.getLastInactivityAlarmTime() == 0L || state.getLastInactivityAlarmTime() < state.getLastActivityTime())) { + if (!state.isActive() && (state.getLastInactivityAlarmTime() == 0L || state.getLastInactivityAlarmTime() < state.getLastActivityTime()) && UUIDs.unixTimestamp(deviceId.getId()) + state.getInactivityTimeout() < ts) { state.setLastInactivityAlarmTime(ts); pushRuleEngineMessage(stateData, INACTIVITY_EVENT); save(deviceId, INACTIVITY_ALARM_TIME, ts); From a046459d62d66f6988c01f1640921acaf4d2d65b Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 8 Oct 2020 13:15:49 +0300 Subject: [PATCH 165/463] added deviceCreationTime to the DeviceStateData --- .../server/service/state/DefaultDeviceStateService.java | 3 ++- .../org/thingsboard/server/service/state/DeviceStateData.java | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 9c54e19eed..6b52f3573b 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -393,7 +393,7 @@ public class DefaultDeviceStateService implements DeviceStateService { if (stateData != null) { DeviceState state = stateData.getState(); state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout()); - if (!state.isActive() && (state.getLastInactivityAlarmTime() == 0L || state.getLastInactivityAlarmTime() < state.getLastActivityTime()) && UUIDs.unixTimestamp(deviceId.getId()) + state.getInactivityTimeout() < ts) { + if (!state.isActive() && (state.getLastInactivityAlarmTime() == 0L || state.getLastInactivityAlarmTime() < state.getLastActivityTime()) && stateData.getDeviceCreationTime() + state.getInactivityTimeout() < ts) { state.setLastInactivityAlarmTime(ts); pushRuleEngineMessage(stateData, INACTIVITY_EVENT); save(deviceId, INACTIVITY_ALARM_TIME, ts); @@ -480,6 +480,7 @@ public class DefaultDeviceStateService implements DeviceStateService { return DeviceStateData.builder() .tenantId(device.getTenantId()) .deviceId(device.getId()) + .deviceCreationTime(device.getCreatedTime()) .metaData(md) .state(deviceState).build(); } catch (Exception e) { diff --git a/application/src/main/java/org/thingsboard/server/service/state/DeviceStateData.java b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateData.java index 1973570f9e..3ace93b89a 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DeviceStateData.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateData.java @@ -30,8 +30,8 @@ class DeviceStateData { private final TenantId tenantId; private final DeviceId deviceId; - + private final long deviceCreationTime; private TbMsgMetaData metaData; private final DeviceState state; - + } From 8e89ea9729888d7870685b52e84ef8b4484ade07 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 8 Oct 2020 13:22:01 +0300 Subject: [PATCH 166/463] refactored --- .../server/service/state/DefaultDeviceStateService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 6b52f3573b..5ef5adc386 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.state; -import com.datastax.driver.core.utils.UUIDs; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Function; import com.google.common.util.concurrent.FutureCallback; From 0240476b4e027e2380852590c1f87007d15bc84f Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 7 Oct 2020 12:08:05 +0300 Subject: [PATCH 167/463] fix duplicates --- .../thingsboard/server/actors/ruleChain/DefaultTbContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index a9ddaea2e4..043dc6bff4 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -119,7 +119,7 @@ class DefaultTbContext implements TbContext { @Override public void enqueue(TbMsg tbMsg, Runnable onSuccess, Consumer onFailure) { - TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator()); + TopicPartitionInfo tpi = resolvePartition(tbMsg); enqueue(tpi, tbMsg, onFailure, onSuccess); } From a045be771baef33e3791563006546aaf2b9d5710 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 7 Oct 2020 12:31:13 +0300 Subject: [PATCH 168/463] added null check for queueName --- .../server/actors/ruleChain/DefaultTbContext.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 043dc6bff4..28a5bde8f9 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.netty.channel.EventLoopGroup; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.util.StringUtils; import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.RuleEngineAlarmService; @@ -47,6 +48,7 @@ import org.thingsboard.server.common.data.rule.RuleNodeState; import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.common.msg.queue.ServiceQueue; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.asset.AssetService; @@ -183,6 +185,9 @@ class DefaultTbContext implements TbContext { } private TopicPartitionInfo resolvePartition(TbMsg tbMsg, String queueName) { + if (StringUtils.isEmpty(queueName)) { + queueName = ServiceQueue.MAIN; + } return mainCtx.resolve(ServiceType.TB_RULE_ENGINE, queueName, getTenantId(), tbMsg.getOriginator()); } From d5bf25e2f346892bdbb4beffbcfb37ffc9c7425c Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 8 Oct 2020 13:19:49 +0300 Subject: [PATCH 169/463] revert the enqueue(root rule chain) method changes --- .../thingsboard/server/actors/ruleChain/DefaultTbContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 28a5bde8f9..402caed226 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -121,7 +121,7 @@ class DefaultTbContext implements TbContext { @Override public void enqueue(TbMsg tbMsg, Runnable onSuccess, Consumer onFailure) { - TopicPartitionInfo tpi = resolvePartition(tbMsg); + TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator()); enqueue(tpi, tbMsg, onFailure, onSuccess); } From d13116db194981f1db7e446cee9d0b7fd4580d6e Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 9 Oct 2020 19:23:02 +0300 Subject: [PATCH 170/463] Update device transport type hints. Update device profile autocomplete - select first profile option --- .../device-profile-autocomplete.component.ts | 27 ++++++++++++++++++- .../device-wizard-dialog.component.html | 1 + .../assets/locale/locale.constant-en_US.json | 4 +-- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts index 1a155391de..d7ed08ae9c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts @@ -64,6 +64,9 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, @Input() selectDefaultProfile = false; + @Input() + selectFirstProfile = false; + @Input() displayAllOnEmpty = false; @@ -183,12 +186,34 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, if (this.selectDefaultProfile && !this.modelValue) { this.deviceProfileService.getDefaultDeviceProfileInfo().subscribe( (profile) => { - if (profile && !this.transportType || (profile.transportType === this.transportType)) { + if (profile && (!this.transportType || (profile.transportType === this.transportType))) { this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(profile, {emitEvent: false}); this.updateView(profile); + } else { + this.selectFirstDeviceProfileIfNeeded(); } } ); + } else { + this.selectFirstDeviceProfileIfNeeded(); + } + } + + selectFirstDeviceProfileIfNeeded(): void { + if (this.selectFirstProfile && !this.modelValue) { + const pageLink = new PageLink(1, 0, null, { + property: 'createdTime', + direction: Direction.DESC + }); + this.deviceProfileService.getDeviceProfileInfos(pageLink, this.transportType, {ignoreLoading: true}).subscribe( + (pageData => { + const data = pageData.data; + if (data.length) { + this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(data[0], {emitEvent: false}); + this.updateView(data[0]); + } + }) + ); } } diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index 6cf1e687e3..1e59f0c9e1 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -79,6 +79,7 @@ (deviceProfileChanged)="$event?.transportType ? deviceWizardFormGroup.get('transportType').patchValue($event?.transportType) : {}" [addNewProfile]="false" [selectDefaultProfile]="true" + [selectFirstProfile]="true" [editProfileEnabled]="false"> diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 08aa57b61a..1cefe69874 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -794,9 +794,9 @@ "transport-type": "Transport type", "transport-type-required": "Transport type is required.", "transport-type-default": "Default", - "transport-type-default-hint": "Default transport type", + "transport-type-default-hint": "Refers to already existing transport configuration or basic MQTT, HTTP, CoAP settings", "transport-type-mqtt": "MQTT", - "transport-type-mqtt-hint": "MQTT transport type", + "transport-type-mqtt-hint": "Advanced MQTT topic filters and Device profile settings", "transport-type-lwm2m": "LWM2M", "transport-type-lwm2m-hint": "LWM2M transport type", "description": "Description", From 06c3fb8eabf397e808b81f2d5e0dc478513a564f Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 12 Oct 2020 09:45:34 +0300 Subject: [PATCH 171/463] UI: Refactoring device profile --- ui-ngx/src/app/core/utils.ts | 16 ++++++++++++++++ .../home/components/entity/entity.component.ts | 17 ++--------------- .../add-device-profile-dialog.component.ts | 3 ++- .../profile/device-profile-data.component.html | 0 .../profile/device-profile.component.ts | 2 +- ui-ngx/src/app/shared/models/device.models.ts | 2 +- 6 files changed, 22 insertions(+), 18 deletions(-) delete mode 100644 ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index 9834405eda..c0a01f3bec 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -387,3 +387,19 @@ export function sortObjectKeys(obj: T): T { return acc; }, {} as T); } + +export function deepTrim(obj: T): T { + if (isNumber(obj) || isUndefined(obj) || isString(obj) || obj === null) { + return obj; + } + return Object.keys(obj).reduce((acc, curr) => { + if (isString(obj[curr])) { + acc[curr] = obj[curr].trim(); + } else if (isObject(obj[curr])) { + acc[curr] = deepTrim(obj[curr]); + } else { + acc[curr] = obj[curr]; + } + return acc; + }, (Array.isArray(obj) ? [] : {}) as T); +} diff --git a/ui-ngx/src/app/modules/home/components/entity/entity.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity.component.ts index 6d3c7e1453..6a76f1e062 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity.component.ts @@ -23,7 +23,7 @@ import { AppState } from '@core/core.state'; import { EntityAction } from '@home/models/entity/entity-component.models'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; import { PageLink } from '@shared/models/page/page-link'; -import { isObject, isString } from '@core/utils'; +import { deepTrim } from '@core/utils'; // @dynamic @Directive() @@ -115,20 +115,7 @@ export abstract class EntityComponent, } prepareFormValue(formValue: any): any { - return this.deepTrim(formValue); - } - - private deepTrim(obj: object): object { - return Object.keys(obj).reduce((acc, curr) => { - if (isString(obj[curr])) { - acc[curr] = obj[curr].trim(); - } else if (isObject(obj[curr])) { - acc[curr] = this.deepTrim(obj[curr]); - } else { - acc[curr] = obj[curr]; - } - return acc; - }, Array.isArray(obj) ? [] : {}); + return deepTrim(formValue); } protected setEntitiesTableConfig(entitiesTableConfig: C) { diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts index 2ca077089a..031e7b2a3b 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts @@ -44,6 +44,7 @@ import { EntityType } from '@shared/models/entity-type.models'; import { MatHorizontalStepper } from '@angular/material/stepper'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; import { StepperSelectionEvent } from '@angular/cdk/stepper'; +import { deepTrim } from '@core/utils'; export interface AddDeviceProfileDialogData { deviceProfileName: string; @@ -171,7 +172,7 @@ export class AddDeviceProfileDialogComponent extends if (this.deviceProfileDetailsFormGroup.get('defaultRuleChainId').value) { deviceProfile.defaultRuleChainId = new RuleChainId(this.deviceProfileDetailsFormGroup.get('defaultRuleChainId').value); } - this.deviceProfileService.saveDeviceProfile(deviceProfile).subscribe( + this.deviceProfileService.saveDeviceProfile(deepTrim(deviceProfile)).subscribe( (savedDeviceProfile) => { this.dialogRef.close(savedDeviceProfile); } diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts index d7bd195171..34c8c7d74c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts @@ -142,7 +142,7 @@ export class DeviceProfileComponent extends EntityComponent { if (formValue.defaultRuleChainId) { formValue.defaultRuleChainId = new RuleChainId(formValue.defaultRuleChainId); } - return formValue; + return super.prepareFormValue(formValue); } onDeviceProfileIdCopied(event) { diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 6adfdefc02..f584eebccc 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -76,7 +76,7 @@ export const deviceTransportTypeHintMap = new Map( [ [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default-hint'], [DeviceTransportType.MQTT, 'device-profile.transport-type-mqtt-hint'], - [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m-hint'] + // [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m-hint'] ] ); From 20c90c8a6f3f9c1f0cc7ce9cca791fc228930160 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 12 Oct 2020 10:12:42 +0300 Subject: [PATCH 172/463] Improve device/device profile components, remove unnecessary transport configurations --- ...ile-transport-configuration.component.html | 4 ++-- ...ofile-transport-configuration.component.ts | 2 +- .../wizard/device-wizard-dialog.component.ts | 6 ++--- ...efault-device-configuration.component.html | 4 ++-- ...ice-transport-configuration.component.html | 4 ++-- ...ice-transport-configuration.component.html | 4 ++-- ...ice-transport-configuration.component.html | 4 ++-- ...evice-transport-configuration.component.ts | 2 +- ...ice-transport-configuration.component.html | 4 ++-- ui-ngx/src/app/shared/models/device.models.ts | 22 +++++++++---------- 10 files changed, 28 insertions(+), 28 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.html index 001502cd83..ea6427addd 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.html @@ -29,11 +29,11 @@ formControlName="configuration"> - +
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m-device-profile-transport-configuration.component.ts index ed81a143fd..7a7a1d285f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m-device-profile-transport-configuration.component.ts @@ -89,7 +89,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro let configuration: DeviceProfileTransportConfiguration = null; if (this.lwm2mDeviceProfileTransportConfigurationFormGroup.valid) { configuration = this.lwm2mDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration; - configuration.type = DeviceTransportType.LWM2M; + // configuration.type = DeviceTransportType.LWM2M; } this.propagateChange(configuration); } diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts index 9def61c4b6..9e861c1359 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts @@ -26,7 +26,7 @@ import { createDeviceProfileTransportConfiguration, DeviceProfile, DeviceProfileType, - DeviceTransportType, deviceTransportTypeHintMap, + DeviceTransportType, deviceTransportTypeConfigurationInfoMap, deviceTransportTypeHintMap, deviceTransportTypeTranslationMap } from '@shared/models/device.models'; import { MatHorizontalStepper } from '@angular/material/stepper'; @@ -123,7 +123,7 @@ export class DeviceWizardDialogComponent extends this.deviceWizardFormGroup.updateValueAndValidity(); this.createProfile = true; this.createTransportConfiguration = this.deviceWizardFormGroup.get('transportType').value && - DeviceTransportType.DEFAULT !== this.deviceWizardFormGroup.get('transportType').value; + deviceTransportTypeConfigurationInfoMap.get(this.deviceWizardFormGroup.get('transportType').value).hasProfileConfiguration; } } )); @@ -228,7 +228,7 @@ export class DeviceWizardDialogComponent extends this.transportConfigFormGroup.patchValue( {transportConfiguration: createDeviceProfileTransportConfiguration(deviceTransportType)}); this.createTransportConfiguration = this.createProfile && deviceTransportType && - DeviceTransportType.DEFAULT !== deviceTransportType; + deviceTransportTypeConfigurationInfoMap.get(deviceTransportType).hasProfileConfiguration; } add(): void { diff --git a/ui-ngx/src/app/modules/home/pages/device/data/default-device-configuration.component.html b/ui-ngx/src/app/modules/home/pages/device/data/default-device-configuration.component.html index a1d4919b5c..001cc16801 100644 --- a/ui-ngx/src/app/modules/home/pages/device/data/default-device-configuration.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/data/default-device-configuration.component.html @@ -16,9 +16,9 @@ --> - - + diff --git a/ui-ngx/src/app/modules/home/pages/device/data/default-device-transport-configuration.component.html b/ui-ngx/src/app/modules/home/pages/device/data/default-device-transport-configuration.component.html index cd2d53aab0..68189fbb84 100644 --- a/ui-ngx/src/app/modules/home/pages/device/data/default-device-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/data/default-device-transport-configuration.component.html @@ -16,9 +16,9 @@ -->
- - +
diff --git a/ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.html b/ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.html index f109335edc..6762202f06 100644 --- a/ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.html @@ -29,11 +29,11 @@ formControlName="configuration"> - +
diff --git a/ui-ngx/src/app/modules/home/pages/device/data/lwm2m-device-transport-configuration.component.html b/ui-ngx/src/app/modules/home/pages/device/data/lwm2m-device-transport-configuration.component.html index 3fdccba628..4a90cf6ecf 100644 --- a/ui-ngx/src/app/modules/home/pages/device/data/lwm2m-device-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/data/lwm2m-device-transport-configuration.component.html @@ -16,9 +16,9 @@ -->
- - +
diff --git a/ui-ngx/src/app/modules/home/pages/device/data/lwm2m-device-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/pages/device/data/lwm2m-device-transport-configuration.component.ts index 05e1448bc3..f20f481824 100644 --- a/ui-ngx/src/app/modules/home/pages/device/data/lwm2m-device-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/data/lwm2m-device-transport-configuration.component.ts @@ -89,7 +89,7 @@ export class Lwm2mDeviceTransportConfigurationComponent implements ControlValueA let configuration: DeviceTransportConfiguration = null; if (this.lwm2mDeviceTransportConfigurationFormGroup.valid) { configuration = this.lwm2mDeviceTransportConfigurationFormGroup.getRawValue().configuration; - configuration.type = DeviceTransportType.LWM2M; + // configuration.type = DeviceTransportType.LWM2M; } this.propagateChange(configuration); } diff --git a/ui-ngx/src/app/modules/home/pages/device/data/mqtt-device-transport-configuration.component.html b/ui-ngx/src/app/modules/home/pages/device/data/mqtt-device-transport-configuration.component.html index e21bb3818a..9e7390e8d7 100644 --- a/ui-ngx/src/app/modules/home/pages/device/data/mqtt-device-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/data/mqtt-device-transport-configuration.component.html @@ -16,9 +16,9 @@ -->
- - +
diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 0bbc93eb12..4f27fc1db8 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -33,7 +33,7 @@ export enum DeviceProfileType { export enum DeviceTransportType { DEFAULT = 'DEFAULT', MQTT = 'MQTT', - LWM2M = 'LWM2M' +// LWM2M = 'LWM2M' } export enum MqttTransportPayloadType { @@ -68,7 +68,7 @@ export const deviceTransportTypeTranslationMap = new Map( [ [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default-hint'], [DeviceTransportType.MQTT, 'device-profile.transport-type-mqtt-hint'], - [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m-hint'] + // [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m-hint'] ] ); @@ -101,16 +101,16 @@ export const deviceTransportTypeConfigurationInfoMap = new Map Date: Mon, 12 Oct 2020 10:58:28 +0300 Subject: [PATCH 173/463] Minor fixes --- .../server/common/data/SearchTextBasedWithAdditionalInfo.java | 1 - .../src/app/modules/home/pages/admin/admin-routing.module.ts | 2 +- ui-ngx/src/app/shared/models/entity-type.models.ts | 3 +-- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBasedWithAdditionalInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBasedWithAdditionalInfo.java index c87f1ca109..4efd673d7b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBasedWithAdditionalInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBasedWithAdditionalInfo.java @@ -59,7 +59,6 @@ public abstract class SearchTextBasedWithAdditionalInfo ext } public void setAdditionalInfo(JsonNode addInfo) { - // TODO why set additionalInfoBytes to [110,117,108,108] if JsonNode is null setJson(addInfo, json -> this.additionalInfo = json, bytes -> this.additionalInfoBytes = bytes); } diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts index 40a8d3addd..af7dc98e19 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts @@ -28,7 +28,7 @@ const routes: Routes = [ { path: 'settings', data: { - auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], + auth: [Authority.SYS_ADMIN], breadcrumb: { label: 'admin.system-settings', icon: 'settings' diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 647e05bc9d..c6a94ad8b5 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -47,8 +47,7 @@ export enum EntityType { RULE_NODE = 'RULE_NODE', ENTITY_VIEW = 'ENTITY_VIEW', WIDGETS_BUNDLE = 'WIDGETS_BUNDLE', - WIDGET_TYPE = 'WIDGET_TYPE', - OAUTH2_CLIENT_REGISTRATION = 'OAUTH2_CLIENT_REGISTRATION' + WIDGET_TYPE = 'WIDGET_TYPE' } export enum AliasEntityType { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index e26f307c7a..8f89fa8f39 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -100,7 +100,7 @@ "proxy-user": "Proxy user", "proxy-password": "Proxy password", "send-test-mail": "Send test mail", - "security-settings": "Security Settings", + "security-settings": "Security settings", "password-policy": "Password policy", "minimum-password-length": "Minimum password length", "minimum-password-length-required": "Minimum password length is required", From ee6bc5ec4b8c341afff28d2a9104ec3960878321 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Fri, 9 Oct 2020 14:12:52 +0300 Subject: [PATCH 174/463] Fixed issue for last level query in case entities have more relations in the hierarchy below requested level --- .../dao/sql/query/DefaultEntityQueryRepository.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java index 232a534e88..2d5c9cbda9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java @@ -472,10 +472,13 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { if (entityFilter.isFetchLastLevelOnly()) { String fromOrTo = (entityFilter.getDirection().equals(EntitySearchDirection.FROM) ? "from" : "to"); StringBuilder notExistsPart = new StringBuilder(); - notExistsPart.append(" NOT EXISTS (SELECT 1 from relation nr where ") + notExistsPart.append(" NOT EXISTS (SELECT 1 from relation nr ") + .append(whereFilter.replaceAll("re\\.", "nr\\.")) + .append(" and ") .append("nr.").append(fromOrTo).append("_id").append(" = re.").append(toOrFrom).append("_id") .append(" and ") .append("nr.").append(fromOrTo).append("_type").append(" = re.").append(toOrFrom).append("_type"); + if (!StringUtils.isEmpty(entityFilter.getRelationType())) { notExistsPart.append(" and nr.relation_type = :where_relation_type"); } @@ -556,7 +559,9 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { .append(" and ") .append("nr.").append(fromOrTo).append("_id").append(" = re.").append(toOrFrom).append("_id") .append(" and ") - .append("nr.").append(fromOrTo).append("_type").append(" = re.").append(toOrFrom).append("_type"); + .append("nr.").append(fromOrTo).append("_type").append(" = re.").append(toOrFrom).append("_type") + .append(" and ") + .append(whereFilter.toString().replaceAll("re\\.", "nr\\.")); notExistsPart.append(")"); whereFilter.append(" and ( re.lvl = ").append(entityFilter.getMaxLevel()).append(" OR ").append(notExistsPart.toString()).append(")"); From ba96409e5d2efd2bfa7b9750a2a24ae46854b113 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Fri, 9 Oct 2020 18:03:09 +0300 Subject: [PATCH 175/463] Code review fixes --- .../server/dao/sql/query/DefaultEntityQueryRepository.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java index 2d5c9cbda9..75f7658a5a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java @@ -479,9 +479,6 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { .append(" and ") .append("nr.").append(fromOrTo).append("_type").append(" = re.").append(toOrFrom).append("_type"); - if (!StringUtils.isEmpty(entityFilter.getRelationType())) { - notExistsPart.append(" and nr.relation_type = :where_relation_type"); - } notExistsPart.append(")"); whereFilter += " and ( re.lvl = " + entityFilter.getMaxLevel() + " OR " + notExistsPart.toString() + ")"; } @@ -554,9 +551,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { StringBuilder notExistsPart = new StringBuilder(); notExistsPart.append(" NOT EXISTS (SELECT 1 from relation nr WHERE "); - notExistsPart.append(whereFilter.toString()); notExistsPart - .append(" and ") .append("nr.").append(fromOrTo).append("_id").append(" = re.").append(toOrFrom).append("_id") .append(" and ") .append("nr.").append(fromOrTo).append("_type").append(" = re.").append(toOrFrom).append("_type") From bebddfd1ae9972eedff35de66d85e3d86a0336a5 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 12 Oct 2020 12:11:53 +0300 Subject: [PATCH 176/463] Improved logic of alarm rules --- .../rule/engine/profile/AlarmRuleState.java | 43 ++++++-- ...ProfileAlarmState.java => AlarmState.java} | 43 ++++++-- .../profile/AlarmStateUpdateResult.java | 2 +- ...iceDataSnapshot.java => DataSnapshot.java} | 34 ++++-- .../rule/engine/profile/DeviceState.java | 104 +++++++++++------- .../rule/engine/profile/EntityKeyValue.java | 2 + ...iceProfileState.java => ProfileState.java} | 52 ++++++++- ...ntityKeyState.java => SnapshotUpdate.java} | 19 +++- .../engine/profile/TbDeviceProfileNode.java | 25 +++-- 9 files changed, 242 insertions(+), 82 deletions(-) rename rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/{DeviceProfileAlarmState.java => AlarmState.java} (81%) rename rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/{DeviceDataSnapshot.java => DataSnapshot.java} (70%) rename rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/{DeviceProfileState.java => ProfileState.java} (51%) rename rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/{EntityKeyState.java => SnapshotUpdate.java} (58%) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java index e307505fcd..a4618bab63 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java @@ -29,6 +29,8 @@ import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpe import org.thingsboard.server.common.data.device.profile.SpecificTimeSchedule; import org.thingsboard.server.common.data.query.BooleanFilterPredicate; import org.thingsboard.server.common.data.query.ComplexFilterPredicate; +import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.data.query.KeyFilterPredicate; import org.thingsboard.server.common.data.query.NumericFilterPredicate; @@ -38,22 +40,24 @@ import org.thingsboard.server.common.msg.tools.SchedulerUtils; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.util.Calendar; +import java.util.Set; @Data -public class AlarmRuleState { +class AlarmRuleState { private final AlarmSeverity severity; private final AlarmRule alarmRule; private final AlarmConditionSpec spec; private final long requiredDurationInMs; private final long requiredRepeats; + private final Set entityKeys; private PersistedAlarmRuleState state; private boolean updateFlag; - public AlarmRuleState(AlarmSeverity severity, AlarmRule alarmRule, PersistedAlarmRuleState state) { + AlarmRuleState(AlarmSeverity severity, AlarmRule alarmRule, Set entityKeys, PersistedAlarmRuleState state) { this.severity = severity; this.alarmRule = alarmRule; + this.entityKeys = entityKeys; if (state != null) { this.state = state; } else { @@ -76,6 +80,30 @@ public class AlarmRuleState { this.requiredRepeats = requiredRepeats; } + public boolean validateTsUpdate(Set changedKeys) { + for (EntityKey key : changedKeys) { + if (entityKeys.contains(key)) { + return true; + } + } + return false; + } + + public boolean validateAttrUpdate(Set changedKeys) { + //If the attribute was updated, but no new telemetry arrived - we ignore this until new telemetry is there. + for (EntityKey key : entityKeys) { + if (key.getType().equals(EntityKeyType.TIME_SERIES)) { + return false; + } + } + for (EntityKey key : changedKeys) { + if (entityKeys.contains(key)) { + return true; + } + } + return false; + } + public AlarmConditionSpec getSpec(AlarmRule alarmRule) { AlarmConditionSpec spec = alarmRule.getCondition().getSpec(); if (spec == null) { @@ -93,7 +121,7 @@ public class AlarmRuleState { } } - public boolean eval(DeviceDataSnapshot data) { + public boolean eval(DataSnapshot data) { boolean active = isActive(data.getTs()); switch (spec.getType()) { case SIMPLE: @@ -167,7 +195,7 @@ public class AlarmRuleState { } } - private boolean evalRepeating(DeviceDataSnapshot data, boolean active) { + private boolean evalRepeating(DataSnapshot data, boolean active) { if (active && eval(alarmRule.getCondition(), data)) { state.setEventCount(state.getEventCount() + 1); updateFlag = true; @@ -177,7 +205,7 @@ public class AlarmRuleState { } } - private boolean evalDuration(DeviceDataSnapshot data, boolean active) { + private boolean evalDuration(DataSnapshot data, boolean active) { if (active && eval(alarmRule.getCondition(), data)) { if (state.getLastEventTs() > 0) { if (data.getTs() > state.getLastEventTs()) { @@ -211,7 +239,7 @@ public class AlarmRuleState { } } - private boolean eval(AlarmCondition condition, DeviceDataSnapshot data) { + private boolean eval(AlarmCondition condition, DataSnapshot data) { boolean eval = true; for (KeyFilter keyFilter : condition.getCondition()) { EntityKeyValue value = data.getValue(keyFilter.getKey()); @@ -380,4 +408,5 @@ public class AlarmRuleState { throw new RuntimeException("Operation not supported: " + predicate.getOperation()); } } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceProfileAlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java similarity index 81% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceProfileAlarmState.java rename to rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index f74d88fe62..a56377d2be 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceProfileAlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.profile; import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.action.TbAlarmResult; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.profile.state.PersistedAlarmRuleState; @@ -27,6 +28,7 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceQueue; @@ -39,8 +41,10 @@ import java.util.concurrent.ExecutionException; import java.util.function.BiFunction; @Data -class DeviceProfileAlarmState { +@Slf4j +class AlarmState { + private final ProfileState deviceProfile; private final EntityId originator; private DeviceProfileAlarm alarmDefinition; private volatile List createRulesSortedBySeverityDesc; @@ -50,27 +54,33 @@ class DeviceProfileAlarmState { private volatile TbMsgMetaData lastMsgMetaData; private volatile String lastMsgQueueName; - public DeviceProfileAlarmState(EntityId originator, DeviceProfileAlarm alarmDefinition, PersistedAlarmState alarmState) { + AlarmState(ProfileState deviceProfile, EntityId originator, DeviceProfileAlarm alarmDefinition, PersistedAlarmState alarmState) { + this.deviceProfile = deviceProfile; this.originator = originator; this.updateState(alarmDefinition, alarmState); } - public boolean process(TbContext ctx, TbMsg msg, DeviceDataSnapshot data) throws ExecutionException, InterruptedException { + public boolean process(TbContext ctx, TbMsg msg, DataSnapshot data, SnapshotUpdate update) throws ExecutionException, InterruptedException { initCurrentAlarm(ctx); lastMsgMetaData = msg.getMetaData(); lastMsgQueueName = msg.getQueueName(); - return createOrClearAlarms(ctx, data, AlarmRuleState::eval); + return createOrClearAlarms(ctx, data, update, AlarmRuleState::eval); } public boolean process(TbContext ctx, long ts) throws ExecutionException, InterruptedException { initCurrentAlarm(ctx); - return createOrClearAlarms(ctx, ts, AlarmRuleState::eval); + return createOrClearAlarms(ctx, ts, null, AlarmRuleState::eval); } - public boolean createOrClearAlarms(TbContext ctx, T data, BiFunction evalFunction) { + public boolean createOrClearAlarms(TbContext ctx, T data, SnapshotUpdate update, BiFunction evalFunction) { boolean stateUpdate = false; AlarmSeverity resultSeverity = null; + log.debug("[{}] processing update: {}", alarmDefinition.getId(), data); for (AlarmRuleState state : createRulesSortedBySeverityDesc) { + if (!validateUpdate(update, state)) { + log.debug("[{}][{}] Update is not valid for current rule state", alarmDefinition.getId(), state.getSeverity()); + continue; + } boolean evalResult = evalFunction.apply(state, data); stateUpdate |= state.checkUpdate(); if (evalResult) { @@ -81,6 +91,10 @@ class DeviceProfileAlarmState { if (resultSeverity != null) { pushMsg(ctx, calculateAlarmResult(ctx, resultSeverity)); } else if (currentAlarm != null && clearState != null) { + if (!validateUpdate(update, clearState)) { + log.debug("[{}] Update is not valid for current clear state", alarmDefinition.getId()); + return stateUpdate; + } Boolean evalResult = evalFunction.apply(clearState, data); if (evalResult) { stateUpdate |= clearState.checkUpdate(); @@ -92,6 +106,18 @@ class DeviceProfileAlarmState { return stateUpdate; } + public boolean validateUpdate(SnapshotUpdate update, AlarmRuleState state) { + if (update != null) { + //Check that the update type and that keys match. + if (update.getType().equals(EntityKeyType.TIME_SERIES)) { + return state.validateTsUpdate(update.getKeys()); + } else if (update.getType().equals(EntityKeyType.ATTRIBUTE)) { + return state.validateAttrUpdate(update.getKeys()); + } + } + return true; + } + public void initCurrentAlarm(TbContext ctx) throws InterruptedException, ExecutionException { if (!initialFetchDone) { Alarm alarm = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), originator, alarmDefinition.getAlarmType()).get(); @@ -137,12 +163,13 @@ class DeviceProfileAlarmState { alarmState.getCreateRuleStates().put(severity, ruleState); } } - createRulesSortedBySeverityDesc.add(new AlarmRuleState(severity, rule, ruleState)); + createRulesSortedBySeverityDesc.add(new AlarmRuleState(severity, rule, + deviceProfile.getCreateAlarmKeys(alarm.getId(), severity), ruleState)); }); createRulesSortedBySeverityDesc.sort(Comparator.comparingInt(state -> state.getSeverity().ordinal())); PersistedAlarmRuleState ruleState = alarmState == null ? null : alarmState.getClearRuleState(); if (alarmDefinition.getClearRule() != null) { - clearState = new AlarmRuleState(null, alarmDefinition.getClearRule(), ruleState); + clearState = new AlarmRuleState(null, alarmDefinition.getClearRule(), deviceProfile.getClearAlarmKeys(alarm.getId()), ruleState); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmStateUpdateResult.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmStateUpdateResult.java index de9708dc58..a84cf96710 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmStateUpdateResult.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmStateUpdateResult.java @@ -15,7 +15,7 @@ */ package org.thingsboard.rule.engine.profile; -public enum AlarmStateUpdateResult { +enum AlarmStateUpdateResult { NONE, CREATED, UPDATED, SEVERITY_UPDATED, CLEARED; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceDataSnapshot.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DataSnapshot.java similarity index 70% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceDataSnapshot.java rename to rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DataSnapshot.java index f1b1067095..0c3abc0ca3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceDataSnapshot.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DataSnapshot.java @@ -24,7 +24,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -public class DeviceDataSnapshot { +class DataSnapshot { private volatile boolean ready; @Getter @@ -33,7 +33,7 @@ public class DeviceDataSnapshot { private final Set keys; private final Map values = new ConcurrentHashMap<>(); - public DeviceDataSnapshot(Set entityKeysToFetch) { + DataSnapshot(Set entityKeysToFetch) { this.keys = entityKeysToFetch; } @@ -56,28 +56,38 @@ public class DeviceDataSnapshot { } } - void putValue(EntityKey key, EntityKeyValue value) { + boolean putValue(EntityKey key, long newTs, EntityKeyValue value) { + boolean updateOfTs = ts != newTs; + boolean result = false; switch (key.getType()) { case ATTRIBUTE: - putIfKeyExists(key, value); - putIfKeyExists(getAttrKey(key, EntityKeyType.CLIENT_ATTRIBUTE), value); - putIfKeyExists(getAttrKey(key, EntityKeyType.SHARED_ATTRIBUTE), value); - putIfKeyExists(getAttrKey(key, EntityKeyType.SERVER_ATTRIBUTE), value); + result |= putIfKeyExists(key, value, updateOfTs); + result |= putIfKeyExists(getAttrKey(key, EntityKeyType.CLIENT_ATTRIBUTE), value, updateOfTs); + result |= putIfKeyExists(getAttrKey(key, EntityKeyType.SHARED_ATTRIBUTE), value, updateOfTs); + result |= putIfKeyExists(getAttrKey(key, EntityKeyType.SERVER_ATTRIBUTE), value, updateOfTs); break; case CLIENT_ATTRIBUTE: case SHARED_ATTRIBUTE: case SERVER_ATTRIBUTE: - putIfKeyExists(key, value); - putIfKeyExists(getAttrKey(key, EntityKeyType.ATTRIBUTE), value); + result |= putIfKeyExists(key, value, updateOfTs); + result |= putIfKeyExists(getAttrKey(key, EntityKeyType.ATTRIBUTE), value, updateOfTs); break; default: - putIfKeyExists(key, value); + result |= putIfKeyExists(key, value, updateOfTs); } + return result; } - private void putIfKeyExists(EntityKey key, EntityKeyValue value) { + private boolean putIfKeyExists(EntityKey key, EntityKeyValue value, boolean updateOfTs) { if (keys.contains(key)) { - values.put(key, value); + EntityKeyValue oldValue = values.put(key, value); + if (updateOfTs) { + return true; + } else { + return oldValue == null || !oldValue.equals(value); + } + } else { + return false; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java index 078be24f90..6824f56d35 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.profile; import com.google.gson.JsonParser; +import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.profile.state.PersistedAlarmState; @@ -29,7 +30,6 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.RuleNodeStateId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; @@ -53,17 +53,18 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; +@Slf4j class DeviceState { private final boolean persistState; private final DeviceId deviceId; + private final ProfileState deviceProfile; private RuleNodeState state; - private DeviceProfileState deviceProfile; private PersistedDeviceState pds; - private DeviceDataSnapshot latestValues; - private final ConcurrentMap alarmStates = new ConcurrentHashMap<>(); + private DataSnapshot latestValues; + private final ConcurrentMap alarmStates = new ConcurrentHashMap<>(); - public DeviceState(TbContext ctx, TbDeviceProfileNodeConfiguration config, DeviceId deviceId, DeviceProfileState deviceProfile, RuleNodeState state) { + DeviceState(TbContext ctx, TbDeviceProfileNodeConfiguration config, DeviceId deviceId, ProfileState deviceProfile, RuleNodeState state) { this.persistState = config.isPersistAlarmRulesState(); this.deviceId = deviceId; this.deviceProfile = deviceProfile; @@ -86,7 +87,7 @@ class DeviceState { if (pds != null) { for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { alarmStates.computeIfAbsent(alarm.getId(), - a -> new DeviceProfileAlarmState(deviceId, alarm, getOrInitPersistedAlarmState(alarm))); + a -> new AlarmState(deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm))); } } } @@ -107,14 +108,20 @@ class DeviceState { if (alarmStates.containsKey(alarm.getId())) { alarmStates.get(alarm.getId()).updateState(alarm, getOrInitPersistedAlarmState(alarm)); } else { - alarmStates.putIfAbsent(alarm.getId(), new DeviceProfileAlarmState(deviceId, alarm, getOrInitPersistedAlarmState(alarm))); + alarmStates.putIfAbsent(alarm.getId(), new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm))); } } } public void harvestAlarms(TbContext ctx, long ts) throws ExecutionException, InterruptedException { - for (DeviceProfileAlarmState state : alarmStates.values()) { - state.process(ctx, ts); + log.debug("[{}] Going to harvest alarms: {}", ctx.getSelfId(), ts); + boolean stateChanged = false; + for (AlarmState state : alarmStates.values()) { + stateChanged |= state.process(ctx, ts); + } + if (persistState && stateChanged) { + state.setStateData(JacksonUtil.toString(pds)); + state = ctx.saveRuleNodeState(state); } } @@ -146,8 +153,8 @@ class DeviceState { boolean stateChanged = false; Alarm alarmNf = JacksonUtil.fromString(msg.getData(), Alarm.class); for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { - DeviceProfileAlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), - a -> new DeviceProfileAlarmState(deviceId, alarm, getOrInitPersistedAlarmState(alarm))); + AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), + a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm))); stateChanged |= alarmState.processAlarmClear(ctx, alarmNf); } ctx.tellSuccess(msg); @@ -175,9 +182,9 @@ class DeviceState { EntityKeyType keyType = getKeyTypeFromScope(scope); keys.forEach(key -> latestValues.removeValue(new EntityKey(keyType, key))); for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { - DeviceProfileAlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), - a -> new DeviceProfileAlarmState(deviceId, alarm, getOrInitPersistedAlarmState(alarm))); - stateChanged |= alarmState.process(ctx, msg, latestValues); + AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), + a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm))); + stateChanged |= alarmState.process(ctx, msg, latestValues, null); } } ctx.tellSuccess(msg); @@ -192,11 +199,11 @@ class DeviceState { private boolean processAttributesUpdate(TbContext ctx, TbMsg msg, Set attributes, String scope) throws ExecutionException, InterruptedException { boolean stateChanged = false; if (!attributes.isEmpty()) { - merge(latestValues, attributes, scope); + SnapshotUpdate update = merge(latestValues, attributes, scope); for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { - DeviceProfileAlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), - a -> new DeviceProfileAlarmState(deviceId, alarm, getOrInitPersistedAlarmState(alarm))); - stateChanged |= alarmState.process(ctx, msg, latestValues); + AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), + a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm))); + stateChanged |= alarmState.process(ctx, msg, latestValues, update); } } ctx.tellSuccess(msg); @@ -206,34 +213,47 @@ class DeviceState { protected boolean processTelemetry(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { boolean stateChanged = false; Map> tsKvMap = JsonConverter.convertToSortedTelemetry(new JsonParser().parse(msg.getData()), TbMsgTimeseriesNode.getTs(msg)); + // iterate over data by ts (ASC order). for (Map.Entry> entry : tsKvMap.entrySet()) { Long ts = entry.getKey(); List data = entry.getValue(); - merge(latestValues, ts, data); - for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { - DeviceProfileAlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), - a -> new DeviceProfileAlarmState(deviceId, alarm, getOrInitPersistedAlarmState(alarm))); - stateChanged |= alarmState.process(ctx, msg, latestValues); + SnapshotUpdate update = merge(latestValues, ts, data); + if (update.hasUpdate()) { + for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { + AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), + a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm))); + stateChanged |= alarmState.process(ctx, msg, latestValues, update); + } } } ctx.tellSuccess(msg); return stateChanged; } - private void merge(DeviceDataSnapshot latestValues, Long ts, List data) { - latestValues.setTs(ts); + private SnapshotUpdate merge(DataSnapshot latestValues, Long newTs, List data) { + Set keys = new HashSet<>(); for (KvEntry entry : data) { - latestValues.putValue(new EntityKey(EntityKeyType.TIME_SERIES, entry.getKey()), toEntityValue(entry)); + EntityKey entityKey = new EntityKey(EntityKeyType.TIME_SERIES, entry.getKey()); + if (latestValues.putValue(entityKey, newTs, toEntityValue(entry))) { + keys.add(entityKey); + } } + latestValues.setTs(newTs); + return new SnapshotUpdate(EntityKeyType.TIME_SERIES, keys); } - private void merge(DeviceDataSnapshot latestValues, Set attributes, String scope) { - long ts = latestValues.getTs(); + private SnapshotUpdate merge(DataSnapshot latestValues, Set attributes, String scope) { + long newTs = 0; + Set keys = new HashSet<>(); for (AttributeKvEntry entry : attributes) { - ts = Math.max(ts, entry.getLastUpdateTs()); - latestValues.putValue(new EntityKey(getKeyTypeFromScope(scope), entry.getKey()), toEntityValue(entry)); + newTs = Math.max(newTs, entry.getLastUpdateTs()); + EntityKey entityKey = new EntityKey(getKeyTypeFromScope(scope), entry.getKey()); + if (latestValues.putValue(entityKey, newTs, toEntityValue(entry))) { + keys.add(entityKey); + } } - latestValues.setTs(ts); + latestValues.setTs(newTs); + return new SnapshotUpdate(EntityKeyType.ATTRIBUTE, keys); } private static EntityKeyType getKeyTypeFromScope(String scope) { @@ -248,14 +268,14 @@ class DeviceState { return EntityKeyType.ATTRIBUTE; } - private DeviceDataSnapshot fetchLatestValues(TbContext ctx, EntityId originator) throws ExecutionException, InterruptedException { + private DataSnapshot fetchLatestValues(TbContext ctx, EntityId originator) throws ExecutionException, InterruptedException { Set entityKeysToFetch = deviceProfile.getEntityKeys(); - DeviceDataSnapshot result = new DeviceDataSnapshot(entityKeysToFetch); + DataSnapshot result = new DataSnapshot(entityKeysToFetch); addEntityKeysToSnapshot(ctx, originator, entityKeysToFetch, result); return result; } - private void addEntityKeysToSnapshot(TbContext ctx, EntityId originator, Set entityKeysToFetch, DeviceDataSnapshot result) throws InterruptedException, ExecutionException { + private void addEntityKeysToSnapshot(TbContext ctx, EntityId originator, Set entityKeysToFetch, DataSnapshot result) throws InterruptedException, ExecutionException { Set serverAttributeKeys = new HashSet<>(); Set clientAttributeKeys = new HashSet<>(); Set sharedAttributeKeys = new HashSet<>(); @@ -291,16 +311,16 @@ class DeviceState { if (device != null) { switch (key) { case EntityKeyMapping.NAME: - result.putValue(entityKey, EntityKeyValue.fromString(device.getName())); + result.putValue(entityKey, device.getCreatedTime(), EntityKeyValue.fromString(device.getName())); break; case EntityKeyMapping.TYPE: - result.putValue(entityKey, EntityKeyValue.fromString(device.getType())); + result.putValue(entityKey, device.getCreatedTime(), EntityKeyValue.fromString(device.getType())); break; case EntityKeyMapping.CREATED_TIME: - result.putValue(entityKey, EntityKeyValue.fromLong(device.getCreatedTime())); + result.putValue(entityKey, device.getCreatedTime(), EntityKeyValue.fromLong(device.getCreatedTime())); break; case EntityKeyMapping.LABEL: - result.putValue(entityKey, EntityKeyValue.fromString(device.getLabel())); + result.putValue(entityKey, device.getCreatedTime(), EntityKeyValue.fromString(device.getLabel())); break; } } @@ -312,7 +332,7 @@ class DeviceState { List data = ctx.getTimeseriesService().findLatest(ctx.getTenantId(), originator, latestTsKeys).get(); for (TsKvEntry entry : data) { if (entry.getValue() != null) { - result.putValue(new EntityKey(EntityKeyType.TIME_SERIES, entry.getKey()), toEntityValue(entry)); + result.putValue(new EntityKey(EntityKeyType.TIME_SERIES, entry.getKey()), entry.getTs(), toEntityValue(entry)); } } } @@ -330,13 +350,13 @@ class DeviceState { } } - private void addToSnapshot(DeviceDataSnapshot snapshot, Set commonAttributeKeys, List data) { + private void addToSnapshot(DataSnapshot snapshot, Set commonAttributeKeys, List data) { for (AttributeKvEntry entry : data) { if (entry.getValue() != null) { EntityKeyValue value = toEntityValue(entry); - snapshot.putValue(new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, entry.getKey()), value); + snapshot.putValue(new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, entry.getKey()), entry.getLastUpdateTs(), value); if (commonAttributeKeys.contains(entry.getKey())) { - snapshot.putValue(new EntityKey(EntityKeyType.ATTRIBUTE, entry.getKey()), value); + snapshot.putValue(new EntityKey(EntityKeyType.ATTRIBUTE, entry.getKey()), entry.getLastUpdateTs(), value); } } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/EntityKeyValue.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/EntityKeyValue.java index 40ca323307..73a4db63b0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/EntityKeyValue.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/EntityKeyValue.java @@ -15,9 +15,11 @@ */ package org.thingsboard.rule.engine.profile; +import lombok.EqualsAndHashCode; import lombok.Getter; import org.thingsboard.server.common.data.kv.DataType; +@EqualsAndHashCode class EntityKeyValue { @Getter diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceProfileState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java similarity index 51% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceProfileState.java rename to rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java index fd9037624e..882a7d7c9a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceProfileState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java @@ -18,19 +18,25 @@ package org.thingsboard.rule.engine.profile; import lombok.AccessLevel; import lombok.Getter; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.device.profile.AlarmRule; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.KeyFilter; +import javax.print.attribute.standard.Severity; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; -class DeviceProfileState { +class ProfileState { private DeviceProfile deviceProfile; @Getter(AccessLevel.PACKAGE) @@ -38,26 +44,64 @@ class DeviceProfileState { @Getter(AccessLevel.PACKAGE) private final Set entityKeys = ConcurrentHashMap.newKeySet(); - DeviceProfileState(DeviceProfile deviceProfile) { + private final Map>> alarmCreateKeys = new HashMap<>(); + private final Map> alarmClearKeys = new HashMap<>(); + + ProfileState(DeviceProfile deviceProfile) { updateDeviceProfile(deviceProfile); } void updateDeviceProfile(DeviceProfile deviceProfile) { this.deviceProfile = deviceProfile; alarmSettings.clear(); + alarmCreateKeys.clear(); + alarmClearKeys.clear(); if (deviceProfile.getProfileData().getAlarms() != null) { alarmSettings.addAll(deviceProfile.getProfileData().getAlarms()); for (DeviceProfileAlarm alarm : deviceProfile.getProfileData().getAlarms()) { - for (AlarmRule alarmRule : alarm.getCreateRules().values()) { + Map> createAlarmKeys = alarmCreateKeys.computeIfAbsent(alarm.getId(), id -> new HashMap<>()); + alarm.getCreateRules().forEach(((severity, alarmRule) -> { + Set ruleKeys = createAlarmKeys.computeIfAbsent(severity, id -> new HashSet<>()); for (KeyFilter keyFilter : alarmRule.getCondition().getCondition()) { entityKeys.add(keyFilter.getKey()); + ruleKeys.add(keyFilter.getKey()); + } + })); + if (alarm.getClearRule() != null) { + Set clearAlarmKeys = alarmClearKeys.computeIfAbsent(alarm.getId(), id -> new HashSet<>()); + for (KeyFilter keyFilter : alarm.getClearRule().getCondition().getCondition()) { + entityKeys.add(keyFilter.getKey()); + clearAlarmKeys.add(keyFilter.getKey()); } } } } } - public DeviceProfileId getProfileId() { + DeviceProfileId getProfileId() { return deviceProfile.getId(); } + + Set getCreateAlarmKeys(String id, AlarmSeverity severity) { + Map> sKeys = alarmCreateKeys.get(id); + if (sKeys == null) { + return Collections.emptySet(); + } else { + Set keys = sKeys.get(severity); + if (keys == null) { + return Collections.emptySet(); + } else { + return keys; + } + } + } + + Set getClearAlarmKeys(String id) { + Set keys = alarmClearKeys.get(id); + if (keys == null) { + return Collections.emptySet(); + } else { + return keys; + } + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/EntityKeyState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/SnapshotUpdate.java similarity index 58% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/EntityKeyState.java rename to rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/SnapshotUpdate.java index 08929bd2a2..e52a784c9c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/EntityKeyState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/SnapshotUpdate.java @@ -15,8 +15,25 @@ */ package org.thingsboard.rule.engine.profile; -public class EntityKeyState { +import lombok.Getter; +import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.common.data.query.EntityKeyType; +import java.util.Set; +class SnapshotUpdate { + @Getter + private final EntityKeyType type; + @Getter + private final Set keys; + + SnapshotUpdate(EntityKeyType type, Set keys) { + this.type = type; + this.keys = keys; + } + + boolean hasUpdate(){ + return !keys.isEmpty(); + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java index 97ec088f10..a6fcd8fa64 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java @@ -23,7 +23,6 @@ import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; -import org.thingsboard.rule.engine.profile.state.PersistedDeviceState; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; @@ -36,11 +35,10 @@ import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.rule.RuleNodeState; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.dao.util.mapping.JacksonUtil; -import java.util.HashMap; import java.util.Map; -import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -68,11 +66,14 @@ public class TbDeviceProfileNode implements TbNode { this.cache = ctx.getDeviceProfileCache(); scheduleAlarmHarvesting(ctx); if (config.isFetchAlarmRulesStateOnStart()) { + log.info("[{}] Fetching alarm rule state", ctx.getSelfId()); + int fetchCount = 0; PageLink pageLink = new PageLink(1024); while (true) { PageData states = ctx.findRuleNodeStates(pageLink); if (!states.getData().isEmpty()) { for (RuleNodeState rns : states.getData()) { + fetchCount++; if (rns.getEntityId().getEntityType().equals(EntityType.DEVICE) && ctx.isLocalEntity(rns.getEntityId())) { getOrCreateDeviceState(ctx, new DeviceId(rns.getEntityId().getId()), rns); } @@ -84,6 +85,7 @@ public class TbDeviceProfileNode implements TbNode { pageLink = pageLink.nextPageLink(); } } + log.info("[{}] Fetched alarm rule state for {} entities", ctx.getSelfId(), fetchCount); } } @@ -112,11 +114,14 @@ public class TbDeviceProfileNode implements TbNode { } } } else if (EntityType.DEVICE_PROFILE.equals(originatorType)) { + log.info("[{}] Received device profile update notification: {}", ctx.getSelfId(), msg.getData()); if (msg.getType().equals("ENTITY_UPDATED")) { DeviceProfile deviceProfile = JacksonUtil.fromString(msg.getData(), DeviceProfile.class); - for (DeviceState state : deviceStates.values()) { - if (deviceProfile.getId().equals(state.getProfileId())) { - state.updateProfile(ctx, deviceProfile); + if (deviceProfile != null) { + for (DeviceState state : deviceStates.values()) { + if (deviceProfile.getId().equals(state.getProfileId())) { + state.updateProfile(ctx, deviceProfile); + } } } } @@ -138,6 +143,12 @@ public class TbDeviceProfileNode implements TbNode { } } + @Override + public void onPartitionChangeMsg(TbContext ctx, PartitionChangeMsg msg) { + // Cleanup the cache for all entities that are no longer assigned to current server partitions + deviceStates.entrySet().removeIf(entry -> !ctx.isLocalEntity(entry.getKey())); + } + @Override public void destroy() { deviceStates.clear(); @@ -148,7 +159,7 @@ public class TbDeviceProfileNode implements TbNode { if (deviceState == null) { DeviceProfile deviceProfile = cache.get(ctx.getTenantId(), deviceId); if (deviceProfile != null) { - deviceState = new DeviceState(ctx, config, deviceId, new DeviceProfileState(deviceProfile), rns); + deviceState = new DeviceState(ctx, config, deviceId, new ProfileState(deviceProfile), rns); deviceStates.put(deviceId, deviceState); } } From ca4e9238c88bb69e2abec04ec8be4a2740d1ead0 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 12 Oct 2020 13:30:23 +0300 Subject: [PATCH 177/463] Fix for device provision tests --- .../AbstractMqttProvisionProtoDeviceTest.java | 10 +++------- .../server/dao/device/DeviceServiceImpl.java | 7 +++++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java index c5becedbbd..12d8f91eb6 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java @@ -15,8 +15,6 @@ */ package org.thingsboard.server.mqtt.provision; -import com.google.gson.JsonObject; -import com.sun.mail.iap.ByteArray; import io.netty.handler.codec.mqtt.MqttQoS; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; @@ -35,12 +33,10 @@ import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.msg.EncryptionUtil; -import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; import org.thingsboard.server.dao.util.mapping.JacksonUtil; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.CredentialsDataProto; import org.thingsboard.server.gen.transport.TransportProtos.CredentialsType; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceCredentialsMsg; @@ -129,7 +125,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken("test_token").build()).build(); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.ACCESS_TOKEN, requestCredentials)).getPayloadBytes()); @@ -149,7 +145,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder().setHash("testHash").build()).build(); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.X509_CERTIFICATE, requestCredentials)).getPayloadBytes()); @@ -175,7 +171,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateBasicMqttCredRequestMsg( ValidateBasicMqttCredRequestMsg.newBuilder() .setClientId("test_clientId") diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 1df488ff63..b0290e8eb1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -472,6 +472,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe } @Override + @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#profile.tenantId, #provisionRequest.deviceName}") @Transactional public Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { Device device = new Device(); @@ -485,10 +486,13 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe !StringUtils.isEmpty(provisionRequest.getCredentialsData().getPassword()) || !StringUtils.isEmpty(provisionRequest.getCredentialsData().getClientId())) { DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getTenantId(), savedDevice.getId()); + if (deviceCredentials == null) { + deviceCredentials = new DeviceCredentials(); + } + deviceCredentials.setDeviceId(savedDevice.getId()); deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType()); switch (provisionRequest.getCredentialsType()) { case ACCESS_TOKEN: - deviceCredentials.setDeviceId(savedDevice.getId()); deviceCredentials.setCredentialsId(provisionRequest.getCredentialsData().getToken()); break; case MQTT_BASIC: @@ -502,7 +506,6 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe deviceCredentials.setCredentialsValue(provisionRequest.getCredentialsData().getX509CertHash()); break; } - deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType()); try { deviceCredentialsService.updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials); } catch (Exception e) { From 6d185f1c01090c3180b7312f1fdfbd258f8ba8ba Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 12 Oct 2020 14:29:20 +0300 Subject: [PATCH 178/463] Improvements to device profile rule node --- .../actors/ruleChain/DefaultTbContext.java | 8 + .../controller/RuleChainController.java | 2 + .../server/dao/rule/RuleNodeStateService.java | 1 + .../data/query/DynamicValueSourceType.java | 3 +- .../dao/rule/BaseRuleNodeStateService.java | 11 + .../server/dao/rule/RuleNodeStateDao.java | 4 + .../dao/sql/rule/JpaRuleNodeStateDao.java | 7 + .../dao/sql/rule/RuleNodeStateRepository.java | 3 + .../rule/engine/api/TbContext.java | 2 + .../rule/engine/profile/AlarmRuleState.java | 219 +++++++++++------- .../rule/engine/profile/AlarmState.java | 6 + .../rule/engine/profile/ProfileState.java | 30 +++ .../engine/profile/TbDeviceProfileNode.java | 4 + 13 files changed, 211 insertions(+), 89 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 402caed226..b02bc302de 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -469,6 +469,14 @@ class DefaultTbContext implements TbContext { return mainCtx.getRuleNodeStateService().save(getTenantId(), state); } + @Override + public void clearRuleNodeStates() { + if (log.isDebugEnabled()) { + log.debug("[{}][{}] Going to clear rule node states", getTenantId(), getSelfId()); + } + mainCtx.getRuleNodeStateService().removeByRuleNodeId(getTenantId(), getSelfId()); + } + private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("ruleNodeId", ruleNodeId.toString()); diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java index 5c54c0232e..6d80c05c05 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -164,6 +164,8 @@ public class RuleChainController extends BaseController { RuleChain savedRuleChain = installScripts.createDefaultRuleChain(getCurrentUser().getTenantId(), request.getName()); + tbClusterService.onEntityStateChange(savedRuleChain.getTenantId(), savedRuleChain.getId(), ComponentLifecycleEvent.CREATED); + logEntityAction(savedRuleChain.getId(), savedRuleChain, null, ActionType.ADDED, null); return savedRuleChain; diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleNodeStateService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleNodeStateService.java index 07138a1a11..d5ad9bbbb6 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleNodeStateService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleNodeStateService.java @@ -30,4 +30,5 @@ public interface RuleNodeStateService { RuleNodeState save(TenantId tenantId, RuleNodeState ruleNodeState); + void removeByRuleNodeId(TenantId tenantId, RuleNodeId selfId); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/DynamicValueSourceType.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/DynamicValueSourceType.java index 96734fde92..7da9ab00af 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/DynamicValueSourceType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/DynamicValueSourceType.java @@ -18,5 +18,6 @@ package org.thingsboard.server.common.data.query; public enum DynamicValueSourceType { CURRENT_TENANT, CURRENT_CUSTOMER, - CURRENT_USER + CURRENT_USER, + CURRENT_DEVICE } diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleNodeStateService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleNodeStateService.java index a0b83f0333..1d7e8af03f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleNodeStateService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleNodeStateService.java @@ -68,6 +68,17 @@ public class BaseRuleNodeStateService extends AbstractEntityService implements R return saveOrUpdate(tenantId, ruleNodeState, false); } + @Override + public void removeByRuleNodeId(TenantId tenantId, RuleNodeId ruleNodeId) { + if (tenantId == null) { + throw new DataValidationException("Tenant id should be specified!."); + } + if (ruleNodeId == null) { + throw new DataValidationException("Rule node id should be specified!."); + } + ruleNodeStateDao.removeByRuleNodeId(ruleNodeId.getId()); + } + public RuleNodeState saveOrUpdate(TenantId tenantId, RuleNodeState ruleNodeState, boolean update) { try { if (update) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeStateDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeStateDao.java index b12c448aa5..89c2baea7f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeStateDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeStateDao.java @@ -16,6 +16,8 @@ package org.thingsboard.server.dao.rule; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleNodeState; @@ -31,4 +33,6 @@ public interface RuleNodeStateDao extends Dao { PageData findByRuleNodeId(UUID ruleNodeId, PageLink pageLink); RuleNodeState findByRuleNodeIdAndEntityId(UUID ruleNodeId, UUID entityId); + + void removeByRuleNodeId(UUID ruleNodeId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeStateDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeStateDao.java index 51e487b456..c61e58353c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeStateDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeStateDao.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -56,4 +57,10 @@ public class JpaRuleNodeStateDao extends JpaAbstractDao 0; - break; - case DOUBLE: - value = ekv.getDblValue() > 0; - break; - case BOOLEAN: - value = ekv.getBoolValue(); - break; - case STRING: - try { - value = Boolean.parseBoolean(ekv.getStrValue()); - break; - } catch (RuntimeException e) { - return false; - } - case JSON: - try { - value = Boolean.parseBoolean(ekv.getJsonValue()); - break; - } catch (RuntimeException e) { - return false; - } - default: - return false; - } - if (value == null) { + private boolean evalBoolPredicate(DataSnapshot data, EntityKeyValue ekv, BooleanFilterPredicate predicate) { + Boolean val = getBoolValue(ekv); + if (val == null) { return false; } + Boolean predicateValue = getPredicateValue(data, predicate.getValue(), AlarmRuleState::getBoolValue); switch (predicate.getOperation()) { case EQUAL: - return value.equals(predicate.getValue().getDefaultValue()); + return val.equals(predicateValue); case NOT_EQUAL: - return !value.equals(predicate.getValue().getDefaultValue()); + return !val.equals(predicateValue); default: throw new RuntimeException("Operation not supported: " + predicate.getOperation()); } } - private boolean evalNumPredicate(EntityKeyValue ekv, NumericFilterPredicate predicate) { - Double value; - switch (ekv.getDataType()) { - case LONG: - value = ekv.getLngValue().doubleValue(); - break; - case DOUBLE: - value = ekv.getDblValue(); - break; - case BOOLEAN: - value = ekv.getBoolValue() ? 1.0 : 0.0; - break; - case STRING: - try { - value = Double.parseDouble(ekv.getStrValue()); - break; - } catch (RuntimeException e) { - return false; - } - case JSON: - try { - value = Double.parseDouble(ekv.getJsonValue()); - break; - } catch (RuntimeException e) { - return false; - } - default: - return false; - } - if (value == null) { + private boolean evalNumPredicate(DataSnapshot data, EntityKeyValue ekv, NumericFilterPredicate predicate) { + Double val = getDblValue(ekv); + if (val == null) { return false; } - - Double predicateValue = predicate.getValue().getDefaultValue(); + Double predicateValue = getPredicateValue(data, predicate.getValue(), AlarmRuleState::getDblValue); switch (predicate.getOperation()) { case NOT_EQUAL: - return !value.equals(predicateValue); + return !val.equals(predicateValue); case EQUAL: - return value.equals(predicateValue); + return val.equals(predicateValue); case GREATER: - return value > predicateValue; + return val > predicateValue; case GREATER_OR_EQUAL: - return value >= predicateValue; + return val >= predicateValue; case LESS: - return value < predicateValue; + return val < predicateValue; case LESS_OR_EQUAL: - return value <= predicateValue; + return val <= predicateValue; default: throw new RuntimeException("Operation not supported: " + predicate.getOperation()); } } - private boolean evalStrPredicate(EntityKeyValue ekv, StringFilterPredicate predicate) { - String val; - String predicateValue; + private boolean evalStrPredicate(DataSnapshot data, EntityKeyValue ekv, StringFilterPredicate predicate) { + String val = getStrValue(ekv); + if (val == null) { + return false; + } + String predicateValue = getPredicateValue(data, predicate.getValue(), AlarmRuleState::getStrValue); if (predicate.isIgnoreCase()) { - val = ekv.getStrValue().toLowerCase(); - predicateValue = predicate.getValue().getDefaultValue().toLowerCase(); - } else { - val = ekv.getStrValue(); - predicateValue = predicate.getValue().getDefaultValue(); + val = val.toLowerCase(); + predicateValue = predicateValue.toLowerCase(); } switch (predicate.getOperation()) { case CONTAINS: @@ -409,4 +357,99 @@ class AlarmRuleState { } } + private T getPredicateValue(DataSnapshot data, FilterPredicateValue value, Function transformFunction) { + EntityKeyValue ekv = getDynamicPredicateValue(data, value); + if (ekv != null) { + T result = transformFunction.apply(ekv); + if (result != null) { + return result; + } + } + return value.getDefaultValue(); + } + + private EntityKeyValue getDynamicPredicateValue(DataSnapshot data, FilterPredicateValue value) { + EntityKeyValue ekv = null; + if (value.getDynamicValue() != null) { + ekv = data.getValue(new EntityKey(EntityKeyType.ATTRIBUTE, value.getDynamicValue().getSourceAttribute())); + if (ekv == null) { + ekv = data.getValue(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, value.getDynamicValue().getSourceAttribute())); + if (ekv == null) { + ekv = data.getValue(new EntityKey(EntityKeyType.SHARED_ATTRIBUTE, value.getDynamicValue().getSourceAttribute())); + if (ekv == null) { + ekv = data.getValue(new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, value.getDynamicValue().getSourceAttribute())); + } + } + } + } + return ekv; + } + + private static String getStrValue(EntityKeyValue ekv) { + switch (ekv.getDataType()) { + case LONG: + return ekv.getLngValue() != null ? ekv.getLngValue().toString() : null; + case DOUBLE: + return ekv.getDblValue() != null ? ekv.getDblValue().toString() : null; + case BOOLEAN: + return ekv.getBoolValue() != null ? ekv.getBoolValue().toString() : null; + case STRING: + return ekv.getStrValue(); + case JSON: + return ekv.getJsonValue(); + default: + return null; + } + } + + private static Double getDblValue(EntityKeyValue ekv) { + switch (ekv.getDataType()) { + case LONG: + return ekv.getLngValue() != null ? ekv.getLngValue().doubleValue() : null; + case DOUBLE: + return ekv.getDblValue() != null ? ekv.getDblValue() : null; + case BOOLEAN: + return ekv.getBoolValue() != null ? (ekv.getBoolValue() ? 1.0 : 0.0) : null; + case STRING: + try { + return Double.parseDouble(ekv.getStrValue()); + } catch (RuntimeException e) { + return null; + } + case JSON: + try { + return Double.parseDouble(ekv.getJsonValue()); + } catch (RuntimeException e) { + return null; + } + default: + return null; + } + } + + private static Boolean getBoolValue(EntityKeyValue ekv) { + switch (ekv.getDataType()) { + case LONG: + return ekv.getLngValue() != null ? ekv.getLngValue() > 0 : null; + case DOUBLE: + return ekv.getDblValue() != null ? ekv.getDblValue() > 0 : null; + case BOOLEAN: + return ekv.getBoolValue(); + case STRING: + try { + return Boolean.parseBoolean(ekv.getStrValue()); + } catch (RuntimeException e) { + return null; + } + case JSON: + try { + return Boolean.parseBoolean(ekv.getJsonValue()); + } catch (RuntimeException e) { + return null; + } + default: + return null; + } + } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index a56377d2be..5fb2c2957c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -98,6 +98,10 @@ class AlarmState { Boolean evalResult = evalFunction.apply(clearState, data); if (evalResult) { stateUpdate |= clearState.checkUpdate(); + for (AlarmRuleState state : createRulesSortedBySeverityDesc) { + state.clear(); + stateUpdate |= state.checkUpdate(); + } ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), JacksonUtil.OBJECT_MAPPER.createObjectNode(), System.currentTimeMillis()); pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm)); currentAlarm = null; @@ -175,6 +179,8 @@ class AlarmState { private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmSeverity severity) { if (currentAlarm != null) { + // TODO: In some extremely rare cases, we might miss the event of alarm clear (If one use in-mem queue and restarted the server) or (if one manipulated the rule chain). + // Maybe we should fetch alarm every time? currentAlarm.setEndTs(System.currentTimeMillis()); AlarmSeverity oldSeverity = currentAlarm.getSeverity(); if (!oldSeverity.equals(severity)) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java index 882a7d7c9a..b758a96c39 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java @@ -22,8 +22,16 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.device.profile.AlarmRule; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.query.ComplexFilterPredicate; +import org.thingsboard.server.common.data.query.DynamicValue; +import org.thingsboard.server.common.data.query.DynamicValueSourceType; import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.common.data.query.EntityKeyType; +import org.thingsboard.server.common.data.query.FilterPredicateValue; import org.thingsboard.server.common.data.query.KeyFilter; +import org.thingsboard.server.common.data.query.KeyFilterPredicate; +import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate; +import org.thingsboard.server.common.data.query.StringFilterPredicate; import javax.print.attribute.standard.Severity; import java.util.Collections; @@ -65,6 +73,7 @@ class ProfileState { for (KeyFilter keyFilter : alarmRule.getCondition().getCondition()) { entityKeys.add(keyFilter.getKey()); ruleKeys.add(keyFilter.getKey()); + addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, ruleKeys); } })); if (alarm.getClearRule() != null) { @@ -72,12 +81,33 @@ class ProfileState { for (KeyFilter keyFilter : alarm.getClearRule().getCondition().getCondition()) { entityKeys.add(keyFilter.getKey()); clearAlarmKeys.add(keyFilter.getKey()); + addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, clearAlarmKeys); } } } } } + private void addDynamicValuesRecursively(KeyFilterPredicate predicate, Set entityKeys, Set ruleKeys) { + switch (predicate.getType()) { + case STRING: + case NUMERIC: + case BOOLEAN: + DynamicValue value = ((SimpleKeyFilterPredicate) predicate).getValue().getDynamicValue(); + if (value != null && value.getSourceType() == DynamicValueSourceType.CURRENT_DEVICE) { + EntityKey entityKey = new EntityKey(EntityKeyType.ATTRIBUTE, value.getSourceAttribute()); + entityKeys.add(entityKey); + ruleKeys.add(entityKey); + } + break; + case COMPLEX: + for (KeyFilterPredicate child : ((ComplexFilterPredicate) predicate).getPredicates()) { + addDynamicValuesRecursively(child, entityKeys, ruleKeys); + } + break; + } + } + DeviceProfileId getProfileId() { return deviceProfile.getId(); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java index 3344af7f21..4b0b87043a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java @@ -89,6 +89,10 @@ public class TbDeviceProfileNode implements TbNode { } log.info("[{}] Fetched alarm rule state for {} entities", ctx.getSelfId(), fetchCount); } + if (!config.isPersistAlarmRulesState() && ctx.isLocalEntity(ctx.getSelfId())) { + log.info("[{}] Going to cleanup rule node states", ctx.getSelfId()); + ctx.clearRuleNodeStates(); + } } /** From 6d4245d6039f812f4cdecc94ba4ae308f6e978f6 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 12 Oct 2020 15:34:02 +0300 Subject: [PATCH 179/463] Chart tooltips: fix wrong number of decimals after floating point --- .../main/data/json/system/widget_bundles/charts.json | 10 +++++----- .../modules/home/components/widget/lib/flot-widget.ts | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/charts.json b/application/src/main/data/json/system/widget_bundles/charts.json index 22c9d20a23..1010013689 100644 --- a/application/src/main/data/json/system/widget_bundles/charts.json +++ b/application/src/main/data/json/system/widget_bundles/charts.json @@ -19,7 +19,7 @@ ], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n var barData = {\n labels: [],\n datasets: []\n };\n \n for (var i = 0; i < self.ctx.datasources.length; i++) {\n var datasource = self.ctx.datasources[i];\n for (var d = 0; d < datasource.dataKeys.length; d++) {\n var dataset = {\n label: datasource.dataKeys[d].label,\n data: [0],\n backgroundColor: [datasource.dataKeys[d].color],\n borderColor: [datasource.dataKeys[d].color],\n borderWidth: 1\n }\n barData.datasets.push(dataset);\n }\n }\n\n var ctx = $('#barChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'bar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false,\n scales: {\n yAxes: [{\n ticks: {\n beginAtZero:true\n }\n }]\n }\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var c = 0;\n for (var i = 0; i < self.ctx.chart.data.datasets.length; i++) {\n var dataset = self.ctx.chart.data.datasets[i];\n var cellData = self.ctx.data[i]; \n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = tvPair[1];\n dataset.data[0] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n", + "controllerScript": "self.onInit = function() {\n var barData = {\n labels: [],\n datasets: []\n };\n \n for (var i = 0; i < self.ctx.datasources.length; i++) {\n var datasource = self.ctx.datasources[i];\n for (var d = 0; d < datasource.dataKeys.length; d++) {\n var dataKey = datasource.dataKeys[d];\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n var dataset = {\n label: dataKey.label + units,\n data: [0],\n backgroundColor: [dataKey.color],\n borderColor: [dataKey.color],\n borderWidth: 1\n }\n barData.datasets.push(dataset);\n }\n }\n\n var ctx = $('#barChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'bar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false,\n scales: {\n yAxes: [{\n ticks: {\n beginAtZero:true\n }\n }]\n }\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var c = 0;\n for (var i = 0; i < self.ctx.chart.data.datasets.length; i++) {\n var dataset = self.ctx.chart.data.datasets[i];\n var cellData = self.ctx.data[i]; \n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n dataset.data[0] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Bars - Chart.js\"}" @@ -55,7 +55,7 @@ ], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n var borderColor = self.ctx.settings.borderColor || '#fff';\n var borderWidth = typeof self.ctx.settings.borderWidth !== 'undefined' ? self.ctx.settings.borderWidth : 5;\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n pieData.labels.push(dataKey.label);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push(borderColor);\n dataset.borderWidth.push(borderWidth);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var options = {\n responsive: false,\n maintainAspectRatio: false,\n legend: {\n display: true,\n labels: {\n fontColor: '#666'\n }\n },\n tooltips: {\n callbacks: {\n label: function(tooltipItem, data) {\n var label = data.labels[tooltipItem.index];\n var value = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];\n var content = label + ': ' + value;\n var units = self.ctx.settings.units ? self.ctx.settings.units : self.ctx.units;\n if (units) {\n content += ' ' + units;\n } \n return content;\n }\n }\n }\n };\n\n if (self.ctx.settings.legend) {\n options.legend.display = self.ctx.settings.legend.display !== false;\n options.legend.labels.fontColor = self.ctx.settings.legend.labelsFontColor || '#666';\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'doughnut',\n data: pieData,\n options: options\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = tvPair[1];\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n", + "controllerScript": "self.onInit = function() {\n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n var borderColor = self.ctx.settings.borderColor || '#fff';\n var borderWidth = typeof self.ctx.settings.borderWidth !== 'undefined' ? self.ctx.settings.borderWidth : 5;\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n pieData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push(borderColor);\n dataset.borderWidth.push(borderWidth);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var options = {\n responsive: false,\n maintainAspectRatio: false,\n legend: {\n display: true,\n labels: {\n fontColor: '#666'\n }\n },\n tooltips: {\n callbacks: {\n label: function(tooltipItem, data) {\n var label = data.labels[tooltipItem.index];\n var value = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];\n var content = label + ': ' + value;\n var units = self.ctx.settings.units ? self.ctx.settings.units : self.ctx.units;\n if (units) {\n content += ' ' + units;\n } \n return content;\n }\n }\n }\n };\n\n if (self.ctx.settings.legend) {\n options.legend.display = self.ctx.settings.legend.display !== false;\n options.legend.labels.fontColor = self.ctx.settings.legend.labelsFontColor || '#666';\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'doughnut',\n data: pieData,\n options: options\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"borderWidth\": {\n \"title\": \"Border width\",\n \"type\": \"number\",\n \"default\": 5\n },\n \"borderColor\": {\n \"title\": \"Border color\",\n \"type\": \"string\",\n \"default\": \"#fff\"\n },\n \"legend\": {\n \"title\": \"Legend settings\",\n \"type\": \"object\",\n \"properties\": {\n \"display\": {\n \"title\": \"Display legend\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"labelsFontColor\": {\n \"title\": \"Labels font color\",\n \"type\": \"string\",\n \"default\": \"#666\"\n }\n }\n }\n },\n \"required\": []\n },\n \"form\": [\n \"borderWidth\", \n {\n \"key\": \"borderColor\",\n \"type\": \"color\"\n }, \n {\n \"key\": \"legend\",\n \"items\": [\n \"legend.display\",\n {\n \"key\": \"legend.labelsFontColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#26a69a\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#f57c00\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#afb42b\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#673ab7\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"borderWidth\":5,\"borderColor\":\"#fff\",\"legend\":{\"display\":true,\"labelsFontColor\":\"#666666\"}},\"title\":\"Doughnut - Chart.js\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}" @@ -91,7 +91,7 @@ ], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n \n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n pieData.labels.push(dataKey.label);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n var borderColor = tinycolor(dataKey.color).darken();\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push('#fff');\n dataset.borderWidth.push(5);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'pie',\n data: pieData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n }); \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = tvPair[1];\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n", + "controllerScript": "self.onInit = function() {\n \n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n pieData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n var borderColor = tinycolor(dataKey.color).darken();\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push('#fff');\n dataset.borderWidth.push(5);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'pie',\n data: pieData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n }); \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Pie - Chart.js\"}" @@ -111,7 +111,7 @@ ], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n pieData.datasets.push(dataset);\n \n for (var i = 0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n pieData.labels.push(dataKey.label);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n var borderColor = tinycolor(dataKey.color).darken();\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push('#fff');\n dataset.borderWidth.push(5);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'polarArea',\n data: pieData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = tvPair[1];\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n \n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n if (self.ctx.height >= 70) {\n try {\n self.ctx.chart.resize();\n } catch (e) {}\n }\n}\n", + "controllerScript": "self.onInit = function() {\n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n pieData.datasets.push(dataset);\n \n for (var i = 0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n pieData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n var borderColor = tinycolor(dataKey.color).darken();\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push('#fff');\n dataset.borderWidth.push(5);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'polarArea',\n data: pieData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n \n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n if (self.ctx.height >= 70) {\n try {\n self.ctx.chart.resize();\n } catch (e) {}\n }\n}\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fifth\",\"color\":\"#607d8b\",\"settings\":{},\"_hash\":0.2074391823443591,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Polar Area - Chart.js\"}" @@ -131,7 +131,7 @@ ], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n var barData = {\n labels: [],\n datasets: []\n };\n\n var backgroundColor = tinycolor(self.ctx.data[0].dataKey.color);\n backgroundColor.setAlpha(0.2);\n var borderColor = tinycolor(self.ctx.data[0].dataKey.color);\n borderColor.setAlpha(1);\n var dataset = {\n label: self.ctx.datasources[0].name,\n data: [],\n backgroundColor: backgroundColor.toRgbString(),\n borderColor: borderColor.toRgbString(),\n pointBackgroundColor: borderColor.toRgbString(),\n pointBorderColor: borderColor.darken().toRgbString(),\n borderWidth: 1\n }\n \n barData.datasets.push(dataset);\n \n for (var i = 0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n barData.labels.push(dataKey.label);\n dataset.data.push(0);\n }\n\n var ctx = $('#radarChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'radar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = tvPair[1];\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n } \n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n if (self.ctx.height >= 70) {\n self.ctx.chart.resize();\n }\n}\n", + "controllerScript": "self.onInit = function() {\n var barData = {\n labels: [],\n datasets: []\n };\n\n var backgroundColor = tinycolor(self.ctx.data[0].dataKey.color);\n backgroundColor.setAlpha(0.2);\n var borderColor = tinycolor(self.ctx.data[0].dataKey.color);\n borderColor.setAlpha(1);\n var dataset = {\n label: self.ctx.datasources[0].name,\n data: [],\n backgroundColor: backgroundColor.toRgbString(),\n borderColor: borderColor.toRgbString(),\n pointBackgroundColor: borderColor.toRgbString(),\n pointBorderColor: borderColor.darken().toRgbString(),\n borderWidth: 1\n }\n \n barData.datasets.push(dataset);\n \n for (var i = 0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n barData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n }\n\n var ctx = $('#radarChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'radar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n } \n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n if (self.ctx.height >= 70) {\n self.ctx.chart.resize();\n }\n}\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Radar - Chart.js\"}" diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts index 03f6d1b975..7b9ab5117a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts @@ -20,7 +20,7 @@ import { createLabelFromDatasource, deepClone, insertVariable, - isDefined, + isDefined, isDefinedAndNotNull, isEqual, isNumber, isUndefined @@ -993,7 +993,7 @@ export class TbFlot { private seriesInfoDivFromInfo(seriesHoverInfo: TbFlotSeriesHoverInfo, seriesIndex: number): string { const units = seriesHoverInfo.units && seriesHoverInfo.units.length ? seriesHoverInfo.units : this.trackUnits; - const decimals = isDefined(seriesHoverInfo.decimals) ? seriesHoverInfo.decimals : this.trackDecimals; + const decimals = isDefinedAndNotNull(seriesHoverInfo.decimals) ? seriesHoverInfo.decimals : this.trackDecimals; const divElement = this.seriesInfoDiv(seriesHoverInfo.label, seriesHoverInfo.color, seriesHoverInfo.value, units, decimals, seriesHoverInfo.index === seriesIndex, null, seriesHoverInfo.tooltipValueFormatFunction); return divElement.prop('outerHTML'); @@ -1020,7 +1020,7 @@ export class TbFlot { private formatPieTooltip(item: TbFlotPlotItem): string { const units = item.series.dataKey.units && item.series.dataKey.units.length ? item.series.dataKey.units : this.trackUnits; - const decimals = isDefined(item.series.dataKey.decimals) ? item.series.dataKey.decimals : this.trackDecimals; + const decimals = isDefinedAndNotNull(item.series.dataKey.decimals) ? item.series.dataKey.decimals : this.trackDecimals; const divElement = this.seriesInfoDiv(item.series.dataKey.label, item.series.dataKey.color, item.datapoint[1][0][1], units, decimals, true, item.series.percent, item.series.dataKey.tooltipValueFormatFunction); return divElement.prop('outerHTML'); From 1a718593ecd6919f164b40830d3783b4415aab27 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 12 Oct 2020 16:19:36 +0300 Subject: [PATCH 180/463] Refactoring --- .../device/DeviceProvisionServiceImpl.java | 72 +++++++++---------- .../transport/DefaultTransportApiService.java | 4 +- .../dao/device/DeviceProvisionService.java | 2 +- 3 files changed, 35 insertions(+), 43 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index ab333442c4..d372b878f7 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -65,6 +65,7 @@ import org.thingsboard.server.service.state.DeviceStateService; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.concurrent.ExecutionException; import java.util.concurrent.locks.ReentrantLock; @@ -109,7 +110,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } @Override - public ListenableFuture provisionDevice(ProvisionRequest provisionRequest) { + public ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) { String provisionRequestKey = provisionRequest.getCredentials().getProvisionDeviceKey(); String provisionRequestSecret = provisionRequest.getCredentials().getProvisionDeviceSecret(); @@ -152,35 +153,24 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); } - private ListenableFuture processProvision(Device device, ProvisionRequest provisionRequest) { - ListenableFuture> provisionStateFuture = attributesService.find(device.getTenantId(), device.getId(), - DataConstants.SERVER_SCOPE, DEVICE_PROVISION_STATE); - ListenableFuture provisionedFuture = Futures.transformAsync(provisionStateFuture, optionalAtr -> { - if (optionalAtr != null && optionalAtr.isPresent()) { - String state = optionalAtr.get().getValueAsString(); - if (state.equals(PROVISIONED_STATE)) { - return Futures.immediateFuture(true); - } else { - log.error("[{}][{}] Unknown provision state: {}!", device.getName(), DEVICE_PROVISION_STATE, state); - throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); - } - } - return Futures.transform(saveProvisionStateAttribute(device), input -> false, MoreExecutors.directExecutor()); - }, MoreExecutors.directExecutor()); - if (provisionedFuture.isCancelled()) { - throw new RuntimeException("Unknown provision state!"); - } - return Futures.transform(provisionedFuture, provisioned -> { - if (provisioned) { + private ProvisionResponse processProvision(Device device, ProvisionRequest provisionRequest) { + try { + Optional provisionState = attributesService.find(device.getTenantId(), device.getId(), + DataConstants.SERVER_SCOPE, DEVICE_PROVISION_STATE).get(); + if (provisionState != null && provisionState.isPresent() && !provisionState.get().getValueAsString().equals(PROVISIONED_STATE)) { notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); + } else { + saveProvisionStateAttribute(device).get(); + notify(device, provisionRequest, DataConstants.PROVISION_SUCCESS, true); } - notify(device, provisionRequest, DataConstants.PROVISION_SUCCESS, true); - return new ProvisionResponse(deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()), ProvisionResponseStatus.SUCCESS); - }, MoreExecutors.directExecutor()); + } catch (InterruptedException | ExecutionException e) { + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); + } + return new ProvisionResponse(deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()), ProvisionResponseStatus.SUCCESS); } - private ListenableFuture createDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { + private ProvisionResponse createDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { deviceCreationLock.lock(); try { return processCreateDevice(provisionRequest, profile); @@ -194,22 +184,24 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { logAction(device.getTenantId(), device.getCustomerId(), device, success, provisionRequest); } - private ListenableFuture processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { + private ProvisionResponse processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName()); - if (device == null) { - Device savedDevice = deviceService.saveDevice(provisionRequest, profile); - - deviceStateService.onDeviceAdded(savedDevice); - pushDeviceCreatedEventToRuleEngine(savedDevice); - notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true); - - return Futures.transform(saveProvisionStateAttribute(savedDevice), input -> - new ProvisionResponse( - getDeviceCredentials(savedDevice), - ProvisionResponseStatus.SUCCESS), MoreExecutors.directExecutor()); - } else { - log.warn("[{}] The device is already provisioned!", device.getName()); - notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); + try { + if (device == null) { + Device savedDevice = deviceService.saveDevice(provisionRequest, profile); + + deviceStateService.onDeviceAdded(savedDevice); + saveProvisionStateAttribute(savedDevice).get(); + pushDeviceCreatedEventToRuleEngine(savedDevice); + notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true); + + return new ProvisionResponse(getDeviceCredentials(savedDevice), ProvisionResponseStatus.SUCCESS); + } else { + log.warn("[{}] The device is already provisioned!", device.getName()); + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); + } + } catch (InterruptedException | ExecutionException e) { throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 7791ec67a7..7fa4630359 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -278,7 +278,7 @@ public class DefaultTransportApiService implements TransportApiService { private ListenableFuture handle(ProvisionDeviceRequestMsg requestMsg) { ListenableFuture provisionResponseFuture = null; try { - provisionResponseFuture = deviceProvisionService.provisionDevice( + provisionResponseFuture = Futures.immediateFuture(deviceProvisionService.provisionDevice( new ProvisionRequest( requestMsg.getDeviceName(), requestMsg.getCredentialsType() != null ? DeviceCredentialsType.valueOf(requestMsg.getCredentialsType().name()) : null, @@ -289,7 +289,7 @@ public class DefaultTransportApiService implements TransportApiService { requestMsg.getCredentialsDataProto().getValidateDeviceX509CertRequestMsg().getHash()), new ProvisionDeviceProfileCredentials( requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceKey(), - requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret()))); + requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret())))); } catch (ProvisionFailedException e) { return Futures.immediateFuture(getTransportApiResponseMsg( TransportProtos.DeviceCredentialsProto.getDefaultInstance(), diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java index 2a0d651801..5d038b2d31 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java @@ -24,5 +24,5 @@ import org.thingsboard.server.dao.device.provision.ProvisionResponse; public interface DeviceProvisionService { - ListenableFuture provisionDevice(ProvisionRequest provisionRequest) throws ProvisionFailedException; + ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) throws ProvisionFailedException; } From 8d4718320b4b95ab1c0c03750b01a04e3cf78399 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 12 Oct 2020 16:21:05 +0300 Subject: [PATCH 181/463] init commit --- .../controller/DeviceProfileController.java | 25 +++ .../mqtt/AbstractMqttIntegrationTest.java | 10 +- ...AttributesRequestProtoIntegrationTest.java | 3 + ...AttributesUpdatesProtoIntegrationTest.java | 3 + ...MqttServerSideRpcProtoIntegrationTest.java | 4 + ...actMqttAttributesProtoIntegrationTest.java | 3 + ...actMqttTimeseriesProtoIntegrationTest.java | 5 + common/data/pom.xml | 12 ++ .../DeviceProfileTransportConfiguration.java | 2 +- ...ttDeviceProfileTransportConfiguration.java | 31 +++- ...onDeviceProfileTransportConfiguration.java | 42 +++++ ...toDeviceProfileTransportConfiguration.java | 152 ++++++++++++++++++ ...qttTransportConfigurationDeserializer.java | 50 ++++++ .../transport/mqtt/MqttTransportHandler.java | 14 +- .../mqtt/adaptors/ProtoMqttAdaptor.java | 20 ++- .../mqtt/session/DeviceSessionCtx.java | 22 ++- pom.xml | 12 ++ ...ile-transport-configuration.component.html | 46 ++++-- ...ofile-transport-configuration.component.ts | 24 ++- .../assets/locale/locale.constant-en_US.json | 4 + 20 files changed, 450 insertions(+), 34 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index b474a0c0f1..10a63d53d7 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -29,7 +29,12 @@ import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.page.PageData; @@ -92,6 +97,16 @@ public class DeviceProfileController extends BaseController { checkEntity(deviceProfile.getId(), deviceProfile, Resource.DEVICE_PROFILE); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + + if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { + if (transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration) { + MqttProtoDeviceProfileTransportConfiguration protoTransportConfiguration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; + if (protoTransportConfiguration.getTransportPayloadType().equals(TransportPayloadType.PROTOBUF)) + checkProtoSchemas(protoTransportConfiguration); + } + } + DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); deviceProfileCache.put(savedDeviceProfile); @@ -200,4 +215,14 @@ public class DeviceProfileController extends BaseController { throw handleException(e); } } + + private void checkProtoSchemas(MqttProtoDeviceProfileTransportConfiguration mqttTransportConfiguration) throws ThingsboardException { + try { + mqttTransportConfiguration.validateTransportProtoSchema(mqttTransportConfiguration.getDeviceAttributesProtoSchema(), "attributes proto schema"); + mqttTransportConfiguration.validateTransportProtoSchema(mqttTransportConfiguration.getDeviceTelemetryProtoSchema(), "telemetry proto schema"); + } catch (Exception exception) { + throw new ThingsboardException(exception.getMessage(), ThingsboardErrorCode.GENERAL); + } + } + } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java index a25b6334e5..6c7572a425 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java @@ -34,6 +34,8 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttJsonDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.controller.AbstractControllerTest; @@ -190,8 +192,12 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest deviceProfile.setDescription(transportPayloadType.name() + " Test"); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); - MqttDeviceProfileTransportConfiguration transportConfiguration = new MqttDeviceProfileTransportConfiguration(); - transportConfiguration.setTransportPayloadType(transportPayloadType); + MqttDeviceProfileTransportConfiguration transportConfiguration; + if (TransportPayloadType.JSON.equals(transportPayloadType)) { + transportConfiguration = new MqttJsonDeviceProfileTransportConfiguration(); + } else { + transportConfiguration = new MqttProtoDeviceProfileTransportConfiguration(); + } if (!StringUtils.isEmpty(telemetryTopic)) { transportConfiguration.setDeviceTelemetryTopic(telemetryTopic); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java index 96cc88fa2f..80a469ed12 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java @@ -23,6 +23,7 @@ import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.TransportPayloadType; @@ -55,12 +56,14 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends } @Test + @Ignore public void testRequestAttributesValuesFromTheServer() throws Exception { processTestRequestAttributesValuesFromTheServer(); } @Test + @Ignore public void testRequestAttributesValuesFromTheServerGateway() throws Exception { processTestGatewayRequestAttributesValuesFromTheServer(); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java index faf8e1ce4d..e27fdda95c 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java @@ -19,6 +19,7 @@ import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; @@ -47,11 +48,13 @@ public abstract class AbstractMqttAttributesUpdatesProtoIntegrationTest extends } @Test + @Ignore public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { processTestSubscribeToAttributesUpdates(); } @Test + @Ignore public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { processGatewayTestSubscribeToAttributesUpdates(); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java index 759a5da912..41d5d380f4 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java @@ -46,21 +46,25 @@ public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends Abst } @Test + @Ignore public void testServerMqttOneWayRpc() throws Exception { processOneWayRpcTest(); } @Test + @Ignore public void testServerMqttTwoWayRpc() throws Exception { processTwoWayRpcTest(); } @Test + @Ignore public void testGatewayServerMqttOneWayRpc() throws Exception { processOneWayRpcTestGateway("Gateway Device OneWay RPC Proto"); } @Test + @Ignore public void testGatewayServerMqttTwoWayRpc() throws Exception { processTwoWayRpcTestGateway("Gateway Device TwoWay RPC Proto"); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java index e9adf19359..0e58ffeedb 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java @@ -18,6 +18,7 @@ package org.thingsboard.server.mqtt.telemetry.attributes; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.gen.transport.TransportApiProtos; @@ -46,6 +47,7 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac } @Test + @Ignore public void testPushMqttAttributes() throws Exception { List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); TransportProtos.PostAttributeMsg msg = getPostAttributeMsg(expectedKeys); @@ -53,6 +55,7 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac } @Test + @Ignore public void testPushMqttAttributesGateway() throws Exception { TransportApiProtos.GatewayAttributesMsg.Builder gatewayAttributesMsgProtoBuilder = TransportApiProtos.GatewayAttributesMsg.newBuilder(); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java index de1c74109c..0a7cb223c3 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.TransportPayloadType; @@ -48,6 +49,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac } @Test + @Ignore public void testPushMqttTelemetry() throws Exception { List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); TransportProtos.TsKvListProto tsKvListProto = getTsKvListProto(expectedKeys, 0); @@ -55,6 +57,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac } @Test + @Ignore public void testPushMqttTelemetryWithTs() throws Exception { List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); TransportProtos.TsKvListProto tsKvListProto = getTsKvListProto(expectedKeys, 10000); @@ -62,6 +65,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac } @Test + @Ignore public void testPushMqttTelemetryGateway() throws Exception { TransportApiProtos.GatewayTelemetryMsg.Builder gatewayTelemetryMsgProtoBuilder = TransportApiProtos.GatewayTelemetryMsg.newBuilder(); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); @@ -75,6 +79,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac } @Test + @Ignore public void testGatewayConnect() throws Exception { String deviceName = "Device A"; TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName); diff --git a/common/data/pom.xml b/common/data/pom.xml index eb7a3b226c..00e53ff2bb 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -71,6 +71,18 @@ java-driver-core test + + org.springframework + spring-web + + + com.squareup.wire + wire-schema + + + com.github.os72 + protobuf-dynamic + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java index 34854958d1..fb337b24f4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java @@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; property = "type") @JsonSubTypes({ @JsonSubTypes.Type(value = DefaultDeviceProfileTransportConfiguration.class, name = "DEFAULT"), - @JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"), + @JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"), @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M")}) public interface DeviceProfileTransportConfiguration { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java index d88ac24cbb..8699a793ed 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java @@ -15,17 +15,40 @@ */ package org.thingsboard.server.common.data.device.profile; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.squareup.wire.schema.Location; +import com.squareup.wire.schema.internal.parser.MessageElement; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; +import com.squareup.wire.schema.internal.parser.ProtoParser; +import com.squareup.wire.schema.internal.parser.TypeElement; import lombok.Data; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.DeviceTransportType; +import org.springframework.util.CollectionUtils; +import java.util.List; + +@Slf4j @Data -public class MqttDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "transportPayloadType") +@JsonSubTypes({ + @JsonSubTypes.Type(value = MqttJsonDeviceProfileTransportConfiguration.class, name = "JSON"), + @JsonSubTypes.Type(value = MqttProtoDeviceProfileTransportConfiguration.class, name = "PROTOBUF")}) +@JsonDeserialize(using = MqttTransportConfigurationDeserializer.class) +public abstract class MqttDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { - private TransportPayloadType transportPayloadType = TransportPayloadType.JSON; + protected String deviceTelemetryTopic = MqttTopics.DEVICE_TELEMETRY_TOPIC; + protected String deviceAttributesTopic = MqttTopics.DEVICE_ATTRIBUTES_TOPIC; - private String deviceTelemetryTopic = MqttTopics.DEVICE_TELEMETRY_TOPIC; - private String deviceAttributesTopic = MqttTopics.DEVICE_ATTRIBUTES_TOPIC; + public abstract TransportPayloadType getTransportPayloadType(); @Override public DeviceTransportType getType() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java new file mode 100644 index 0000000000..83a17e93ef --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile; + +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.TransportPayloadType; + +@Slf4j +@EqualsAndHashCode(callSuper = true) +@Data +@JsonDeserialize(using = JsonDeserializer.None.class) +public class MqttJsonDeviceProfileTransportConfiguration extends MqttDeviceProfileTransportConfiguration{ + + @Override + public DeviceTransportType getType() { + return super.getType(); + } + + @Override + public TransportPayloadType getTransportPayloadType() { + return TransportPayloadType.JSON; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java new file mode 100644 index 0000000000..f4185bd2e2 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java @@ -0,0 +1,152 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.github.os72.protobuf.dynamic.EnumDefinition; +import com.github.os72.protobuf.dynamic.MessageDefinition; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.squareup.wire.schema.Location; +import com.squareup.wire.schema.internal.parser.EnumConstantElement; +import com.squareup.wire.schema.internal.parser.EnumElement; +import com.squareup.wire.schema.internal.parser.FieldElement; +import com.squareup.wire.schema.internal.parser.MessageElement; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; +import com.squareup.wire.schema.internal.parser.ProtoParser; +import com.squareup.wire.schema.internal.parser.TypeElement; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.TransportPayloadType; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Slf4j +@EqualsAndHashCode(callSuper = true) +@Data +@JsonDeserialize(using = JsonDeserializer.None.class) +public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProfileTransportConfiguration { + + public static final Location LOCATION = new Location("", "", -1, -1); + public static final String ATTRIBUTES_PROTO_SCHEMA = "attributes proto schema"; + public static final String TELEMETRY_PROTO_SCHEMA = "telemetry proto schema"; + + private String deviceTelemetryProtoSchema; + private String deviceAttributesProtoSchema; + + @JsonIgnore + private Descriptors.Descriptor telemetryMsgDescriptor; + + @JsonIgnore + private Descriptors.Descriptor attributesMsgDescriptor; + + @Override + public DeviceTransportType getType() { + return super.getType(); + } + + @Override + public TransportPayloadType getTransportPayloadType() { + return TransportPayloadType.PROTOBUF; + } + + public void validateTransportProtoSchema(String schema, String schemaName) throws IllegalArgumentException { + ProtoParser schemaParser = new ProtoParser(LOCATION, schema.toCharArray()); + ProtoFileElement protoFileElement; + try { + protoFileElement = schemaParser.readProtoFile(); + } catch (Exception e) { + throw new IllegalArgumentException("Failed to parse: " + schemaName + " due to: " + e.getMessage()); + } + List types = protoFileElement.getTypes(); + if (!CollectionUtils.isEmpty(types)) { + if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) { + throw new IllegalArgumentException("Invalid " + schemaName + " provided! At least one Message definition should exists!"); + } + } else { + throw new IllegalArgumentException("Invalid " + schemaName + " provided!"); + } + } + + public Descriptors.Descriptor getDynamicMessageDescriptor(String protoSchema, String schemaName) { + ProtoFileElement protoFileElement = getTransportProtoSchema(protoSchema); + DynamicSchema.Builder schemaBuilder = DynamicSchema.newBuilder(); + schemaBuilder.setName(schemaName); + schemaBuilder.setPackage(!StringUtils.isEmpty(protoFileElement.getPackageName()) ? + protoFileElement.getPackageName() : schemaName.toLowerCase()); + + List types = protoFileElement.getTypes(); + + List enumTypes = types.stream() + .filter(typeElement -> typeElement instanceof EnumElement) + .map(typeElement -> (EnumElement) typeElement) + .collect(Collectors.toList()); + + List messageTypes = types.stream() + .filter(typeElement -> typeElement instanceof MessageElement) + .map(typeElement -> (MessageElement) typeElement) + .collect(Collectors.toList()); + + if (!CollectionUtils.isEmpty(enumTypes)) { + enumTypes.forEach(enumElement -> { + List enumElementTypeConstants = enumElement.getConstants(); + EnumDefinition.Builder enumDefinitionBuilder = EnumDefinition.newBuilder(enumElement.getName()); + if (!CollectionUtils.isEmpty(enumElementTypeConstants)) { + enumElementTypeConstants.forEach(constantElement -> enumDefinitionBuilder.addValue(constantElement.getName(), constantElement.getTag())); + } + EnumDefinition enumDefinition = enumDefinitionBuilder.build(); + schemaBuilder.addEnumDefinition(enumDefinition); + }); + } + + if (!CollectionUtils.isEmpty(messageTypes)) { + messageTypes.forEach(messageElement -> { + List messageElementFields = messageElement.getFields(); + MessageDefinition.Builder messageDefinitionBuilder = MessageDefinition.newBuilder(messageElement.getName()); + if (!CollectionUtils.isEmpty(messageElementFields)) { + messageElementFields.forEach(fieldElement -> messageDefinitionBuilder.addField(fieldElement.getType(), fieldElement.getName(), fieldElement.getTag(), fieldElement.getDefaultValue())); + } + MessageDefinition messageDefinition = messageDefinitionBuilder.build(); + schemaBuilder.addMessageDefinition(messageDefinition); + }); + MessageElement lastMsg = messageTypes.stream().reduce((previous, last) -> last).get(); + try { + DynamicSchema dynamicSchema = schemaBuilder.build(); + DynamicMessage.Builder builder = dynamicSchema.newMessageBuilder(lastMsg.getName()); + return builder.getDescriptorForType(); + } catch (Descriptors.DescriptorValidationException e) { + throw new RuntimeException(e); + } + } else { + throw new RuntimeException("Failed to get Message Descriptor! Message types is empty for " + schemaName + " schema!"); + } + } + + + private ProtoFileElement getTransportProtoSchema(String protoSchema) { + return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java new file mode 100644 index 0000000000..0089420ed2 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java @@ -0,0 +1,50 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; + +@Slf4j +public class MqttTransportConfigurationDeserializer extends StdDeserializer { + + public MqttTransportConfigurationDeserializer() { + super(MqttDeviceProfileTransportConfiguration.class); + } + + @Override + public MqttDeviceProfileTransportConfiguration deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + try { + JsonNode jsonNode = jsonParser.readValueAsTree(); + if (jsonNode.has("deviceTelemetryProtoSchema") || jsonNode.has("deviceAttributesProtoSchema")) { + return jsonParser.getCodec().treeToValue(jsonNode, MqttProtoDeviceProfileTransportConfiguration.class); + } else { + return jsonParser.getCodec().treeToValue(jsonNode, MqttJsonDeviceProfileTransportConfiguration.class); + } + } catch (IOException e) { + log.trace("Failed to deserialize JSON content into equivalent tree model during creating {}!", MqttDeviceProfileTransportConfiguration.class.getSimpleName(), e); + throw new RuntimeException("Failed to deserialize JSON content into equivalent tree model during creating " + MqttDeviceProfileTransportConfiguration.class.getSimpleName() + "!", e); + } + } + +} \ No newline at end of file diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index cf353bdd72..16522eaa26 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -208,24 +208,24 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private void processDevicePublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, String topicName, int msgId) { try { - MqttTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor(); + MqttTransportAdaptor adaptor = deviceSessionCtx.getPayloadAdaptor(); if (deviceSessionCtx.isDeviceTelemetryTopic(topicName)) { - TransportProtos.PostTelemetryMsg postTelemetryMsg = payloadAdaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg); + TransportProtos.PostTelemetryMsg postTelemetryMsg = adaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg)); } else if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) { - TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg); + TransportProtos.PostAttributeMsg postAttributeMsg = adaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg)); } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) { - TransportProtos.GetAttributeRequestMsg getAttributeMsg = payloadAdaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg); + TransportProtos.GetAttributeRequestMsg getAttributeMsg = adaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg)); } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_RESPONSE_TOPIC)) { - TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = payloadAdaptor.convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg); + TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = adaptor.convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(ctx, msgId, rpcResponseMsg)); } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_REQUESTS_TOPIC)) { - TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = payloadAdaptor.convertToServerRpcRequest(deviceSessionCtx, mqttMsg); + TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = adaptor.convertToServerRpcRequest(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequestMsg, getPubAckCallback(ctx, msgId, rpcRequestMsg)); } else if (topicName.equals(MqttTopics.DEVICE_CLAIM_TOPIC)) { - TransportProtos.ClaimDeviceMsg claimDeviceMsg = payloadAdaptor.convertToClaimDevice(deviceSessionCtx, mqttMsg); + TransportProtos.ClaimDeviceMsg claimDeviceMsg = adaptor.convertToClaimDevice(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(ctx, msgId, claimDeviceMsg)); } else { transportService.reportActivity(deviceSessionCtx.getSessionInfo()); diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java index f66daf2289..15dff19532 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java @@ -15,7 +15,11 @@ */ package org.thingsboard.server.transport.mqtt.adaptors; +import com.google.gson.JsonParser; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.UnpooledByteBufAllocator; @@ -29,9 +33,11 @@ import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.adaptor.ProtoConverter; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext; import java.util.Optional; @@ -44,19 +50,27 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { @Override public TransportProtos.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { + DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx; byte[] bytes = toBytes(inbound.payload()); try { - return ProtoConverter.convertToTelemetryProto(bytes); - } catch (InvalidProtocolBufferException | IllegalArgumentException e) { + Descriptors.Descriptor telemetryDynamicMsgDescriptor = deviceSessionCtx.getTelemetryDynamicMsgDescriptor(); + DynamicMessage dynamicMessage = DynamicMessage.parseFrom(telemetryDynamicMsgDescriptor, bytes); + String stringMsg = JsonFormat.printer().includingDefaultValueFields().print(dynamicMessage); + return JsonConverter.convertToTelemetryProto(new JsonParser().parse(stringMsg)); + } catch (Exception e) { throw new AdaptorException(e); } } @Override public TransportProtos.PostAttributeMsg convertToPostAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { + DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx; byte[] bytes = toBytes(inbound.payload()); try { - return ProtoConverter.validatePostAttributeMsg(bytes); + Descriptors.Descriptor attributesDynamicMessage = deviceSessionCtx.getAttributesDynamicMessageDescriptor(); + DynamicMessage dynamicMessage = DynamicMessage.parseFrom(attributesDynamicMessage, bytes); + String stringMsg = JsonFormat.printer().includingDefaultValueFields().print(dynamicMessage); + return JsonConverter.convertToAttributesProto(new JsonParser().parse(stringMsg)); } catch (InvalidProtocolBufferException | IllegalArgumentException e) { throw new AdaptorException(e); } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index ba701ba56c..81fefde514 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.mqtt.session; +import com.google.protobuf.Descriptors; import io.netty.channel.ChannelHandlerContext; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -23,6 +24,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; import org.thingsboard.server.transport.mqtt.MqttTransportContext; import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.util.MqttTopicFilter; @@ -49,6 +51,8 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { private volatile MqttTopicFilter telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); private volatile MqttTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); private volatile TransportPayloadType payloadType = TransportPayloadType.JSON; + private volatile Descriptors.Descriptor attributesDynamicMessageDescriptor; + private volatile Descriptors.Descriptor telemetryDynamicMessageDescriptor; public DeviceSessionCtx(UUID sessionId, ConcurrentMap mqttQoSMap, MqttTransportContext context) { super(sessionId, mqttQoSMap); @@ -63,7 +67,9 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { return msgIdSeq.incrementAndGet(); } - public boolean isDeviceTelemetryTopic(String topicName) { return telemetryTopicFilter.filter(topicName); } + public boolean isDeviceTelemetryTopic(String topicName) { + return telemetryTopicFilter.filter(topicName); + } public boolean isDeviceAttributesTopic(String topicName) { return attributesTopicFilter.filter(topicName); @@ -77,6 +83,14 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { return payloadType.equals(TransportPayloadType.JSON); } + public Descriptors.Descriptor getTelemetryDynamicMsgDescriptor() { + return telemetryDynamicMessageDescriptor; + } + + public Descriptors.Descriptor getAttributesDynamicMessageDescriptor() { + return attributesDynamicMessageDescriptor; + } + @Override public void setDeviceProfile(DeviceProfile deviceProfile) { super.setDeviceProfile(deviceProfile); @@ -98,10 +112,14 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { payloadType = mqttConfig.getTransportPayloadType(); telemetryTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceTelemetryTopic()); attributesTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesTopic()); + if (payloadType.equals(TransportPayloadType.PROTOBUF) && mqttConfig instanceof MqttProtoDeviceProfileTransportConfiguration) { + MqttProtoDeviceProfileTransportConfiguration protoMqttConfig = (MqttProtoDeviceProfileTransportConfiguration) mqttConfig; + telemetryDynamicMessageDescriptor = protoMqttConfig.getDynamicMessageDescriptor(protoMqttConfig.getDeviceTelemetryProtoSchema(), "telemetrySchema"); + attributesDynamicMessageDescriptor = protoMqttConfig.getDynamicMessageDescriptor(protoMqttConfig.getDeviceAttributesProtoSchema(), "attributesSchema"); + } } else { telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); } } - } diff --git a/pom.xml b/pom.xml index cc5bbce51b..ef98bd3b79 100755 --- a/pom.xml +++ b/pom.xml @@ -106,6 +106,8 @@ 3.2.2 1.5.0 1.5.2 + 1.0.1 + 3.4.0 @@ -1369,6 +1371,16 @@ micrometer-registry-prometheus ${micrometer.version} + + com.github.os72 + protobuf-dynamic + ${protobuf-dynamic.version} + + + com.squareup.wire + wire-schema + ${wire-schema.version} + diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html index 00ff4760ab..c83066bbae 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html @@ -20,17 +20,6 @@
device-profile.mqtt-device-topic-filters
- - device-profile.mqtt-device-payload-type - - - {{mqttTransportPayloadTypeTranslations.get(type) | translate}} - - - - {{ 'device-profile.mqtt-payload-type-required' | translate }} - -
device-profile.telemetry-topic-filter @@ -68,5 +57,40 @@
+
+ device-profile.mqtt-device-payload-type +
+ + + + {{mqttTransportPayloadTypeTranslations.get(type) | translate}} + + + + {{ 'device-profile.mqtt-payload-type-required' | translate }} + + +
+ + device-profile.telemetry-proto-schema + + + {{ 'device-profile.telemetry-proto-schema-required' | translate}} + + + + device-profile.attributes-proto-schema + + + {{ 'device-profile.attributes-proto-schema-required' | translate}} + + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index 18dc1b2bf4..c654c24f75 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -28,12 +28,13 @@ import { Store } from '@ngrx/store'; import { AppState } from '@app/core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { - MqttTransportPayloadType, DeviceProfileTransportConfiguration, DeviceTransportType, - MqttDeviceProfileTransportConfiguration, mqttTransportPayloadTypeTranslationMap + MqttDeviceProfileTransportConfiguration, + MqttTransportPayloadType, + mqttTransportPayloadTypeTranslationMap } from '@shared/models/device.models'; -import { isDefinedAndNotNull } from '@core/utils'; +import {isDefinedAndNotNull} from '@core/utils'; @Component({ selector: 'tb-mqtt-device-profile-transport-configuration', @@ -86,7 +87,9 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control configuration: this.fb.group({ deviceAttributesTopic: [null, [Validators.required, this.validationMQTTTopic()]], deviceTelemetryTopic: [null, [Validators.required, this.validationMQTTTopic()]], - transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required] + transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required], + // deviceTelemetryProtoSchema: [null, Validators.required], + // deviceAttributesProtoSchema: [null, Validators.required] }) }); this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { @@ -103,6 +106,11 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control } } + protoPayloadType(): boolean { + let configuration = this.mqttDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration; + return configuration.transportPayloadType === MqttTransportPayloadType.PROTOBUF; + } + writeValue(value: MqttDeviceProfileTransportConfiguration | null): void { if (isDefinedAndNotNull(value)) { this.mqttDeviceProfileTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); @@ -114,6 +122,14 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control if (this.mqttDeviceProfileTransportConfigurationFormGroup.valid) { configuration = this.mqttDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration; configuration.type = DeviceTransportType.MQTT; + let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; + if (configuration.transportPayloadType === MqttTransportPayloadType.PROTOBUF) { + configurationFormGroup.addControl('deviceTelemetryProtoSchema', this.fb.control(null, Validators.required)); + configurationFormGroup.addControl('deviceAttributesProtoSchema', this.fb.control(null, Validators.required)); + } else { + configurationFormGroup.removeControl('deviceTelemetryProtoSchema'); + configurationFormGroup.removeControl('deviceAttributesProtoSchema'); + } } this.propagateChange(configuration); } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 0fd0e1f7a2..3e3550f0d0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -807,6 +807,10 @@ "telemetry-topic-filter-required": "Telemetry topic filter is required.", "attributes-topic-filter": "Attributes topic filter", "attributes-topic-filter-required": "Attributes topic filter is required.", + "telemetry-proto-schema": "Telemetry proto schema", + "telemetry-proto-schema-required": "Telemetry proto schema is required.", + "attributes-proto-schema": "Attributes proto schema", + "attributes-proto-schema-required": "Attributes proto schema is required.", "rpc-response-topic-filter": "RPC response topic filter", "rpc-response-topic-filter-required": "RPC response topic filter is required.", "not-valid-pattern-topic-filter": "Not valid pattern topic filter", From 155b967b478f0d3462d06ba95a745255de2bf4be Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 12 Oct 2020 17:29:27 +0300 Subject: [PATCH 182/463] Improve device profile alarm rules UI --- .../home/components/home-components.module.ts | 19 +- ...alarm-rule-condition-dialog.component.html | 124 ++++++++++++ ...alarm-rule-condition-dialog.component.scss | 20 ++ .../alarm-rule-condition-dialog.component.ts | 150 ++++++++++++++ .../alarm/alarm-rule-condition.component.html | 8 +- .../alarm/alarm-rule-condition.component.scss | 11 +- .../alarm/alarm-rule-condition.component.ts | 85 ++++++-- .../profile/alarm/alarm-rule.component.html | 118 +++-------- .../profile/alarm/alarm-rule.component.scss | 24 +++ .../profile/alarm/alarm-rule.component.ts | 93 +++------ ...l => alarm-schedule-dialog.component.html} | 15 +- ....ts => alarm-schedule-dialog.component.ts} | 34 ++-- .../alarm/alarm-schedule-info.component.html | 33 +++ .../alarm/alarm-schedule-info.component.scss | 34 ++++ .../alarm/alarm-schedule-info.component.ts | 137 +++++++++++++ .../alarm/alarm-schedule.component.html | 191 ++---------------- .../profile/alarm/alarm-schedule.component.ts | 83 ++++---- .../alarm/create-alarm-rules.component.scss | 1 + .../alarm/device-profile-alarm.component.html | 2 +- .../alarm/device-profile-alarm.component.ts | 1 + .../device-profile-alarms.component.html | 3 +- .../edit-alarm-details-dialog.component.html | 53 +++++ .../edit-alarm-details-dialog.component.ts | 80 ++++++++ .../device-profile-tabs.component.html | 28 +-- ui-ngx/src/app/shared/models/device.models.ts | 61 ++++++ .../app/shared/models/query/query.models.ts | 32 +-- .../assets/locale/locale.constant-en_US.json | 10 +- 27 files changed, 992 insertions(+), 458 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts rename ui-ngx/src/app/modules/home/components/profile/alarm/{alarm-rule-key-filters-dialog.component.html => alarm-schedule-dialog.component.html} (74%) rename ui-ngx/src/app/modules/home/components/profile/alarm/{alarm-rule-key-filters-dialog.component.ts => alarm-schedule-dialog.component.ts} (65%) create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.ts diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 9bbac4154d..15d33f709c 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -102,13 +102,16 @@ import { DeviceProfileAlarmComponent } from './profile/alarm/device-profile-alar import { CreateAlarmRulesComponent } from './profile/alarm/create-alarm-rules.component'; import { AlarmRuleComponent } from './profile/alarm/alarm-rule.component'; import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-condition.component'; -import { AlarmRuleKeyFiltersDialogComponent } from './profile/alarm/alarm-rule-key-filters-dialog.component'; import { FilterTextComponent } from './filter/filter-text.component'; import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component'; import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component'; import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component'; import { DeviceWizardDialogComponent } from './wizard/device-wizard-dialog.component'; import { DeviceCredentialsComponent } from './device/device-credentials.component'; +import { AlarmScheduleInfoComponent } from './profile/alarm/alarm-schedule-info.component'; +import { AlarmScheduleDialogComponent } from '@home/components/profile/alarm/alarm-schedule-dialog.component'; +import { EditAlarmDetailsDialogComponent } from './profile/alarm/edit-alarm-details-dialog.component'; +import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alarm/alarm-rule-condition-dialog.component'; @NgModule({ declarations: @@ -190,7 +193,7 @@ import { DeviceCredentialsComponent } from './device/device-credentials.componen DeviceProfileTransportConfigurationComponent, CreateAlarmRulesComponent, AlarmRuleComponent, - AlarmRuleKeyFiltersDialogComponent, + AlarmRuleConditionDialogComponent, AlarmRuleConditionComponent, DeviceProfileAlarmComponent, DeviceProfileAlarmsComponent, @@ -198,9 +201,12 @@ import { DeviceCredentialsComponent } from './device/device-credentials.componen DeviceProfileDialogComponent, AddDeviceProfileDialogComponent, RuleChainAutocompleteComponent, + AlarmScheduleInfoComponent, AlarmScheduleComponent, DeviceWizardDialogComponent, - DeviceCredentialsComponent + DeviceCredentialsComponent, + AlarmScheduleDialogComponent, + EditAlarmDetailsDialogComponent ], imports: [ CommonModule, @@ -271,7 +277,7 @@ import { DeviceCredentialsComponent } from './device/device-credentials.componen DeviceProfileTransportConfigurationComponent, CreateAlarmRulesComponent, AlarmRuleComponent, - AlarmRuleKeyFiltersDialogComponent, + AlarmRuleConditionDialogComponent, AlarmRuleConditionComponent, DeviceProfileAlarmComponent, DeviceProfileAlarmsComponent, @@ -281,7 +287,10 @@ import { DeviceCredentialsComponent } from './device/device-credentials.componen RuleChainAutocompleteComponent, DeviceWizardDialogComponent, DeviceCredentialsComponent, - AlarmScheduleComponent + AlarmScheduleInfoComponent, + AlarmScheduleComponent, + AlarmScheduleDialogComponent, + EditAlarmDetailsDialogComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html new file mode 100644 index 0000000000..7be952c68d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html @@ -0,0 +1,124 @@ + +
+ +

{{ (readonly ? 'device-profile.alarm-rule-condition' : 'device-profile.edit-alarm-rule-condition') | translate }}

+ + +
+ + +
+
+
+ + +
+ + device-profile.condition-type + + + {{ alarmConditionTypeTranslation.get(alarmConditionType) | translate }} + + + + {{ 'device-profile.condition-type-required' | translate }} + + +
+ + + + + {{ 'device-profile.condition-duration-value-required' | translate }} + + + {{ 'device-profile.condition-duration-value-range' | translate }} + + + {{ 'device-profile.condition-duration-value-range' | translate }} + + + {{ 'device-profile.condition-duration-value-pattern' | translate }} + + + + + + + {{ timeUnitTranslations.get(timeUnit) | translate }} + + + + {{ 'device-profile.condition-duration-time-unit-required' | translate }} + + +
+
+ + + + + {{ 'device-profile.condition-repeating-value-required' | translate }} + + + {{ 'device-profile.condition-repeating-value-range' | translate }} + + + {{ 'device-profile.condition-repeating-value-range' | translate }} + + + {{ 'device-profile.condition-repeating-value-pattern' | translate }} + + +
+
+
+
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.scss new file mode 100644 index 0000000000..4af573d021 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.scss @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + .row { + margin-top: 1em; + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts new file mode 100644 index 0000000000..4055560b38 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts @@ -0,0 +1,150 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; +import { ErrorStateMatcher } from '@angular/material/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { DialogComponent } from '@app/shared/components/dialog.component'; +import { UtilsService } from '@core/services/utils.service'; +import { TranslateService } from '@ngx-translate/core'; +import { KeyFilter, keyFilterInfosToKeyFilters, keyFiltersToKeyFilterInfos } from '@shared/models/query/query.models'; +import { AlarmCondition, AlarmConditionType, AlarmConditionTypeTranslationMap } from '@shared/models/device.models'; +import { TimeUnit, timeUnitTranslationMap } from '@shared/models/time/time.models'; + +export interface AlarmRuleConditionDialogData { + readonly: boolean; + condition: AlarmCondition; +} + +@Component({ + selector: 'tb-alarm-rule-condition-dialog', + templateUrl: './alarm-rule-condition-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: AlarmRuleConditionDialogComponent}], + styleUrls: ['/alarm-rule-condition-dialog.component.scss'] +}) +export class AlarmRuleConditionDialogComponent extends DialogComponent + implements OnInit, ErrorStateMatcher { + + timeUnits = Object.keys(TimeUnit); + timeUnitTranslations = timeUnitTranslationMap; + alarmConditionTypes = Object.keys(AlarmConditionType); + AlarmConditionType = AlarmConditionType; + alarmConditionTypeTranslation = AlarmConditionTypeTranslationMap; + + readonly = this.data.readonly; + condition = this.data.condition; + + conditionFormGroup: FormGroup; + + submitted = false; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: AlarmRuleConditionDialogData, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + private fb: FormBuilder, + private utils: UtilsService, + public translate: TranslateService) { + super(store, router, dialogRef); + + this.conditionFormGroup = this.fb.group({ + keyFilters: [keyFiltersToKeyFilterInfos(this.condition?.condition), Validators.required], + spec: this.fb.group({ + type: [AlarmConditionType.SIMPLE, Validators.required], + unit: [{value: null, disable: true}, Validators.required], + value: [{value: null, disable: true}, [Validators.required, Validators.min(1), Validators.max(2147483647), Validators.pattern('[0-9]*')]], + count: [{value: null, disable: true}, [Validators.required, Validators.min(1), Validators.max(2147483647), Validators.pattern('[0-9]*')]] + }) + }); + this.conditionFormGroup.patchValue({spec: this.condition?.spec}); + this.conditionFormGroup.get('spec.type').valueChanges.subscribe((type) => { + this.updateValidators(type, true, true); + }); + if (this.readonly) { + this.conditionFormGroup.disable({emitEvent: false}); + } else { + this.updateValidators(this.condition?.spec?.type); + } + } + + ngOnInit(): void { + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + private updateValidators(type: AlarmConditionType, resetDuration = false, emitEvent = false) { + switch (type) { + case AlarmConditionType.DURATION: + this.conditionFormGroup.get('spec.value').enable(); + this.conditionFormGroup.get('spec.unit').enable(); + this.conditionFormGroup.get('spec.count').disable(); + if (resetDuration) { + this.conditionFormGroup.get('spec').patchValue({ + count: null + }); + } + break; + case AlarmConditionType.REPEATING: + this.conditionFormGroup.get('spec.count').enable(); + this.conditionFormGroup.get('spec.value').disable(); + this.conditionFormGroup.get('spec.unit').disable(); + if (resetDuration) { + this.conditionFormGroup.get('spec').patchValue({ + value: null, + unit: null + }); + } + break; + case AlarmConditionType.SIMPLE: + this.conditionFormGroup.get('spec.value').disable(); + this.conditionFormGroup.get('spec.unit').disable(); + this.conditionFormGroup.get('spec.count').disable(); + if (resetDuration) { + this.conditionFormGroup.get('spec').patchValue({ + value: null, + unit: null, + count: null + }); + } + break; + } + this.conditionFormGroup.get('spec.value').updateValueAndValidity({emitEvent}); + this.conditionFormGroup.get('spec.unit').updateValueAndValidity({emitEvent}); + this.conditionFormGroup.get('spec.count').updateValueAndValidity({emitEvent}); + } + + cancel(): void { + this.dialogRef.close(null); + } + + save(): void { + this.submitted = true; + this.condition = { + condition: keyFilterInfosToKeyFilters(this.conditionFormGroup.get('keyFilters').value), + spec: this.conditionFormGroup.get('spec').value + }; + this.dialogRef.close(this.condition); + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.html index 170182ded3..15d6173e0e 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.html @@ -15,8 +15,9 @@ limitations under the License. --> -
+
+
- + +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.scss index 1d654bc0f4..0b1ccabd65 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.scss @@ -21,10 +21,15 @@ } } .tb-alarm-rule-condition { - padding: 8px; - border: 1px groove rgba(0, 0, 0, .25); - border-radius: 4px; cursor: pointer; + .tb-alarm-rule-condition-spec { + margin-top: 1em; + line-height: 1.8em; + padding: 4px; + &.disabled { + opacity: 0.7; + } + } } } diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.ts index 7416b944eb..23e52cf8cd 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.ts @@ -18,20 +18,21 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { ControlValueAccessor, FormBuilder, - FormControl, + FormControl, FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR, - Validator + Validator, Validators } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; -import { KeyFilter } from '@shared/models/query/query.models'; -import { deepClone } from '@core/utils'; -import { - AlarmRuleKeyFiltersDialogComponent, - AlarmRuleKeyFiltersDialogData -} from './alarm-rule-key-filters-dialog.component'; +import { deepClone, isUndefined } from '@core/utils'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; +import { AlarmCondition, AlarmConditionSpec, AlarmConditionType } from '@shared/models/device.models'; +import { + AlarmRuleConditionDialogComponent, + AlarmRuleConditionDialogData +} from '@home/components/profile/alarm/alarm-rule-condition-dialog.component'; +import { TimeUnit } from '@shared/models/time/time.models'; @Component({ selector: 'tb-alarm-rule-condition', @@ -55,9 +56,11 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit @Input() disabled: boolean; - alarmRuleConditionControl: FormControl; + alarmRuleConditionFormGroup: FormGroup; + + specText = ''; - private modelValue: Array; + private modelValue: AlarmCondition; private propagateChange = (v: any) => { }; @@ -75,25 +78,31 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit } ngOnInit() { - this.alarmRuleConditionControl = this.fb.control(null); + this.alarmRuleConditionFormGroup = this.fb.group({ + condition: [null, Validators.required], + spec: [null, Validators.required] + }); } setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (this.disabled) { - this.alarmRuleConditionControl.disable({emitEvent: false}); + this.alarmRuleConditionFormGroup.disable({emitEvent: false}); } else { - this.alarmRuleConditionControl.enable({emitEvent: false}); + this.alarmRuleConditionFormGroup.enable({emitEvent: false}); } } - writeValue(value: Array): void { + writeValue(value: AlarmCondition): void { this.modelValue = value; + if (this.modelValue !== null && isUndefined(this.modelValue?.spec)) { + this.modelValue = Object.assign(this.modelValue, {spec: {type: AlarmConditionType.SIMPLE}}); + } this.updateConditionInfo(); } public conditionSet() { - return this.modelValue && this.modelValue.length; + return this.modelValue && this.modelValue.condition.length; } public validate(c: FormControl) { @@ -108,13 +117,13 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit if ($event) { $event.stopPropagation(); } - this.dialog.open>(AlarmRuleKeyFiltersDialogComponent, { + this.dialog.open(AlarmRuleConditionDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { readonly: this.disabled, - keyFilters: this.disabled ? this.modelValue : deepClone(this.modelValue) + condition: this.disabled ? this.modelValue : deepClone(this.modelValue) } }).afterClosed().subscribe((result) => { if (result) { @@ -125,7 +134,45 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit } private updateConditionInfo() { - this.alarmRuleConditionControl.patchValue(this.modelValue); + this.alarmRuleConditionFormGroup.patchValue( + { + condition: this.modelValue?.condition, + spec: this.modelValue?.spec + } + ); + this.updateSpecText(); + } + + private updateSpecText() { + this.specText = ''; + if (this.modelValue && this.modelValue.spec) { + const spec = this.modelValue.spec; + switch (spec.type) { + case AlarmConditionType.SIMPLE: + break; + case AlarmConditionType.DURATION: + let duringText = ''; + switch (spec.unit) { + case TimeUnit.SECONDS: + duringText = this.translate.instant('timewindow.seconds', {seconds: spec.value}); + break; + case TimeUnit.MINUTES: + duringText = this.translate.instant('timewindow.minutes', {minutes: spec.value}); + break; + case TimeUnit.HOURS: + duringText = this.translate.instant('timewindow.hours', {hours: spec.value}); + break; + case TimeUnit.DAYS: + duringText = this.translate.instant('timewindow.days', {days: spec.value}); + break; + } + this.specText = this.translate.instant('device-profile.condition-during', {during: duringText}); + break; + case AlarmConditionType.REPEATING: + this.specText = this.translate.instant('device-profile.condition-repeat-times', {count: spec.count}); + break; + } + } } private updateModel() { diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html index 66eb99f27e..3c49f61cf0 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html @@ -16,90 +16,36 @@ -->
- - - - -
- - device-profile.condition-type - - - {{ alarmConditionTypeTranslation.get(alarmConditionType) | translate }} - - - - {{ 'device-profile.condition-type-required' | translate }} - - -
- - - - - {{ 'device-profile.condition-duration-value-required' | translate }} - - - {{ 'device-profile.condition-duration-value-range' | translate }} - - - {{ 'device-profile.condition-duration-value-range' | translate }} - - - {{ 'device-profile.condition-duration-value-pattern' | translate }} - - - - - - - {{ timeUnitTranslations.get(timeUnit) | translate }} - - - - {{ 'device-profile.condition-duration-time-unit-required' | translate }} - - -
-
- - - - - {{ 'device-profile.condition-repeating-value-required' | translate }} - - - {{ 'device-profile.condition-repeating-value-range' | translate }} - - - {{ 'device-profile.condition-repeating-value-range' | translate }} - - - {{ 'device-profile.condition-repeating-value-pattern' | translate }} - - -
-
-
- - - - - - - device-profile.alarm-details - - - -
+ + + + + + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.scss index 8af986af69..a52e6f312e 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.scss @@ -17,5 +17,29 @@ .row { margin-top: 1em; } + .tb-alarm-rule-details { + a.mat-button { + &:hover, &:focus { + border-bottom: none; + } + } + .tb-alarm-rule-details-content { + min-height: 33px; + overflow: hidden; + white-space: pre; + line-height: 1.8em; + padding: 4px; + cursor: pointer; + &.collapsed { + max-height: 33px; + white-space: nowrap; + text-overflow: ellipsis; + } + &.disabled { + opacity: 0.7; + cursor: auto; + } + } + } } diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts index fe2916b854..4c86f7dfbd 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts @@ -25,11 +25,14 @@ import { Validator, Validators } from '@angular/forms'; -import { AlarmConditionType, AlarmConditionTypeTranslationMap, AlarmRule } from '@shared/models/device.models'; +import { AlarmRule } from '@shared/models/device.models'; import { MatDialog } from '@angular/material/dialog'; -import { TimeUnit, timeUnitTranslationMap } from '@shared/models/time/time.models'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; -import { isUndefined } from '@core/utils'; +import { isDefinedAndNotNull } from '@core/utils'; +import { + EditAlarmDetailsDialogComponent, + EditAlarmDetailsDialogData +} from '@home/components/profile/alarm/edit-alarm-details-dialog.component'; @Component({ selector: 'tb-alarm-rule', @@ -50,12 +53,6 @@ import { isUndefined } from '@core/utils'; }) export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validator { - timeUnits = Object.keys(TimeUnit); - timeUnitTranslations = timeUnitTranslationMap; - alarmConditionTypes = Object.keys(AlarmConditionType); - AlarmConditionType = AlarmConditionType; - alarmConditionTypeTranslation = AlarmConditionTypeTranslationMap; - @Input() disabled: boolean; @@ -72,6 +69,8 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat alarmRuleFormGroup: FormGroup; + expandAlarmDetails = false; + private propagateChange = (v: any) => { }; constructor(private dialog: MatDialog, @@ -87,21 +86,10 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat ngOnInit() { this.alarmRuleFormGroup = this.fb.group({ - condition: this.fb.group({ - condition: [null, Validators.required], - spec: this.fb.group({ - type: [AlarmConditionType.SIMPLE, Validators.required], - unit: [{value: null, disable: true}, Validators.required], - value: [{value: null, disable: true}, [Validators.required, Validators.min(1), Validators.max(2147483647), Validators.pattern('[0-9]*')]], - count: [{value: null, disable: true}, [Validators.required, Validators.min(1), Validators.max(2147483647), Validators.pattern('[0-9]*')]] - }) - }, Validators.required), + condition: [null, [Validators.required]], schedule: [null], alarmDetails: [null] }); - this.alarmRuleFormGroup.get('condition.spec.type').valueChanges.subscribe((type) => { - this.updateValidators(type, true, true); - }); this.alarmRuleFormGroup.valueChanges.subscribe(() => { this.updateModel(); }); @@ -118,11 +106,25 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat writeValue(value: AlarmRule): void { this.modelValue = value; - if (this.modelValue !== null && isUndefined(this.modelValue?.condition?.spec)) { - this.modelValue = Object.assign(this.modelValue, {condition: {spec: {type: AlarmConditionType.SIMPLE}}}); - } this.alarmRuleFormGroup.reset(this.modelValue || undefined, {emitEvent: false}); - this.updateValidators(this.modelValue?.condition?.spec?.type); + } + + public openEditDetailsDialog($event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(EditAlarmDetailsDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + alarmDetails: this.alarmRuleFormGroup.get('alarmDetails').value + } + }).afterClosed().subscribe((alarmDetails) => { + if (isDefinedAndNotNull(alarmDetails)) { + this.alarmRuleFormGroup.patchValue({alarmDetails}); + } + }); } public validate(c: FormControl) { @@ -133,47 +135,6 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat }; } - private updateValidators(type: AlarmConditionType, resetDuration = false, emitEvent = false) { - switch (type) { - case AlarmConditionType.DURATION: - this.alarmRuleFormGroup.get('condition.spec.value').enable(); - this.alarmRuleFormGroup.get('condition.spec.unit').enable(); - this.alarmRuleFormGroup.get('condition.spec.count').disable(); - if (resetDuration) { - this.alarmRuleFormGroup.get('condition.spec').patchValue({ - count: null - }); - } - break; - case AlarmConditionType.REPEATING: - this.alarmRuleFormGroup.get('condition.spec.count').enable(); - this.alarmRuleFormGroup.get('condition.spec.value').disable(); - this.alarmRuleFormGroup.get('condition.spec.unit').disable(); - if (resetDuration) { - this.alarmRuleFormGroup.get('condition.spec').patchValue({ - value: null, - unit: null - }); - } - break; - case AlarmConditionType.SIMPLE: - this.alarmRuleFormGroup.get('condition.spec.value').disable(); - this.alarmRuleFormGroup.get('condition.spec.unit').disable(); - this.alarmRuleFormGroup.get('condition.spec.count').disable(); - if (resetDuration) { - this.alarmRuleFormGroup.get('condition.spec').patchValue({ - value: null, - unit: null, - count: null - }); - } - break; - } - this.alarmRuleFormGroup.get('condition.spec.value').updateValueAndValidity({emitEvent}); - this.alarmRuleFormGroup.get('condition.spec.unit').updateValueAndValidity({emitEvent}); - this.alarmRuleFormGroup.get('condition.spec.count').updateValueAndValidity({emitEvent}); - } - private updateModel() { const value = this.alarmRuleFormGroup.value; if (this.modelValue) { diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-key-filters-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-dialog.component.html similarity index 74% rename from ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-key-filters-dialog.component.html rename to ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-dialog.component.html index 26defe62ff..abe511fc3f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-key-filters-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-dialog.component.html @@ -15,9 +15,9 @@ limitations under the License. --> -
+ -

{{ (readonly ? 'device-profile.alarm-rule-condition' : 'device-profile.edit-alarm-rule-condition') | translate }}

+

{{ (readonly ? 'device-profile.schedule' : 'device-profile.edit-schedule') | translate }}

@@ -43,7 +40,7 @@
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts index 0062687d5d..65009dbd0b 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts @@ -28,7 +28,12 @@ import { Validator, Validators } from '@angular/forms'; -import { AlarmSchedule, AlarmScheduleType, AlarmScheduleTypeTranslationMap } from '@shared/models/device.models'; +import { + AlarmSchedule, + AlarmScheduleType, + AlarmScheduleTypeTranslationMap, + dayOfWeekTranslations, getAlarmScheduleRangeText, timeOfDayToUTCTimestamp, utcTimestampToTimeOfDay +} from '@shared/models/device.models'; import { isDefined, isDefinedAndNotNull } from '@core/utils'; import * as _moment from 'moment-timezone'; import { MatCheckboxChange } from '@angular/material/checkbox'; @@ -59,11 +64,18 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, alarmScheduleType = AlarmScheduleType; alarmScheduleTypeTranslate = AlarmScheduleTypeTranslationMap; + dayOfWeekTranslationsArray = dayOfWeekTranslations; + + allDays = Array(7).fill(0).map((x, i) => i); + + firstRowDays = Array(4).fill(0).map((x, i) => i); + secondRowDays = Array(3).fill(0).map((x, i) => i + 4); + private modelValue: AlarmSchedule; private defaultItems = Array.from({length: 7}, (value, i) => ({ enabled: true, - dayOfWeek: i + dayOfWeek: i + 1 })); private propagateChange = (v: any) => { }; @@ -75,10 +87,10 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, this.alarmScheduleForm = this.fb.group({ type: [AlarmScheduleType.ANY_TIME, Validators.required], timezone: [null, Validators.required], - daysOfWeek: this.fb.array(new Array(7).fill(false)), + daysOfWeek: this.fb.array(new Array(7).fill(false), this.validateDayOfWeeks), startsOn: [0, Validators.required], endsOn: [0, Validators.required], - items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i))) + items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)), this.validateItems) }); this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => { this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: this.defaultTimezone}, {emitEvent: false}); @@ -90,6 +102,26 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, }); } + validateDayOfWeeks(control: AbstractControl): ValidationErrors | null { + const dayOfWeeks: boolean[] = control.value; + if (!dayOfWeeks || !dayOfWeeks.length || !dayOfWeeks.find(v => v === true)) { + return { + dayOfWeeks: true + }; + } + return null; + } + + validateItems(control: AbstractControl): ValidationErrors | null { + const items: any[] = control.value; + if (!items || !items.length || !items.find(v => v.enabled === true)) { + return { + dayOfWeeks: true + }; + } + return null; + } + registerOnChange(fn: any): void { this.propagateChange = fn; } @@ -123,8 +155,8 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, type: this.modelValue.type, timezone: this.modelValue.timezone, daysOfWeek, - startsOn: this.timestampToTime(this.modelValue.startsOn), - endsOn: this.timestampToTime(this.modelValue.endsOn) + startsOn: utcTimestampToTimeOfDay(this.modelValue.startsOn), + endsOn: utcTimestampToTimeOfDay(this.modelValue.endsOn) }, {emitEvent: false}); break; case AlarmScheduleType.CUSTOM: @@ -136,8 +168,8 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, this.disabledSelectedTime(item.enabled, index); alarmDays.push({ enabled: item.enabled, - startsOn: this.timestampToTime(item.startsOn), - endsOn: this.timestampToTime(item.endsOn) + startsOn: utcTimestampToTimeOfDay(item.startsOn), + endsOn: utcTimestampToTimeOfDay(item.endsOn) }); }); this.alarmScheduleForm.patchValue({ @@ -202,15 +234,15 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, .filter(day => !!day); } if (isDefined(value.startsOn) && value.startsOn !== 0) { - value.startsOn = this.timeToTimestampUTC(value.startsOn); + value.startsOn = timeOfDayToUTCTimestamp(value.startsOn); } if (isDefined(value.endsOn) && value.endsOn !== 0) { - value.endsOn = this.timeToTimestampUTC(value.endsOn); + value.endsOn = timeOfDayToUTCTimestamp(value.endsOn); } if (isDefined(value.items)){ value.items = this.alarmScheduleForm.getRawValue().items; value.items = value.items.map((item) => { - return { ...item, startsOn: this.timeToTimestampUTC(item.startsOn), endsOn: this.timeToTimestampUTC(item.endsOn)}; + return { ...item, startsOn: timeOfDayToUTCTimestamp(item.startsOn), endsOn: timeOfDayToUTCTimestamp(item.endsOn)}; }); } this.modelValue = value; @@ -218,21 +250,11 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, } } - private timeToTimestampUTC(date: Date | number): number { - if (typeof date === 'number' || date === null) { - return 0; - } - return _moment.utc([1970, 0, 1, date.getHours(), date.getMinutes(), date.getSeconds(), 0]).valueOf(); - } - - private timestampToTime(time = 0): Date { - return new Date(time + new Date().getTimezoneOffset() * 60 * 1000); - } private defaultItemsScheduler(index): FormGroup { return this.fb.group({ enabled: [true], - dayOfWeek: [index], + dayOfWeek: [index + 1], startsOn: [0, Validators.required], endsOn: [0, Validators.required] }); @@ -253,23 +275,8 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, } } - private timeToMoment(date: Date | number): _moment.Moment { - if (typeof date === 'number' || date === null) { - return _moment([1970, 0, 1, 0, 0, 0, 0]); - } - return _moment([1970, 0, 1, date.getHours(), date.getMinutes(), 0, 0]); - } - getSchedulerRangeText(control: FormGroup | AbstractControl): string { - const start = this.timeToMoment(control.get('startsOn').value); - const end = this.timeToMoment(control.get('endsOn').value); - if (start < end) { - return `${start.format('hh:mm A')} – ${end.format('hh:mm A')}`; - } else if (start.valueOf() === 0 && end.valueOf() === 0 || start.isSame(_moment([1970, 0])) && end.isSame(_moment([1970, 0]))) { - return '12:00 AM – 12:00 PM'; - } - return `12:00 AM – ${end.format('hh:mm A')}` + - ` and ${start.format('hh:mm A')} – 12:00 PM`; + return getAlarmScheduleRangeText(control.get('startsOn').value, control.get('endsOn').value); } get itemsSchedulerForm(): FormArray { diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.scss index eedaac2ca1..91335f53a4 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.scss @@ -19,6 +19,7 @@ border: 2px groove rgba(0, 0, 0, .45); border-radius: 4px; padding: 8px; + min-width: 0; } } diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html index 7d2ad8b75d..f1322ef9a9 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html @@ -56,7 +56,7 @@ {{ 'device-profile.propagate-alarm' | translate }} -
+
device-profile.alarm-rule-relation-types-list diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts index 7fa60e79d5..116d9a2a9c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts @@ -57,6 +57,7 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit separatorKeysCodes = [ENTER, COMMA, SEMICOLON]; + @Input() expanded = false; private modelValue: DeviceProfileAlarm; diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.html index 7bd7c546db..e1b446c65a 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.html @@ -21,6 +21,7 @@ let $index = index; last as isLast;" fxLayout="column" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
@@ -29,7 +30,7 @@ device-profile.no-alarm-rules -
+ + + +
+
+
+ + device-profile.alarm-details + + +
+
+
+
+ + +
+ diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.ts new file mode 100644 index 0000000000..bc46bee51d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.ts @@ -0,0 +1,80 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; +import { ErrorStateMatcher } from '@angular/material/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm } from '@angular/forms'; +import { Router } from '@angular/router'; +import { DialogComponent } from '@app/shared/components/dialog.component'; +import { UtilsService } from '@core/services/utils.service'; +import { TranslateService } from '@ngx-translate/core'; + +export interface EditAlarmDetailsDialogData { + alarmDetails: string; +} + +@Component({ + selector: 'tb-edit-alarm-details-dialog', + templateUrl: './edit-alarm-details-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: EditAlarmDetailsDialogComponent}], + styleUrls: [] +}) +export class EditAlarmDetailsDialogComponent extends DialogComponent + implements OnInit, ErrorStateMatcher { + + alarmDetails = this.data.alarmDetails; + + editDetailsFormGroup: FormGroup; + + submitted = false; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: EditAlarmDetailsDialogData, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + private fb: FormBuilder, + private utils: UtilsService, + public translate: TranslateService) { + super(store, router, dialogRef); + + this.editDetailsFormGroup = this.fb.group({ + alarmDetails: [this.alarmDetails] + }); + } + + ngOnInit(): void { + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.dialogRef.close(null); + } + + save(): void { + this.submitted = true; + this.alarmDetails = this.editDetailsFormGroup.get('alarmDetails').value; + this.dialogRef.close(this.alarmDetails); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html index 437fc21c86..76dfe27511 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html @@ -61,29 +61,7 @@
- - - - - - - - - - - - - + + diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index d1fb40a9d5..4354521330 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -25,6 +25,8 @@ import { RuleChainId } from '@shared/models/id/rule-chain-id'; import { EntityInfoData } from '@shared/models/entity.models'; import { KeyFilter } from '@shared/models/query/query.models'; import { TimeUnit } from '@shared/models/time/time.models'; +import * as _moment from 'moment-timezone'; +import { AbstractControl, FormGroup } from '@angular/forms'; export enum DeviceProfileType { DEFAULT = 'DEFAULT' @@ -408,3 +410,62 @@ export interface ClaimResult { device: Device; response: ClaimResponse; } + +export const dayOfWeekTranslations = new Array( + 'device-profile.schedule-day.monday', + 'device-profile.schedule-day.tuesday', + 'device-profile.schedule-day.wednesday', + 'device-profile.schedule-day.thursday', + 'device-profile.schedule-day.friday', + 'device-profile.schedule-day.saturday', + 'device-profile.schedule-day.sunday' +); + +export function getDayString(day: number): string { + switch (day) { + case 0: + return 'device-profile.schedule-day.monday'; + case 1: + return this.translate.instant('device-profile.schedule-day.tuesday'); + case 2: + return this.translate.instant('device-profile.schedule-day.wednesday'); + case 3: + return this.translate.instant('device-profile.schedule-day.thursday'); + case 4: + return this.translate.instant('device-profile.schedule-day.friday'); + case 5: + return this.translate.instant('device-profile.schedule-day.saturday'); + case 6: + return this.translate.instant('device-profile.schedule-day.sunday'); + } +} + +export function timeOfDayToUTCTimestamp(date: Date | number): number { + if (typeof date === 'number' || date === null) { + return 0; + } + return _moment.utc([1970, 0, 1, date.getHours(), date.getMinutes(), date.getSeconds(), 0]).valueOf(); +} + +export function utcTimestampToTimeOfDay(time = 0): Date { + return new Date(time + new Date().getTimezoneOffset() * 60 * 1000); +} + +function timeOfDayToMoment(date: Date | number): _moment.Moment { + if (typeof date === 'number' || date === null) { + return _moment([1970, 0, 1, 0, 0, 0, 0]); + } + return _moment([1970, 0, 1, date.getHours(), date.getMinutes(), 0, 0]); +} + +export function getAlarmScheduleRangeText(startsOn: Date | number, endsOn: Date | number): string { + const start = timeOfDayToMoment(startsOn); + const end = timeOfDayToMoment(endsOn); + if (start < end) { + return `${start.format('hh:mm A')} – ${end.format('hh:mm A')}`; + } else if (start.valueOf() === 0 && end.valueOf() === 0 || start.isSame(_moment([1970, 0])) && end.isSame(_moment([1970, 0]))) { + return '12:00 AM – 12:00 PM'; + } + return `12:00 AM – ${end.format('hh:mm A')}` + + ` and ${start.format('hh:mm A')} – 12:00 PM`; +} diff --git a/ui-ngx/src/app/shared/models/query/query.models.ts b/ui-ngx/src/app/shared/models/query/query.models.ts index 3f4b6896db..ba86bce85b 100644 --- a/ui-ngx/src/app/shared/models/query/query.models.ts +++ b/ui-ngx/src/app/shared/models/query/query.models.ts @@ -476,21 +476,23 @@ export function keyFilterInfosToKeyFilters(keyFilterInfos: Array) export function keyFiltersToKeyFilterInfos(keyFilters: Array): Array { const keyFilterInfos: Array = []; const keyFilterInfoMap: {[infoKey: string]: KeyFilterInfo} = {}; - for (const keyFilter of keyFilters) { - const key = keyFilter.key; - const infoKey = key.key + key.type + keyFilter.valueType; - let keyFilterInfo = keyFilterInfoMap[infoKey]; - if (!keyFilterInfo) { - keyFilterInfo = { - key, - valueType: keyFilter.valueType, - predicates: [] - }; - keyFilterInfoMap[infoKey] = keyFilterInfo; - keyFilterInfos.push(keyFilterInfo); - } - if (keyFilter.predicate) { - keyFilterInfo.predicates.push(keyFilterPredicateToKeyFilterPredicateInfo(keyFilter.predicate)); + if (keyFilters) { + for (const keyFilter of keyFilters) { + const key = keyFilter.key; + const infoKey = key.key + key.type + keyFilter.valueType; + let keyFilterInfo = keyFilterInfoMap[infoKey]; + if (!keyFilterInfo) { + keyFilterInfo = { + key, + valueType: keyFilter.valueType, + predicates: [] + }; + keyFilterInfoMap[infoKey] = keyFilterInfo; + keyFilterInfos.push(keyFilterInfo); + } + if (keyFilter.predicate) { + keyFilterInfo.predicates.push(keyFilterPredicateToKeyFilterPredicateInfo(keyFilter.predicate)); + } } } return keyFilterInfos; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 8f89fa8f39..9a5d71a2e6 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -55,7 +55,9 @@ "continue": "Continue", "discard-changes": "Discard Changes", "download": "Download", - "next-with-label": "Next: {{label}}" + "next-with-label": "Next: {{label}}", + "read-more": "Read more", + "hide": "Hide" }, "aggregation": { "aggregation": "Aggregation", @@ -932,15 +934,18 @@ "condition-type": "Condition type", "condition-type-simple": "Simple", "condition-type-duration": "Duration", + "condition-during": "During {{during}}", "condition-type-repeating": "Repeating", "condition-type-required": "Condition type is required.", "condition-repeating-value": "Count of events", "condition-repeating-value-range": "Count of events should be in a range from 1 to 2147483647.", "condition-repeating-value-pattern": "Count of events should be integers.", "condition-repeating-value-required": "Count of events is required.", + "condition-repeat-times": "Repeats { count, plural, 1 {1 time} other {# times} }", "schedule-type": "Scheduler type", "schedule-type-required": "Scheduler type is required.", "schedule": "Schedule", + "edit-schedule": "Edit alarm schedule", "schedule-any-time": "Active all the time", "schedule-specific-time": "Active at a specific time", "schedule-custom": "Custom", @@ -956,7 +961,8 @@ "schedule-days": "Days", "schedule-time": "Time", "schedule-time-from": "From", - "schedule-time-to": "To" + "schedule-time-to": "To", + "schedule-days-of-week-required": "At least one day of week should be selected." }, "dialog": { "close": "Close dialog" From 4952c310a466bbf675e263a32a26def1b57b4cdd Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Mon, 12 Oct 2020 15:13:40 +0300 Subject: [PATCH 183/463] Added mapperConfig to OAuth2Template --- .../facebook_config.json | 13 +++-- .../github_config.json | 7 ++- .../google_config.json | 13 +++-- .../OAuth2ClientRegistrationTemplate.java | 6 +-- ...Auth2ClientRegistrationTemplateEntity.java | 50 +++++++++++-------- .../OAuth2ConfigTemplateServiceImpl.java | 8 ++- .../BaseOAuth2ConfigTemplateServiceTest.java | 10 ++-- 7 files changed, 64 insertions(+), 43 deletions(-) diff --git a/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json b/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json index 9b059e81c1..3961bf3688 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json @@ -7,11 +7,14 @@ "userInfoUri": "https://graph.facebook.com/me?fields=id,name,first_name,last_name,email", "clientAuthenticationMethod": "BASIC", "userNameAttributeName": "email", - "basic": { - "emailAttributeKey": "email", - "firstNameAttributeKey": "first_name", - "lastNameAttributeKey": "last_name", - "tenantNameStrategy": "DOMAIN" + "mapperConfig": { + "type": "BASIC", + "basic": { + "emailAttributeKey": "email", + "firstNameAttributeKey": "first_name", + "lastNameAttributeKey": "last_name", + "tenantNameStrategy": "DOMAIN" + } }, "comment": null, "loginButtonIcon": "mdi:facebook", diff --git a/application/src/main/data/json/system/oauth2_config_templates/github_config.json b/application/src/main/data/json/system/oauth2_config_templates/github_config.json index 6c3a51723f..6569162ead 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/github_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/github_config.json @@ -7,8 +7,11 @@ "userInfoUri": "https://api.github.com/user", "clientAuthenticationMethod": "BASIC", "userNameAttributeName": "login", - "basic": { - "tenantNameStrategy": "DOMAIN" + "mapperConfig": { + "type": "GITHUB", + "basic": { + "tenantNameStrategy": "DOMAIN" + } }, "comment": "In order to log into ThingsBoard you need to have user's email. You may configure and use Custom OAuth2 Mapper to get email information. Please refer to Github Documentation", "loginButtonIcon": "mdi:github", diff --git a/application/src/main/data/json/system/oauth2_config_templates/google_config.json b/application/src/main/data/json/system/oauth2_config_templates/google_config.json index 2756bcc957..03e3046125 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/google_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/google_config.json @@ -8,11 +8,14 @@ "userInfoUri": "https://openidconnect.googleapis.com/v1/userinfo", "clientAuthenticationMethod": "BASIC", "userNameAttributeName": "email", - "basic": { - "emailAttributeKey": "email", - "firstNameAttributeKey": "given_name", - "lastNameAttributeKey": "family_name", - "tenantNameStrategy": "DOMAIN" + "mapperConfig": { + "type": "BASIC", + "basic": { + "emailAttributeKey": "email", + "firstNameAttributeKey": "given_name", + "lastNameAttributeKey": "family_name", + "tenantNameStrategy": "DOMAIN" + } }, "comment": null, "loginButtonIcon": "mdi:google", diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java index 897472f154..30c6e1c4a9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java @@ -34,8 +34,7 @@ import java.util.List; public class OAuth2ClientRegistrationTemplate extends SearchTextBasedWithAdditionalInfo implements HasName { private String providerId; - private MapperType mapperType; - private OAuth2BasicMapperConfig basic; + private OAuth2MapperConfig mapperConfig; private String authorizationUri; private String accessTokenUri; private List scope; @@ -51,8 +50,7 @@ public class OAuth2ClientRegistrationTemplate extends SearchTextBasedWithAdditio public OAuth2ClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { super(clientRegistrationTemplate); this.providerId = clientRegistrationTemplate.providerId; - this.mapperType = clientRegistrationTemplate.mapperType; - this.basic = clientRegistrationTemplate.basic; + this.mapperConfig = clientRegistrationTemplate.mapperConfig; this.authorizationUri = clientRegistrationTemplate.authorizationUri; this.accessTokenUri = clientRegistrationTemplate.accessTokenUri; this.scope = clientRegistrationTemplate.scope; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationTemplateEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationTemplateEntity.java index e7973dcd5d..d4fb6bfebe 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationTemplateEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationTemplateEntity.java @@ -109,17 +109,20 @@ public class OAuth2ClientRegistrationTemplateEntity extends BaseSqlEntity Date: Mon, 12 Oct 2020 18:03:05 +0300 Subject: [PATCH 184/463] Improved scheduler logic --- .../rule/engine/profile/AlarmRuleState.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java index 95356239f5..d9d08efbdc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java @@ -165,9 +165,7 @@ class AlarmRuleState { return false; } } - long startOfDay = zdt.toLocalDate().atStartOfDay(zoneId).toInstant().toEpochMilli(); - long msFromStartOfDay = eventTs - startOfDay; - return schedule.getStartsOn() <= msFromStartOfDay && schedule.getEndsOn() > msFromStartOfDay; + return isActive(eventTs, zoneId, zdt, schedule.getStartsOn(), schedule.getEndsOn()); } private boolean isActiveCustom(CustomTimeSchedule schedule, long eventTs) { @@ -177,9 +175,7 @@ class AlarmRuleState { for (CustomTimeScheduleItem item : schedule.getItems()) { if (item.getDayOfWeek() == dayOfWeek) { if (item.isEnabled()) { - long startOfDay = zdt.toLocalDate().atStartOfDay(zoneId).toInstant().toEpochMilli(); - long msFromStartOfDay = eventTs - startOfDay; - return item.getStartsOn() <= msFromStartOfDay && item.getEndsOn() > msFromStartOfDay; + return isActive(eventTs, zoneId, zdt, item.getStartsOn(), item.getEndsOn()); } else { return false; } @@ -188,6 +184,16 @@ class AlarmRuleState { return false; } + private boolean isActive(long eventTs, ZoneId zoneId, ZonedDateTime zdt, long startsOn, long endsOn) { + long startOfDay = zdt.toLocalDate().atStartOfDay(zoneId).toInstant().toEpochMilli(); + long msFromStartOfDay = eventTs - startOfDay; + if (startsOn <= endsOn) { + return startsOn <= msFromStartOfDay && endsOn > msFromStartOfDay; + } else { + return startsOn < msFromStartOfDay || (0 < msFromStartOfDay && msFromStartOfDay < endsOn); + } + } + public void clear() { if (state.getEventCount() > 0 || state.getLastEventTs() > 0 || state.getDuration() > 0) { state.setEventCount(0L); From 40be6e588c5460f5223aa67343415b7dde449b67 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 12 Oct 2020 18:05:26 +0300 Subject: [PATCH 185/463] Fix device wizard dialog. Fix install script for oauth2 client registration templates. --- .../server/service/install/InstallScripts.java | 6 ++++++ .../server/dao/oauth2/OAuth2ConfigTemplateService.java | 3 +++ .../dao/oauth2/OAuth2ClientRegistrationTemplateDao.java | 4 ++++ .../dao/oauth2/OAuth2ConfigTemplateServiceImpl.java | 9 +++++++++ .../oauth2/JpaOAuth2ClientRegistrationTemplateDao.java | 7 +++++++ .../OAuth2ClientRegistrationTemplateRepository.java | 3 +++ .../components/wizard/device-wizard-dialog.component.ts | 4 ++-- 7 files changed, 34 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index f432851ffc..ac047060ae 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -42,6 +42,7 @@ import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Optional; import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper; @@ -243,6 +244,11 @@ public class InstallScripts { try { JsonNode oauth2ConfigTemplateJson = objectMapper.readTree(path.toFile()); OAuth2ClientRegistrationTemplate clientRegistrationTemplate = objectMapper.treeToValue(oauth2ConfigTemplateJson, OAuth2ClientRegistrationTemplate.class); + Optional existingClientRegistrationTemplate = + oAuth2TemplateService.findClientRegistrationTemplateByProviderId(clientRegistrationTemplate.getProviderId()); + if (existingClientRegistrationTemplate.isPresent()) { + clientRegistrationTemplate.setId(existingClientRegistrationTemplate.get().getId()); + } oAuth2TemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate); } catch (Exception e) { log.error("Unable to load oauth2 config templates from json: [{}]", path.toString()); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateService.java index b3718a8d92..54a47d9fdb 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateService.java @@ -19,10 +19,13 @@ import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import java.util.List; +import java.util.Optional; public interface OAuth2ConfigTemplateService { OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate); + Optional findClientRegistrationTemplateByProviderId(String providerId); + OAuth2ClientRegistrationTemplate findClientRegistrationTemplateById(OAuth2ClientRegistrationTemplateId templateId); List findAllClientRegistrationTemplates(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationTemplateDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationTemplateDao.java index 75fcbf1b10..6c377bc067 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationTemplateDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationTemplateDao.java @@ -19,7 +19,11 @@ import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplat import org.thingsboard.server.dao.Dao; import java.util.List; +import java.util.Optional; public interface OAuth2ClientRegistrationTemplateDao extends Dao { + + Optional findByProviderId(String providerId); + List findAll(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java index 4eaeb914c9..e95ea6c39b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java @@ -29,6 +29,7 @@ import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import java.util.List; +import java.util.Optional; import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateString; @@ -37,6 +38,7 @@ import static org.thingsboard.server.dao.service.Validator.validateString; @Service public class OAuth2ConfigTemplateServiceImpl extends AbstractEntityService implements OAuth2ConfigTemplateService { public static final String INCORRECT_CLIENT_REGISTRATION_TEMPLATE_ID = "Incorrect clientRegistrationTemplateId "; + public static final String INCORRECT_CLIENT_REGISTRATION_PROVIDER_ID = "Incorrect clientRegistrationProviderId "; @Autowired private OAuth2ClientRegistrationTemplateDao clientRegistrationTemplateDao; @@ -59,6 +61,13 @@ public class OAuth2ConfigTemplateServiceImpl extends AbstractEntityService imple return savedClientRegistrationTemplate; } + @Override + public Optional findClientRegistrationTemplateByProviderId(String providerId) { + log.trace("Executing findClientRegistrationTemplateByProviderId [{}]", providerId); + validateString(providerId, INCORRECT_CLIENT_REGISTRATION_PROVIDER_ID + providerId); + return clientRegistrationTemplateDao.findByProviderId(providerId); + } + @Override public OAuth2ClientRegistrationTemplate findClientRegistrationTemplateById(OAuth2ClientRegistrationTemplateId templateId) { log.trace("Executing findClientRegistrationTemplateById [{}]", templateId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationTemplateDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationTemplateDao.java index 70c3c03479..4540a9b3d1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationTemplateDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationTemplateDao.java @@ -26,6 +26,7 @@ import org.thingsboard.server.dao.sql.JpaAbstractDao; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.UUID; @Component @@ -43,6 +44,12 @@ public class JpaOAuth2ClientRegistrationTemplateDao extends JpaAbstractDao findByProviderId(String providerId) { + OAuth2ClientRegistrationTemplate oAuth2ClientRegistrationTemplate = DaoUtil.getData(repository.findByProviderId(providerId)); + return Optional.ofNullable(oAuth2ClientRegistrationTemplate); + } + @Override public List findAll() { Iterable entities = repository.findAll(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationTemplateRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationTemplateRepository.java index 8ffd380496..cba85280d7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationTemplateRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationTemplateRepository.java @@ -21,4 +21,7 @@ import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationTemplateEnti import java.util.UUID; public interface OAuth2ClientRegistrationTemplateRepository extends CrudRepository { + + OAuth2ClientRegistrationTemplateEntity findByProviderId(String providerId); + } diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts index 9e861c1359..bbbde588b7 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts @@ -266,11 +266,11 @@ export class DeviceWizardDialogComponent extends }) ); } else { - return of(null); + return of(this.deviceWizardFormGroup.get('deviceProfileId').value); } } - private createDevice(profileId: EntityId = this.deviceWizardFormGroup.get('deviceProfileId').value): Observable> { + private createDevice(profileId): Observable> { const device = { name: this.deviceWizardFormGroup.get('name').value, label: this.deviceWizardFormGroup.get('label').value, From 1f7623352a52741700cfdc9c2df63bba0d5ef150 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 12 Oct 2020 18:11:22 +0300 Subject: [PATCH 186/463] UI: refactoring OAuth2/Login Page and added oauth mapperType GITHUB --- ui-ngx/src/app/core/auth/auth.service.ts | 9 +- ui-ngx/src/app/core/http/admin.service.ts | 22 +-- ui-ngx/src/app/core/http/oauth2.service.ts | 44 +++++ ui-ngx/src/app/core/http/public-api.ts | 1 + .../admin/oauth2-settings.component.html | 35 ++-- .../admin/oauth2-settings.component.scss | 11 +- .../pages/admin/oauth2-settings.component.ts | 158 +++++++++--------- .../login/pages/login/login.component.html | 28 ++-- .../login/pages/login/login.component.scss | 13 +- .../login/pages/login/login.component.ts | 4 +- ui-ngx/src/app/shared/models/login.models.ts | 6 - ui-ngx/src/app/shared/models/oauth2.models.ts | 119 +++++++++++++ ui-ngx/src/app/shared/models/public-api.ts | 1 + .../src/app/shared/models/settings.models.ts | 99 ----------- 14 files changed, 298 insertions(+), 252 deletions(-) create mode 100644 ui-ngx/src/app/core/http/oauth2.service.ts create mode 100644 ui-ngx/src/app/shared/models/oauth2.models.ts diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts index 32733ac139..da41c7c1b1 100644 --- a/ui-ngx/src/app/core/auth/auth.service.ts +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -21,7 +21,7 @@ import { HttpClient } from '@angular/common/http'; import { forkJoin, Observable, of, ReplaySubject, throwError } from 'rxjs'; import { catchError, map, mergeMap, tap } from 'rxjs/operators'; -import { LoginRequest, LoginResponse, OAuth2Client, PublicLoginRequest } from '@shared/models/login.models'; +import { LoginRequest, LoginResponse, PublicLoginRequest } from '@shared/models/login.models'; import { ActivatedRoute, Router, UrlTree } from '@angular/router'; import { defaultHttpOptions } from '../http/http-utils'; import { UserService } from '../http/user.service'; @@ -44,6 +44,7 @@ import { AdminService } from '@core/http/admin.service'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component'; +import { OAuth2ClientInfo } from '@shared/models/oauth2.models'; @Injectable({ providedIn: 'root' @@ -67,7 +68,7 @@ export class AuthService { } redirectUrl: string; - oauth2Clients: Array = null; + oauth2Clients: Array = null; private refreshTokenSubject: ReplaySubject = null; private jwtHelper = new JwtHelperService(); @@ -197,8 +198,8 @@ export class AuthService { }); } - public loadOAuth2Clients(): Observable> { - return this.http.post>(`/api/noauth/oauth2Clients`, + public loadOAuth2Clients(): Observable> { + return this.http.post>(`/api/noauth/oauth2Clients`, null, defaultHttpOptions()).pipe( tap((OAuth2Clients) => { this.oauth2Clients = OAuth2Clients; diff --git a/ui-ngx/src/app/core/http/admin.service.ts b/ui-ngx/src/app/core/http/admin.service.ts index 9423392773..f575f56906 100644 --- a/ui-ngx/src/app/core/http/admin.service.ts +++ b/ui-ngx/src/app/core/http/admin.service.ts @@ -18,14 +18,7 @@ import { Injectable } from '@angular/core'; import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; -import { - AdminSettings, - ClientProviderTemplated, - MailServerSettings, - OAuth2Settings, - SecuritySettings, - UpdateMessage -} from '@shared/models/settings.models'; +import { AdminSettings, MailServerSettings, SecuritySettings, UpdateMessage } from '@shared/models/settings.models'; @Injectable({ providedIn: 'root' @@ -60,19 +53,6 @@ export class AdminService { defaultHttpOptionsFromConfig(config)); } - public getOAuth2Settings(config?: RequestConfig): Observable { - return this.http.get(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config)); - } - - public getOAuth2Template(config?: RequestConfig): Observable> { - return this.http.get>(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config)); - } - - public saveOAuth2Settings(OAuth2Setting: OAuth2Settings, config?: RequestConfig): Observable { - return this.http.post('/api/oauth2/config', OAuth2Setting, - defaultHttpOptionsFromConfig(config)); - } - public checkUpdates(config?: RequestConfig): Observable { return this.http.get(`/api/admin/updates`, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/core/http/oauth2.service.ts b/ui-ngx/src/app/core/http/oauth2.service.ts new file mode 100644 index 0000000000..a8f890223d --- /dev/null +++ b/ui-ngx/src/app/core/http/oauth2.service.ts @@ -0,0 +1,44 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; +import { Observable } from 'rxjs'; +import { OAuth2ClientRegistrationTemplate, OAuth2ClientsParams } from '@shared/models/oauth2.models'; + +@Injectable({ + providedIn: 'root' +}) +export class OAuth2Service { + + constructor( + private http: HttpClient + ) { } + + public getOAuth2Settings(config?: RequestConfig): Observable { + return this.http.get(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config)); + } + + public getOAuth2Template(config?: RequestConfig): Observable> { + return this.http.get>(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config)); + } + + public saveOAuth2Settings(OAuth2Setting: OAuth2ClientsParams, config?: RequestConfig): Observable { + return this.http.post('/api/oauth2/config', OAuth2Setting, + defaultHttpOptionsFromConfig(config)); + } +} diff --git a/ui-ngx/src/app/core/http/public-api.ts b/ui-ngx/src/app/core/http/public-api.ts index db477c3a8c..10c6b5429e 100644 --- a/ui-ngx/src/app/core/http/public-api.ts +++ b/ui-ngx/src/app/core/http/public-api.ts @@ -28,6 +28,7 @@ export * from './entity-relation.service'; export * from './entity-view.service'; export * from './event.service'; export * from './http-utils'; +export * from './oauth2.service'; export * from './queue.service'; export * from './rule-chain.service'; export * from './tenant.service'; diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html index 30564e9306..9274b5cbfc 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -33,7 +33,7 @@ {{ 'admin.oauth2.enable' | translate }} -
+
@@ -59,7 +59,7 @@
-
+
@@ -160,7 +160,7 @@ -
+
@@ -182,9 +182,9 @@
- + [disabled]="isCustomProvider(registration)" + [expanded]="isCustomProvider(registration)"> + {{ 'admin.oauth2.custom-setting' | translate }} @@ -199,7 +199,7 @@ @@ -216,7 +216,7 @@ @@ -235,7 +235,7 @@ @@ -249,7 +249,7 @@ @@ -271,7 +271,7 @@ -
+
admin.oauth2.login-button-label admin.oauth2.type - - {{ converterTypeExternalUser }} + + {{ mapperConfigType }}
- + *ngIf="registration.get('mapperConfig.type').value !== mapperConfigType.CUSTOM"> + admin.oauth2.email-attribute-key
+ *ngIf="registration.get('mapperConfig.type').value === mapperConfigType.CUSTOM"> admin.oauth2.url diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss index 0ad644f61b..0358badb1c 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss @@ -41,6 +41,14 @@ padding-bottom: 0; } } + + .mat-expansion-panel { + .mat-expansion-panel-header { + &.mat-expanded { + height: 48px; + } + } + } } :host ::ng-deep{ @@ -49,9 +57,6 @@ .mat-expansion-panel-body{ padding: 0 2px 1em; } - .mat-tab-label{ - text-transform: none; - } .mat-form-field-suffix .mat-icon-button .mat-icon{ font-size: 18px; } diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index 11b39468e9..75a25a5eff 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -18,22 +18,21 @@ import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ClientAuthenticationMethod, - ClientProviderTemplated, ClientRegistration, DomainInfo, DomainSchema, domainSchemaTranslations, - DomainsParam, MapperConfig, MapperConfigBasic, MapperConfigCustom, MapperConfigType, - OAuth2Settings, + OAuth2ClientRegistrationTemplate, + OAuth2ClientsDomainParams, + OAuth2ClientsParams, TenantNameStrategy -} from '@shared/models/settings.models'; +} from '@shared/models/oauth2.models'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { AdminService } from '@core/http/admin.service'; import { PageComponent } from '@shared/components/page.component'; import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; import { COMMA, ENTER } from '@angular/cdk/keycodes'; @@ -42,7 +41,8 @@ import { WINDOW } from '@core/services/window.service'; import { forkJoin, Subscription } from 'rxjs'; import { DialogService } from '@core/services/dialog.service'; import { TranslateService } from '@ngx-translate/core'; -import { isDefined } from '@core/utils'; +import { isDefined, isDefinedAndNotNull } from '@core/utils'; +import { OAuth2Service } from '@core/http/oauth2.service'; @Component({ selector: 'tb-oauth2-settings', @@ -53,7 +53,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha private URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.,?+=&%@\-/]*)?$/; private subscriptions: Subscription[] = []; - private templates = new Map(); + private templates = new Map(); private defaultProvider = { additionalInfo: { providerName: 'Custom' @@ -75,10 +75,11 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha readonly separatorKeysCodes: number[] = [ENTER, COMMA]; oauth2SettingsForm: FormGroup; - oauth2Settings: OAuth2Settings; + auth2ClientsParams: OAuth2ClientsParams; clientAuthenticationMethods = Object.keys(ClientAuthenticationMethod); - converterTypesExternalUser = Object.keys(MapperConfigType); + mapperConfigType = MapperConfigType; + mapperConfigTypes = Object.keys(MapperConfigType); tenantNameStrategies = Object.keys(TenantNameStrategy); protocols = Object.keys(DomainSchema); domainSchemaTranslations = domainSchemaTranslations; @@ -86,7 +87,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha templateProvider = ['Custom']; constructor(protected store: Store, - private adminService: AdminService, + private oauth2Service: OAuth2Service, private fb: FormBuilder, private dialogService: DialogService, private translate: TranslateService, @@ -97,13 +98,13 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha ngOnInit(): void { this.buildOAuth2SettingsForm(); forkJoin([ - this.adminService.getOAuth2Template(), - this.adminService.getOAuth2Settings() + this.oauth2Service.getOAuth2Template(), + this.oauth2Service.getOAuth2Settings() ]).subscribe( - ([templates, oauth2Settings]) => { + ([templates, auth2ClientsParams]) => { this.initTemplates(templates); - this.oauth2Settings = oauth2Settings; - this.initOAuth2Settings(this.oauth2Settings); + this.auth2ClientsParams = auth2ClientsParams; + this.initOAuth2Settings(this.auth2ClientsParams); } ); } @@ -115,8 +116,11 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha }); } - private initTemplates(templates: ClientProviderTemplated[]): void { - templates.map(provider => this.templates.set(provider.name, provider)); + private initTemplates(templates: OAuth2ClientRegistrationTemplate[]): void { + templates.map(provider => { + delete provider.additionalInfo; + this.templates.set(provider.name, provider); + }); this.templateProvider.push(...Array.from(this.templates.keys())); this.templateProvider.sort(); } @@ -169,10 +173,10 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha }); } - private initOAuth2Settings(oauth2Settings: OAuth2Settings): void { - if (oauth2Settings) { - this.oauth2SettingsForm.patchValue({enabled: oauth2Settings.enabled}, {emitEvent: false}); - oauth2Settings.domainsParams.forEach((domain) => { + private initOAuth2Settings(auth2ClientsParams: OAuth2ClientsParams): void { + if (auth2ClientsParams) { + this.oauth2SettingsForm.patchValue({enabled: auth2ClientsParams.enabled}, {emitEvent: false}); + auth2ClientsParams.domainsParams.forEach((domain) => { this.domainsParams.push(this.buildDomainsForm(domain)); }); } @@ -204,17 +208,17 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha return this.translate.instant('admin.oauth2.new-domain'); } - private buildDomainsForm(domainParams?: DomainsParam): FormGroup { + private buildDomainsForm(auth2ClientsDomainParams?: OAuth2ClientsDomainParams): FormGroup { const formDomain = this.fb.group({ domainInfos: this.fb.array([], Validators.required), clientRegistrations: this.fb.array([], Validators.required) }); - if (domainParams) { - domainParams.domainInfos.forEach((domain) => { + if (auth2ClientsDomainParams) { + auth2ClientsDomainParams.domainInfos.forEach((domain) => { this.clientDomainInfos(formDomain).push(this.buildDomainForm(domain)); }); - domainParams.clientRegistrations.forEach((registration) => { + auth2ClientsDomainParams.clientRegistrations.forEach((registration) => { this.clientDomainProviders(formDomain).push(this.buildProviderForm(registration)); }); } else { @@ -235,10 +239,10 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha return domain; } - private buildProviderForm(registrationData?: ClientRegistration): FormGroup { + private buildProviderForm(clientRegistration?: ClientRegistration): FormGroup { let additionalInfo = null; - if (registrationData?.additionalInfo) { - additionalInfo = JSON.parse(registrationData.additionalInfo); + if (isDefinedAndNotNull(clientRegistration?.additionalInfo)) { + additionalInfo = clientRegistration.additionalInfo; if (this.templateProvider.indexOf(additionalInfo.providerName) === -1) { additionalInfo.providerName = 'Custom'; } @@ -248,73 +252,70 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha defaultProviderName = 'Google'; } - const clientRegistration = this.fb.group({ - id: this.fb.group({ - id: [registrationData?.id?.id ? registrationData.id.id : null], - entityType: [registrationData?.id?.entityType ? registrationData.id.entityType : null] - }), + const clientRegistrationFormGroup = this.fb.group({ additionalInfo: this.fb.group({ providerName: [additionalInfo?.providerName ? additionalInfo?.providerName : defaultProviderName, Validators.required] }), - loginButtonLabel: [registrationData?.loginButtonLabel ? registrationData.loginButtonLabel : null, Validators.required], - loginButtonIcon: [registrationData?.loginButtonIcon ? registrationData.loginButtonIcon : null], - clientId: [registrationData?.clientId ? registrationData.clientId : '', Validators.required], - clientSecret: [registrationData?.clientSecret ? registrationData.clientSecret : '', Validators.required], - accessTokenUri: [registrationData?.accessTokenUri ? registrationData.accessTokenUri : '', + loginButtonLabel: [clientRegistration?.loginButtonLabel ? clientRegistration.loginButtonLabel : null, Validators.required], + loginButtonIcon: [clientRegistration?.loginButtonIcon ? clientRegistration.loginButtonIcon : null], + clientId: [clientRegistration?.clientId ? clientRegistration.clientId : '', Validators.required], + clientSecret: [clientRegistration?.clientSecret ? clientRegistration.clientSecret : '', Validators.required], + accessTokenUri: [clientRegistration?.accessTokenUri ? clientRegistration.accessTokenUri : '', [Validators.required, Validators.pattern(this.URL_REGEXP)]], - authorizationUri: [registrationData?.authorizationUri ? registrationData.authorizationUri : '', + authorizationUri: [clientRegistration?.authorizationUri ? clientRegistration.authorizationUri : '', [Validators.required, Validators.pattern(this.URL_REGEXP)]], - scope: this.fb.array(registrationData?.scope ? registrationData.scope : [], Validators.required), - jwkSetUri: [registrationData?.jwkSetUri ? registrationData.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)], - userInfoUri: [registrationData?.userInfoUri ? registrationData.userInfoUri : '', + scope: this.fb.array(clientRegistration?.scope ? clientRegistration.scope : [], Validators.required), + jwkSetUri: [clientRegistration?.jwkSetUri ? clientRegistration.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)], + userInfoUri: [clientRegistration?.userInfoUri ? clientRegistration.userInfoUri : '', [Validators.required, Validators.pattern(this.URL_REGEXP)]], clientAuthenticationMethod: [ - registrationData?.clientAuthenticationMethod ? registrationData.clientAuthenticationMethod : ClientAuthenticationMethod.POST, + clientRegistration?.clientAuthenticationMethod ? clientRegistration.clientAuthenticationMethod : ClientAuthenticationMethod.POST, Validators.required], userNameAttributeName: [ - registrationData?.userNameAttributeName ? registrationData.userNameAttributeName : 'email', Validators.required], + clientRegistration?.userNameAttributeName ? clientRegistration.userNameAttributeName : 'email', Validators.required], mapperConfig: this.fb.group({ - allowUserCreation: [registrationData?.mapperConfig?.allowUserCreation ? registrationData.mapperConfig.allowUserCreation : true], - activateUser: [registrationData?.mapperConfig?.activateUser ? registrationData.mapperConfig.activateUser : false], - type: [registrationData?.mapperConfig?.type ? registrationData.mapperConfig.type : MapperConfigType.BASIC, Validators.required] + allowUserCreation: [ + clientRegistration?.mapperConfig?.allowUserCreation ? clientRegistration.mapperConfig.allowUserCreation : true + ], + activateUser: [clientRegistration?.mapperConfig?.activateUser ? clientRegistration.mapperConfig.activateUser : false], + type: [ + clientRegistration?.mapperConfig?.type ? clientRegistration.mapperConfig.type : MapperConfigType.BASIC, Validators.required + ] } ) }); - if (registrationData) { - this.changeMapperConfigType(clientRegistration, registrationData.mapperConfig.type, registrationData.mapperConfig); + if (clientRegistration) { + this.changeMapperConfigType(clientRegistrationFormGroup, clientRegistration.mapperConfig.type, clientRegistration.mapperConfig); } else { - this.changeMapperConfigType(clientRegistration, MapperConfigType.BASIC); - this.setProviderDefaultValue(defaultProviderName, clientRegistration); + this.changeMapperConfigType(clientRegistrationFormGroup, MapperConfigType.BASIC); + this.setProviderDefaultValue(defaultProviderName, clientRegistrationFormGroup); } - this.subscriptions.push(clientRegistration.get('mapperConfig.type').valueChanges.subscribe((value) => { - this.changeMapperConfigType(clientRegistration, value); + this.subscriptions.push(clientRegistrationFormGroup.get('mapperConfig.type').valueChanges.subscribe((value) => { + this.changeMapperConfigType(clientRegistrationFormGroup, value); })); - this.subscriptions.push(clientRegistration.get('additionalInfo.providerName').valueChanges.subscribe((provider) => { - (clientRegistration.get('scope') as FormArray).clear(); - this.setProviderDefaultValue(provider, clientRegistration); + this.subscriptions.push(clientRegistrationFormGroup.get('additionalInfo.providerName').valueChanges.subscribe((provider) => { + (clientRegistrationFormGroup.get('scope') as FormArray).clear(); + this.setProviderDefaultValue(provider, clientRegistrationFormGroup); })); - return clientRegistration; + return clientRegistrationFormGroup; } private setProviderDefaultValue(provider: string, clientRegistration: FormGroup) { if (provider === 'Custom') { - const defaultSettings = {...this.defaultProvider, ...{id: clientRegistration.get('id').value}}; - clientRegistration.reset(defaultSettings, {emitEvent: false}); + clientRegistration.reset(this.defaultProvider, {emitEvent: false}); clientRegistration.get('accessTokenUri').enable(); clientRegistration.get('authorizationUri').enable(); clientRegistration.get('jwkSetUri').enable(); clientRegistration.get('userInfoUri').enable(); } else { const template = this.templates.get(provider); - delete template.id; - delete template.additionalInfo; template.clientId = ''; template.clientSecret = ''; template.scope.forEach(() => { @@ -324,44 +325,33 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha clientRegistration.get('authorizationUri').disable(); clientRegistration.get('jwkSetUri').disable(); clientRegistration.get('userInfoUri').disable(); - clientRegistration.patchValue(template, {emitEvent: false}); + clientRegistration.patchValue(template); } } private changeMapperConfigType(control: AbstractControl, type: MapperConfigType, predefinedValue?: MapperConfig) { const mapperConfig = control.get('mapperConfig') as FormGroup; - if (type === MapperConfigType.BASIC) { - mapperConfig.removeControl('custom'); - mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic)); - } else { + if (type === MapperConfigType.CUSTOM) { mapperConfig.removeControl('basic'); mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom)); + } else { + mapperConfig.removeControl('custom'); + mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic)); } } save(): void { - const setting = this.prepareFormValue(this.oauth2SettingsForm.getRawValue()); - this.adminService.saveOAuth2Settings(setting).subscribe( + const setting = this.oauth2SettingsForm.getRawValue(); + this.oauth2Service.saveOAuth2Settings(setting).subscribe( (oauth2Settings) => { - this.oauth2Settings = oauth2Settings; - this.oauth2SettingsForm.markAsPristine(); + this.auth2ClientsParams = oauth2Settings; + this.oauth2SettingsForm.patchValue(this.oauth2SettingsForm, {emitEvent: false}); this.oauth2SettingsForm.markAsUntouched(); + this.oauth2SettingsForm.markAsPristine(); } ); } - private prepareFormValue(formValue: OAuth2Settings): OAuth2Settings{ - formValue.domainsParams.forEach((setting, index) => { - setting.clientRegistrations.forEach((registration) => { - registration.additionalInfo = JSON.stringify(registration.additionalInfo); - if (registration.id.id === null) { - delete registration.id; - } - }); - }); - return formValue; - } - confirmForm(): FormGroup { return this.oauth2SettingsForm; } @@ -451,6 +441,10 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha return controller.get('additionalInfo.providerName').value; } + isCustomProvider(controller: AbstractControl): boolean { + return this.getProviderName(controller) === 'Custom'; + } + getHelpLink(controller: AbstractControl): string { const provider = controller.get('additionalInfo.providerName').value; if (provider === null || provider === 'Custom') { diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.html b/ui-ngx/src/app/modules/login/pages/login/login.component.html index 3f5438dcd3..35dbb0e401 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.html +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.html @@ -28,6 +28,19 @@
+
+ + + +
+
+
{{ "login.or" | translate | uppercase }}
+
+
+
login.username @@ -41,7 +54,7 @@ lock -
+
@@ -49,19 +62,6 @@
-
-
-
-
{{ "login.or" | translate | uppercase }}
-
-
- - - - {{ 'login.login-with' | translate: {name: oauth2Client.name} }} - - -
diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.scss b/ui-ngx/src/app/modules/login/pages/login/login.component.scss index e8e9fc29f2..6a1c75aaaf 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.scss +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.scss @@ -27,8 +27,11 @@ width: 550px !important; } - .tb-reset-password{ - padding: 0 6px; + .forgot-password { + padding: 0 0.5em 1em; + .tb-reset-password { + padding: 0 6px; + } } .tb-action-button{ @@ -65,9 +68,11 @@ min-width: 20px; } - a.centered { + a.login-with-button { + color: rgba(black, 0.87); + &:hover { - border-bottom: 0px; + border-bottom: 0; } } diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.ts b/ui-ngx/src/app/modules/login/pages/login/login.component.ts index a9717179d6..30ecafe0b2 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.ts @@ -23,7 +23,7 @@ import { FormBuilder } from '@angular/forms'; import { HttpErrorResponse } from '@angular/common/http'; import { Constants } from '@shared/models/constants'; import { Router } from '@angular/router'; -import { OAuth2Client } from '@shared/models/login.models'; +import { OAuth2ClientInfo } from '@shared/models/oauth2.models'; @Component({ selector: 'tb-login', @@ -36,7 +36,7 @@ export class LoginComponent extends PageComponent implements OnInit { username: '', password: '' }); - oauth2Clients: Array = null; + oauth2Clients: Array = null; constructor(protected store: Store, private authService: AuthService, diff --git a/ui-ngx/src/app/shared/models/login.models.ts b/ui-ngx/src/app/shared/models/login.models.ts index 7c22bd3952..19b40fd50e 100644 --- a/ui-ngx/src/app/shared/models/login.models.ts +++ b/ui-ngx/src/app/shared/models/login.models.ts @@ -27,9 +27,3 @@ export interface LoginResponse { token: string; refreshToken: string; } - -export interface OAuth2Client { - name: string; - icon?: string; - url: string; -} diff --git a/ui-ngx/src/app/shared/models/oauth2.models.ts b/ui-ngx/src/app/shared/models/oauth2.models.ts new file mode 100644 index 0000000000..54d4d3c8d7 --- /dev/null +++ b/ui-ngx/src/app/shared/models/oauth2.models.ts @@ -0,0 +1,119 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { HasUUID } from '@shared/models/id/has-uuid'; + +export interface OAuth2ClientsParams { + enabled: boolean; + domainsParams: OAuth2ClientsDomainParams[]; +} + +export interface OAuth2ClientsDomainParams { + clientRegistrations: ClientRegistration[]; + domainInfos: DomainInfo[]; +} + +export interface DomainInfo { + name: string; + scheme: DomainSchema; +} + +export enum DomainSchema{ + HTTP = 'HTTP', + HTTPS = 'HTTPS', + MIXED = 'MIXED' +} + +export const domainSchemaTranslations = new Map( + [ + [DomainSchema.HTTP, 'admin.oauth2.domain-schema-http'], + [DomainSchema.HTTPS, 'admin.oauth2.domain-schema-https'], + [DomainSchema.MIXED, 'admin.oauth2.domain-schema-mixed'] + ] +); + +export enum MapperConfigType{ + BASIC = 'BASIC', + CUSTOM = 'CUSTOM', + GITHUB = 'GITHUB' +} + +export enum TenantNameStrategy{ + DOMAIN = 'DOMAIN', + EMAIL = 'EMAIL', + CUSTOM = 'CUSTOM' +} + +export interface OAuth2ClientRegistrationTemplate extends ClientRegistration{ + comment: string; + createdTime: number; + helpLink: string; + name: string; + providerId: string; + id: HasUUID; +} + +export interface ClientRegistration { + loginButtonLabel: string; + loginButtonIcon: string; + clientId: string; + clientSecret: string; + accessTokenUri: string; + authorizationUri: string; + scope: string[]; + jwkSetUri?: string; + userInfoUri: string; + clientAuthenticationMethod: ClientAuthenticationMethod; + userNameAttributeName: string; + mapperConfig: MapperConfig; + additionalInfo: string; +} + +export enum ClientAuthenticationMethod { + BASIC = 'BASIC', + POST = 'POST' +} + +export interface MapperConfig { + allowUserCreation: boolean; + activateUser: boolean; + type: MapperConfigType; + basic?: MapperConfigBasic; + custom?: MapperConfigCustom; +} + +export interface MapperConfigBasic { + emailAttributeKey: string; + firstNameAttributeKey?: string; + lastNameAttributeKey?: string; + tenantNameStrategy: TenantNameStrategy; + tenantNamePattern?: string; + customerNamePattern?: string; + defaultDashboardName?: string; + alwaysFullScreen?: boolean; +} + +export interface MapperConfigCustom { + url: string; + username?: string; + password?: string; +} + +export interface OAuth2ClientInfo { + name: string; + icon?: string; + url: string; +} diff --git a/ui-ngx/src/app/shared/models/public-api.ts b/ui-ngx/src/app/shared/models/public-api.ts index cbc030a4fa..c2130236c1 100644 --- a/ui-ngx/src/app/shared/models/public-api.ts +++ b/ui-ngx/src/app/shared/models/public-api.ts @@ -36,6 +36,7 @@ export * from './error.models'; export * from './event.models'; export * from './login.models'; export * from './material.models'; +export * from './oauth2.models'; export * from './queue.models'; export * from './relation.models'; export * from './rule-chain.models'; diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index f31dc5ebe8..f71ac1bb2e 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -14,9 +14,6 @@ /// limitations under the License. /// -import { EntityId } from '@shared/models/id/entity-id'; -import { TenantId } from '@shared/models/id/tenant-id'; - export const smtpPortPattern: RegExp = /^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/; export interface AdminSettings { @@ -63,99 +60,3 @@ export interface UpdateMessage { message: string; updateAvailable: boolean; } - -export interface OAuth2Settings { - enabled: boolean; - domainsParams: DomainsParam[]; -} - -export interface DomainsParam { - clientRegistrations: ClientRegistration[]; - domainInfos: DomainInfo[]; -} - -export interface DomainInfo { - name: string; - scheme: DomainSchema; -} - -export enum DomainSchema{ - HTTP = 'HTTP', - HTTPS = 'HTTPS', - MIXED = 'MIXED' -} - -export const domainSchemaTranslations = new Map( - [ - [DomainSchema.HTTP, 'admin.oauth2.domain-schema-http'], - [DomainSchema.HTTPS, 'admin.oauth2.domain-schema-https'], - [DomainSchema.MIXED, 'admin.oauth2.domain-schema-mixed'] - ] -); - -export enum MapperConfigType{ - BASIC = 'BASIC', - CUSTOM = 'CUSTOM' -} - -export enum TenantNameStrategy{ - DOMAIN = 'DOMAIN', - EMAIL = 'EMAIL', - CUSTOM = 'CUSTOM' -} - -export interface ClientProviderTemplated extends ClientRegistration{ - comment: string; - createdTime: number; - helpLink: string; - name: string; - providerId: string; - tenantId: TenantId; -} - -export interface ClientRegistration { - loginButtonLabel: string; - loginButtonIcon: string; - clientId: string; - clientSecret: string; - accessTokenUri: string; - authorizationUri: string; - scope: string[]; - jwkSetUri?: string; - userInfoUri: string; - clientAuthenticationMethod: ClientAuthenticationMethod; - userNameAttributeName: string; - mapperConfig: MapperConfig; - id?: EntityId; - additionalInfo: string; -} - -export enum ClientAuthenticationMethod { - BASIC = 'BASIC', - POST = 'POST' -} - -export interface MapperConfig { - allowUserCreation: boolean; - activateUser: boolean; - type: MapperConfigType; - basic?: MapperConfigBasic; - custom?: MapperConfigCustom; -} - -export interface MapperConfigBasic { - emailAttributeKey: string; - firstNameAttributeKey?: string; - lastNameAttributeKey?: string; - tenantNameStrategy: TenantNameStrategy; - tenantNamePattern?: string; - customerNamePattern?: string; - defaultDashboardName?: string; - alwaysFullScreen?: boolean; -} - -export interface MapperConfigCustom { - url: string; - username?: string; - password?: string; -} From df784db8a1481b318757eff457870af136554417 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 12 Oct 2020 19:31:42 +0300 Subject: [PATCH 187/463] Refactoring --- .../src/app/modules/home/components/widget/lib/flot-widget.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts index 7b9ab5117a..b7962a098d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts @@ -20,7 +20,8 @@ import { createLabelFromDatasource, deepClone, insertVariable, - isDefined, isDefinedAndNotNull, + isDefined, + isDefinedAndNotNull, isEqual, isNumber, isUndefined From 054fcba46f58f6b12a9ed347fa4a9b8afecf3222 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 12 Oct 2020 19:33:52 +0300 Subject: [PATCH 188/463] UI: Change OAuth2 templated icon --- ui-ngx/src/app/app.component.ts | 21 +++++++++++++++++++ .../admin/oauth2-settings.component.html | 2 +- .../login/pages/login/login.component.html | 2 +- .../login/pages/login/login.component.scss | 6 ++++++ .../assets/locale/locale.constant-en_US.json | 2 +- 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/app.component.ts b/ui-ngx/src/app/app.component.ts index b46adc095d..f7d290c5ca 100644 --- a/ui-ngx/src/app/app.component.ts +++ b/ui-ngx/src/app/app.component.ts @@ -68,6 +68,27 @@ export class AppComponent implements OnInit { ) ); + this.matIconRegistry.addSvgIconLiteral( + 'google-logo', + this.domSanitizer.bypassSecurityTrustHtml( + '' + ) + ); + + this.matIconRegistry.addSvgIconLiteral( + 'github-logo', + this.domSanitizer.bypassSecurityTrustHtml( + '' + ) + ); + + this.matIconRegistry.addSvgIconLiteral( + 'facebook-logo', + this.domSanitizer.bypassSecurityTrustHtml( + '' + ) + ); + this.storageService.testLocalStorage(); this.setupTranslate(); diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html index 9274b5cbfc..ee1c5b725c 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -275,7 +275,7 @@ admin.oauth2.login-button-label {{ 'admin.oauth2.login-button-label-required' | translate }} diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.html b/ui-ngx/src/app/modules/login/pages/login/login.component.html index 35dbb0e401..c0d4d5ed23 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.html +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.html @@ -31,7 +31,7 @@
diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.scss b/ui-ngx/src/app/modules/login/pages/login/login.component.scss index 6a1c75aaaf..b8fa052e75 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.scss +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.scss @@ -74,6 +74,12 @@ &:hover { border-bottom: 0; } + + .icon{ + height: 20px; + width: 20px; + vertical-align: sub; + } } .centered ::ng-deep .mat-button-wrapper { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 9a5d71a2e6..6c2cb84b80 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -158,7 +158,7 @@ "last-name-attribute-key": "Last name attribute key", "login-button-icon": "Login button icon", "login-button-label": "Provider label", - "login-button-label-1": "Login with $(Provider label)", + "login-button-label-placeholder": "Login with $(Provider label)", "login-button-label-required": "Label is required.", "login-provider": "Login provider", "mapper": "Mapper", From 220c5f627510cdd9c00e5a47233f6f821a3e6d6b Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 12 Oct 2020 19:35:11 +0300 Subject: [PATCH 189/463] UI: Change OAuth2 templated json icon --- .../json/system/oauth2_config_templates/facebook_config.json | 2 +- .../data/json/system/oauth2_config_templates/github_config.json | 2 +- .../data/json/system/oauth2_config_templates/google_config.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json b/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json index 3961bf3688..04847a9da6 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json @@ -17,7 +17,7 @@ } }, "comment": null, - "loginButtonIcon": "mdi:facebook", + "loginButtonIcon": "facebook-logo", "loginButtonLabel": "Facebook", "helpLink": "https://developers.facebook.com/docs/facebook-login/web#logindialog" } diff --git a/application/src/main/data/json/system/oauth2_config_templates/github_config.json b/application/src/main/data/json/system/oauth2_config_templates/github_config.json index 6569162ead..0a1ae5779b 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/github_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/github_config.json @@ -14,7 +14,7 @@ } }, "comment": "In order to log into ThingsBoard you need to have user's email. You may configure and use Custom OAuth2 Mapper to get email information. Please refer to Github Documentation", - "loginButtonIcon": "mdi:github", + "loginButtonIcon": "github-logo", "loginButtonLabel": "Github", "helpLink": "https://docs.github.com/en/developers/apps/creating-an-oauth-app" } diff --git a/application/src/main/data/json/system/oauth2_config_templates/google_config.json b/application/src/main/data/json/system/oauth2_config_templates/google_config.json index 03e3046125..f8626439ba 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/google_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/google_config.json @@ -18,7 +18,7 @@ } }, "comment": null, - "loginButtonIcon": "mdi:google", + "loginButtonIcon": "google-logo", "loginButtonLabel": "Google", "helpLink": "https://developers.google.com/adwords/api/docs/guides/authentication" } From e113daa085dd760a0c034537dab797d4e4023884 Mon Sep 17 00:00:00 2001 From: Vladyslav Prykhodko Date: Tue, 13 Oct 2020 00:59:56 +0300 Subject: [PATCH 190/463] UI: Added validation scope --- .../pages/admin/oauth2-settings.component.html | 4 ++++ .../home/pages/admin/oauth2-settings.component.ts | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html index ee1c5b725c..e9d3030019 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -316,6 +316,10 @@ {{ 'admin.oauth2.scope-required' | translate }} + + diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index 75a25a5eff..400b2d3fc2 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -15,7 +15,7 @@ /// import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; -import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { AbstractControl, FormArray, FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms'; import { ClientAuthenticationMethod, ClientRegistration, @@ -266,7 +266,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha authorizationUri: [clientRegistration?.authorizationUri ? clientRegistration.authorizationUri : '', [Validators.required, Validators.pattern(this.URL_REGEXP)]], - scope: this.fb.array(clientRegistration?.scope ? clientRegistration.scope : [], Validators.required), + scope: this.fb.array(clientRegistration?.scope ? clientRegistration.scope : [], this.validateScope), jwkSetUri: [clientRegistration?.jwkSetUri ? clientRegistration.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)], userInfoUri: [clientRegistration?.userInfoUri ? clientRegistration.userInfoUri : '', [Validators.required, @@ -307,6 +307,16 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha return clientRegistrationFormGroup; } + private validateScope (control: AbstractControl): ValidationErrors | null { + const scope: string[] = control.value; + if (!scope || !scope.length) { + return { + required: true + }; + } + return null; + } + private setProviderDefaultValue(provider: string, clientRegistration: FormGroup) { if (provider === 'Custom') { clientRegistration.reset(this.defaultProvider, {emitEvent: false}); From 29b9682ff1b107fc4cce29553d3894a569d7fdc8 Mon Sep 17 00:00:00 2001 From: Vladyslav Prykhodko Date: Tue, 13 Oct 2020 01:41:13 +0300 Subject: [PATCH 191/463] UI: Added device profile show settings in edit mode --- .../profile/device-profile.component.html | 59 +++++++++++++++++++ .../profile/device-profile.component.ts | 18 ++++++ 2 files changed, 77 insertions(+) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html index 780d98adfe..4fbc413d98 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html @@ -68,6 +68,65 @@ device-profile.description + + device-profile.transport-type + + + {{deviceTransportTypeTranslations.get(type) | translate}} + + + + {{ 'device-profile.transport-type-required' | translate }} + + +
+ + + + +
device-profile.profile-configuration
+
+
+ + +
+ + + +
device-profile.transport-configuration
+
+
+ + +
+ + + +
{{'device-profile.alarm-rules-with-count' | translate: + {count: entityForm.get('profileData.alarms').value ? + entityForm.get('profileData.alarms').value.length : 0} }}
+
+
+ + +
+ + + +
device-profile.device-provisioning
+
+
+ + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts index 38aed4d77a..09223491e1 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts @@ -28,10 +28,12 @@ import { DeviceProfile, DeviceProfileData, DeviceProfileType, + deviceProfileTypeConfigurationInfoMap, deviceProfileTypeTranslationMap, DeviceProvisionConfiguration, DeviceProvisionType, DeviceTransportType, + deviceTransportTypeConfigurationInfoMap, deviceTransportTypeTranslationMap } from '@shared/models/device.models'; import { EntityType } from '@shared/models/entity-type.models'; @@ -57,6 +59,10 @@ export class DeviceProfileComponent extends EntityComponent { deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; + displayProfileConfiguration: boolean; + + displayTransportConfiguration: boolean; + constructor(protected store: Store, protected translate: TranslateService, @Optional() @Inject('entity') protected entityValue: DeviceProfile, @@ -74,6 +80,10 @@ export class DeviceProfileComponent extends EntityComponent { } buildForm(entity: DeviceProfile): FormGroup { + this.displayProfileConfiguration = entity && entity.type && + deviceProfileTypeConfigurationInfoMap.get(entity.type).hasProfileConfiguration; + this.displayTransportConfiguration = entity && entity.transportType && + deviceTransportTypeConfigurationInfoMap.get(entity.transportType).hasProfileConfiguration; const deviceProvisionConfiguration: DeviceProvisionConfiguration = { type: entity?.provisionType ? entity?.provisionType : DeviceProvisionType.DISABLED, provisionDeviceKey: entity?.provisionDeviceKey, @@ -116,6 +126,8 @@ export class DeviceProfileComponent extends EntityComponent { private deviceProfileTypeChanged(form: FormGroup) { const deviceProfileType: DeviceProfileType = form.get('type').value; + this.displayProfileConfiguration = deviceProfileType && + deviceProfileTypeConfigurationInfoMap.get(deviceProfileType).hasProfileConfiguration; let profileData: DeviceProfileData = form.getRawValue().profileData; if (!profileData) { profileData = { @@ -129,6 +141,8 @@ export class DeviceProfileComponent extends EntityComponent { private deviceProfileTransportTypeChanged(form: FormGroup) { const deviceTransportType: DeviceTransportType = form.get('transportType').value; + this.displayTransportConfiguration = deviceTransportType && + deviceTransportTypeConfigurationInfoMap.get(deviceTransportType).hasProfileConfiguration; let profileData: DeviceProfileData = form.getRawValue().profileData; if (!profileData) { profileData = { @@ -141,6 +155,10 @@ export class DeviceProfileComponent extends EntityComponent { } updateForm(entity: DeviceProfile) { + this.displayProfileConfiguration = entity.type && + deviceProfileTypeConfigurationInfoMap.get(entity.type).hasProfileConfiguration; + this.displayTransportConfiguration = entity.transportType && + deviceTransportTypeConfigurationInfoMap.get(entity.transportType).hasProfileConfiguration; const deviceProvisionConfiguration: DeviceProvisionConfiguration = { type: entity?.provisionType ? entity?.provisionType : DeviceProvisionType.DISABLED, provisionDeviceKey: entity?.provisionDeviceKey, From 31676d2110ba43eed338a66783463e122b524798 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 13 Oct 2020 09:09:12 +0300 Subject: [PATCH 192/463] UI: Refactoring show error in device-credentials component --- .../components/device/device-credentials.component.html | 6 +++--- .../home/components/device/device-credentials.component.ts | 7 ++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html index fa0c7e14a2..a8b48e30d1 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html @@ -70,8 +70,8 @@ {{hidePassword ? 'visibility_off' : 'visibility'}}
- - {{ 'device.client-id-or-user-name-necessary' | translate }} - +
diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts index 298270d153..fd6680fe17 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts @@ -174,8 +174,8 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, updateValidators(): void { this.hidePassword = true; - const crendetialsType = this.deviceCredentialsFormGroup.get('credentialsType').value as DeviceCredentialsType; - switch (crendetialsType) { + const credentialsType = this.deviceCredentialsFormGroup.get('credentialsType').value as DeviceCredentialsType; + switch (credentialsType) { case DeviceCredentialsType.ACCESS_TOKEN: this.deviceCredentialsFormGroup.get('credentialsId').setValidators([Validators.required, Validators.pattern(/^.{1,20}$/)]); this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity({emitEvent: false}); @@ -215,9 +215,6 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, const value = this.deviceCredentialsFormGroup.get('credentialsBasic.password').value; if (value !== '') { this.deviceCredentialsFormGroup.get('credentialsBasic.userName').setValidators([Validators.required]); - if (this.deviceCredentialsFormGroup.get('credentialsBasic.userName').untouched) { - this.deviceCredentialsFormGroup.get('credentialsBasic.userName').markAsTouched({onlySelf: true}); - } } else { this.deviceCredentialsFormGroup.get('credentialsBasic.userName').setValidators([]); } From 6ef7e813fe99f22aa4e350040f93d8ffdfe999ae Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 13 Oct 2020 10:02:30 +0300 Subject: [PATCH 193/463] UI: Added catch error get OAuth2 --- ui-ngx/src/app/core/auth/auth.service.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts index da41c7c1b1..1f8788d821 100644 --- a/ui-ngx/src/app/core/auth/auth.service.ts +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -201,10 +201,11 @@ export class AuthService { public loadOAuth2Clients(): Observable> { return this.http.post>(`/api/noauth/oauth2Clients`, null, defaultHttpOptions()).pipe( - tap((OAuth2Clients) => { - this.oauth2Clients = OAuth2Clients; - }) - ); + catchError(err => of([])), + tap((OAuth2Clients) => { + this.oauth2Clients = OAuth2Clients; + }) + ); } private forceDefaultPlace(authState?: AuthState, path?: string, params?: any): boolean { From e0ac59b94b53c0d3c887e8dc0f7f1317a6ff40a6 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 13 Oct 2020 11:08:13 +0300 Subject: [PATCH 194/463] UI: Device provisioning autogenerate device key and secret --- ui-ngx/src/app/core/utils.ts | 12 ++++++++++++ ...vice-profile-provision-configuration.component.ts | 12 ++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index c0a01f3bec..4650caca77 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -403,3 +403,15 @@ export function deepTrim(obj: T): T { return acc; }, (Array.isArray(obj) ? [] : {}) as T); } + +export function generateSecret(length?: number): string { + if (isUndefined(length) || length == null) { + length = 1; + } + const l = length > 10 ? 10 : length; + const str = Math.random().toString(36).substr(2, l); + if (str.length >= length) { + return str; + } + return str.concat(generateSecret(length - str.length)); +} diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index aaaf81bc04..9793af2748 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -32,7 +32,7 @@ import { DeviceProvisionType, deviceProvisionTypeTranslationMap } from "@shared/models/device.models"; -import { isDefinedAndNotNull } from "@core/utils"; +import { generateSecret, isDefinedAndNotNull } from '@core/utils'; @Component({ selector: 'tb-device-profile-provision-configuration', @@ -85,10 +85,18 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu this.provisionConfigurationFormGroup.get('type').valueChanges.subscribe((type) => { if (type === DeviceProvisionType.DISABLED) { this.provisionConfigurationFormGroup.get('provisionDeviceSecret').disable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('provisionDeviceSecret').patchValue(null,{emitEvent: false}); + this.provisionConfigurationFormGroup.get('provisionDeviceSecret').patchValue(null, {emitEvent: false}); this.provisionConfigurationFormGroup.get('provisionDeviceKey').disable({emitEvent: false}); this.provisionConfigurationFormGroup.get('provisionDeviceKey').patchValue(null); } else { + const provisionDeviceSecret: string = this.provisionConfigurationFormGroup.get('provisionDeviceSecret').value; + if (!provisionDeviceSecret || !provisionDeviceSecret.length) { + this.provisionConfigurationFormGroup.get('provisionDeviceSecret').patchValue(generateSecret(20), {emitEvent: false}); + } + const provisionDeviceKey: string = this.provisionConfigurationFormGroup.get('provisionDeviceKey').value; + if (!provisionDeviceKey || !provisionDeviceKey.length) { + this.provisionConfigurationFormGroup.get('provisionDeviceKey').patchValue(generateSecret(20), {emitEvent: false}); + } this.provisionConfigurationFormGroup.get('provisionDeviceSecret').enable({emitEvent: false}); this.provisionConfigurationFormGroup.get('provisionDeviceKey').enable({emitEvent: false}); } From ff4ca5a760c8cde794788e8284a6746537c94f1a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 13 Oct 2020 11:12:12 +0300 Subject: [PATCH 195/463] UI: Update transport type hints --- ui-ngx/src/assets/locale/locale.constant-en_US.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index d4bae7064e..bc7c84981b 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -862,9 +862,9 @@ "transport-type": "Transport type", "transport-type-required": "Transport type is required.", "transport-type-default": "Default", - "transport-type-default-hint": "Refers to already existing transport configuration or basic MQTT, HTTP, CoAP settings", + "transport-type-default-hint": "Supports basic MQTT, HTTP and CoAP transport", "transport-type-mqtt": "MQTT", - "transport-type-mqtt-hint": "Advanced MQTT topic filters and Device profile settings", + "transport-type-mqtt-hint": "Enables advanced MQTT transport settings", "transport-type-lwm2m": "LWM2M", "transport-type-lwm2m-hint": "LWM2M transport type", "description": "Description", From 6f80cf95e2699c0d36bc8e378158c6ac7f8ce622 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 13 Oct 2020 11:53:22 +0300 Subject: [PATCH 196/463] Tenant Profile Cache --- .../server/actors/ActorSystemContext.java | 5 + .../server/actors/app/AppActor.java | 33 ++++-- .../server/actors/tenant/TenantActor.java | 4 +- .../server/controller/BaseController.java | 4 + .../server/controller/TenantController.java | 2 + .../controller/TenantProfileController.java | 7 +- .../profile/DefaultTbTenantProfileCache.java | 106 ++++++++++++++++++ .../service/profile/TbTenantProfileCache.java | 38 +++++++ .../DefaultTenantRoutingInfoService.java | 10 +- .../transport/DefaultTransportApiService.java | 13 +-- 10 files changed, 195 insertions(+), 27 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java create mode 100644 application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 8f6a290af4..d35dadd16f 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -72,6 +72,7 @@ import org.thingsboard.server.service.executors.ExternalCallExecutorService; import org.thingsboard.server.service.executors.SharedEventLoopGroupService; import org.thingsboard.server.service.mail.MailExecutorService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; +import org.thingsboard.server.service.profile.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; @@ -127,6 +128,10 @@ public class ActorSystemContext { @Getter private DeviceService deviceService; + @Autowired + @Getter + private TbTenantProfileCache tenantProfileCache; + @Autowired @Getter private TbDeviceProfileCache deviceProfileCache; diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java index 16beb3a045..826596db56 100644 --- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.MsgType; @@ -40,7 +41,9 @@ import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg; import org.thingsboard.server.common.msg.queue.RuleEngineException; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.service.profile.TbTenantProfileCache; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; import java.util.HashSet; @@ -50,13 +53,14 @@ import java.util.Set; @Slf4j public class AppActor extends ContextAwareActor { - private static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID); + private final TbTenantProfileCache tenantProfileCache; private final TenantService tenantService; private final Set deletedTenants; private boolean ruleChainsInitialized; private AppActor(ActorSystemContext systemContext) { super(systemContext); + this.tenantProfileCache = systemContext.getTenantProfileCache(); this.tenantService = systemContext.getTenantService(); this.deletedTenants = new HashSet<>(); } @@ -117,8 +121,7 @@ public class AppActor extends ContextAwareActor { boolean isRuleEngine = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE); boolean isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE); for (Tenant tenant : tenantIterator) { - // TODO: Tenant Profile from cache - TenantProfile tenantProfile = systemContext.getTenantProfileService().findTenantProfileById(TenantId.SYS_TENANT_ID, tenant.getTenantProfileId()); + TenantProfile tenantProfile = tenantProfileCache.get(tenant.getTenantProfileId()); if (isCore || (isRuleEngine && !tenantProfile.isIsolatedTbRuleEngine())) { log.debug("[{}] Creating tenant actor", tenant.getId()); getOrCreateTenantActor(tenant.getId()); @@ -133,7 +136,7 @@ public class AppActor extends ContextAwareActor { } private void onQueueToRuleEngineMsg(QueueToRuleEngineMsg msg) { - if (SYSTEM_TENANT.equals(msg.getTenantId())) { + if (TenantId.SYS_TENANT_ID.equals(msg.getTenantId())) { msg.getTbMsg().getCallback().onFailure(new RuleEngineException("Message has system tenant id!")); } else { if (!deletedTenants.contains(msg.getTenantId())) { @@ -146,15 +149,23 @@ public class AppActor extends ContextAwareActor { private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { TbActorRef target = null; - if (SYSTEM_TENANT.equals(msg.getTenantId())) { - log.warn("Message has system tenant id: {}", msg); + if (TenantId.SYS_TENANT_ID.equals(msg.getTenantId())) { + if (msg.getEntityId().getEntityType() == EntityType.TENANT_PROFILE) { + tenantProfileCache.evict(new TenantProfileId(msg.getEntityId().getId())); + } else { + log.warn("Message has system tenant id: {}", msg); + } } else { - if (msg.getEntityId().getEntityType() == EntityType.TENANT - && msg.getEvent() == ComponentLifecycleEvent.DELETED) { - log.info("[{}] Handling tenant deleted notification: {}", msg.getTenantId(), msg); + if (msg.getEntityId().getEntityType() == EntityType.TENANT) { TenantId tenantId = new TenantId(msg.getEntityId().getId()); - deletedTenants.add(tenantId); - ctx.stop(new TbEntityActorId(tenantId)); + tenantProfileCache.evict(tenantId); + if (msg.getEvent() == ComponentLifecycleEvent.DELETED) { + log.info("[{}] Handling tenant deleted notification: {}", msg.getTenantId(), msg); + deletedTenants.add(tenantId); + ctx.stop(new TbEntityActorId(tenantId)); + } else { + target = getOrCreateTenantActor(msg.getTenantId()); + } } else { target = getOrCreateTenantActor(msg.getTenantId()); } diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java index 128280e9bc..161239be7e 100644 --- a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java @@ -77,9 +77,7 @@ public class TenantActor extends RuleChainManagerActor { // This Service may be started for specific tenant only. Optional isolatedTenantId = systemContext.getServiceInfoProvider().getIsolatedTenant(); - // TODO: Tenant Profile from cache - - TenantProfile tenantProfile = systemContext.getTenantProfileService().findTenantProfileById(tenantId, tenant.getTenantProfileId()); + TenantProfile tenantProfile = systemContext.getTenantProfileCache().get(tenant.getTenantProfileId()); isRuleEngineForCurrentTenant = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE); isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE); diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 703bb39eb0..b8fe956217 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -94,6 +94,7 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; +import org.thingsboard.server.service.profile.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.AccessControlService; @@ -205,6 +206,9 @@ public abstract class BaseController { @Autowired protected TbQueueProducerProvider producerProvider; + @Autowired + protected TbTenantProfileCache tenantProfileCache; + @Autowired protected TbDeviceProfileCache deviceProfileCache; diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantController.java b/application/src/main/java/org/thingsboard/server/controller/TenantController.java index ebb46778c9..7c23545374 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantController.java @@ -91,6 +91,7 @@ public class TenantController extends BaseController { if (newTenant) { installScripts.createDefaultRuleChains(tenant.getId()); } + tenantProfileCache.evict(tenant.getId()); return tenant; } catch (Exception e) { throw handleException(e); @@ -106,6 +107,7 @@ public class TenantController extends BaseController { TenantId tenantId = new TenantId(toUUID(strTenantId)); checkTenantId(tenantId, Operation.DELETE); tenantService.deleteTenant(tenantId); + tenantProfileCache.evict(tenantId); tbClusterService.onEntityStateChange(tenantId, tenantId, ComponentLifecycleEvent.DELETED); } catch (Exception e) { throw handleException(e); diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java index dfe0b19a42..4c9d541ea6 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java @@ -29,9 +29,11 @@ import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -93,8 +95,11 @@ public class TenantProfileController extends BaseController { } tenantProfile = checkNotNull(tenantProfileService.saveTenantProfile(getTenantId(), tenantProfile)); + tenantProfileCache.put(tenantProfile); + tbClusterService.onEntityStateChange(TenantId.SYS_TENANT_ID, tenantProfile.getId(), + newTenantProfile ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); return tenantProfile; - } catch (Exception e) { + } catch (Exception e) { throw handleException(e); } } diff --git a/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java b/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java new file mode 100644 index 0000000000..4260e36400 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java @@ -0,0 +1,106 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.profile; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; +import org.thingsboard.server.dao.device.DeviceProfileService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.tenant.TenantProfileService; +import org.thingsboard.server.dao.tenant.TenantService; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +@Service +@Slf4j +public class DefaultTbTenantProfileCache implements TbTenantProfileCache { + + private final Lock tenantProfileFetchLock = new ReentrantLock(); + private final TenantProfileService tenantProfileService; + private final TenantService tenantService; + + private final ConcurrentMap tenantProfilesMap = new ConcurrentHashMap<>(); + private final ConcurrentMap tenantsMap = new ConcurrentHashMap<>(); + + public DefaultTbTenantProfileCache(TenantProfileService tenantProfileService, TenantService tenantService) { + this.tenantProfileService = tenantProfileService; + this.tenantService = tenantService; + } + + @Override + public TenantProfile get(TenantProfileId tenantProfileId) { + TenantProfile profile = tenantProfilesMap.get(tenantProfileId); + if (profile == null) { + profile = tenantProfilesMap.get(tenantProfileId); + if (profile == null) { + tenantProfileFetchLock.lock(); + try { + profile = tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, tenantProfileId); + if (profile != null) { + tenantProfilesMap.put(tenantProfileId, profile); + } + } finally { + tenantProfileFetchLock.unlock(); + } + } + } + return profile; + } + + @Override + public TenantProfile get(TenantId tenantId) { + TenantProfileId profileId = tenantsMap.get(tenantId); + if (profileId == null) { + Tenant tenant = tenantService.findTenantById(tenantId); + if (tenant != null) { + profileId = tenant.getTenantProfileId(); + tenantsMap.put(tenantId, profileId); + } else { + return null; + } + } + return get(profileId); + } + + @Override + public void put(TenantProfile profile) { + if (profile.getId() != null) { + tenantProfilesMap.put(profile.getId(), profile); + } + } + + @Override + public void evict(TenantProfileId profileId) { + tenantProfilesMap.remove(profileId); + } + + @Override + public void evict(TenantId tenantId) { + tenantsMap.remove(tenantId); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java b/application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java new file mode 100644 index 0000000000..d9f944012f --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.profile; + +import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; + +public interface TbTenantProfileCache { + + TenantProfile get(TenantId tenantId); + + TenantProfile get(TenantProfileId tenantProfileId); + + void put(TenantProfile profile); + + void evict(TenantProfileId id); + + void evict(TenantId id); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java index f89e05a57b..4d5219fbfe 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java @@ -25,6 +25,7 @@ import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.queue.discovery.TenantRoutingInfo; import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; +import org.thingsboard.server.service.profile.TbTenantProfileCache; @Slf4j @Service @@ -33,19 +34,18 @@ public class DefaultTenantRoutingInfoService implements TenantRoutingInfoService private final TenantService tenantService; - private final TenantProfileService tenantProfileService; + private final TbTenantProfileCache tenantProfileCache; - public DefaultTenantRoutingInfoService(TenantService tenantService, TenantProfileService tenantProfileService) { + public DefaultTenantRoutingInfoService(TenantService tenantService, TbTenantProfileCache tenantProfileCache) { this.tenantService = tenantService; - this.tenantProfileService = tenantProfileService; + this.tenantProfileCache = tenantProfileCache; } @Override public TenantRoutingInfo getRoutingInfo(TenantId tenantId) { Tenant tenant = tenantService.findTenantById(tenantId); if (tenant != null) { - // TODO: Tenant Profile from cache - TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(tenantId, tenant.getTenantProfileId()); + TenantProfile tenantProfile = tenantProfileCache.get(tenant.getTenantProfileId()); return new TenantRoutingInfo(tenantId, tenantProfile.isIsolatedTbCore(), tenantProfile.isIsolatedTbRuleEngine()); } else { throw new RuntimeException("Tenant not found!"); diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 7fa4630359..c806757ddb 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -70,6 +70,7 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.service.executors.DbCallbackExecutorService; +import org.thingsboard.server.service.profile.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.state.DeviceStateService; @@ -92,7 +93,7 @@ public class DefaultTransportApiService implements TransportApiService { //TODO: Constructor dependencies; private final DeviceProfileService deviceProfileService; private final TenantService tenantService; - private final TenantProfileService tenantProfileService; + private final TbTenantProfileCache tenantProfileCache; private final DeviceService deviceService; private final RelationService relationService; private final DeviceCredentialsService deviceCredentialsService; @@ -106,14 +107,14 @@ public class DefaultTransportApiService implements TransportApiService { private final ConcurrentMap deviceCreationLocks = new ConcurrentHashMap<>(); public DefaultTransportApiService(DeviceProfileService deviceProfileService, TenantService tenantService, - TenantProfileService tenantProfileService, DeviceService deviceService, + TbTenantProfileCache tenantProfileCache, DeviceService deviceService, RelationService relationService, DeviceCredentialsService deviceCredentialsService, DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService, TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService, DeviceProvisionService deviceProvisionService) { this.deviceProfileService = deviceProfileService; this.tenantService = tenantService; - this.tenantProfileService = tenantProfileService; + this.tenantProfileCache = tenantProfileCache; this.deviceService = deviceService; this.relationService = relationService; this.deviceCredentialsService = deviceCredentialsService; @@ -321,10 +322,8 @@ public class DefaultTransportApiService implements TransportApiService { private ListenableFuture handle(GetTenantRoutingInfoRequestMsg requestMsg) { TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB())); - // TODO: Tenant Profile from cache - ListenableFuture tenantProfileFuture = - Futures.transform(tenantService.findTenantByIdAsync(TenantId.SYS_TENANT_ID, tenantId), tenant -> - tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, tenant.getTenantProfileId()), dbCallbackExecutorService); + + ListenableFuture tenantProfileFuture = Futures.immediateFuture(tenantProfileCache.get(tenantId)); return Futures.transform(tenantProfileFuture, tenantProfile -> TransportApiResponseMsg.newBuilder() .setGetTenantRoutingInfoResponseMsg(GetTenantRoutingInfoResponseMsg.newBuilder().setIsolatedTbCore(tenantProfile.isIsolatedTbCore()) .setIsolatedTbRuleEngine(tenantProfile.isIsolatedTbRuleEngine()).build()).build(), dbCallbackExecutorService); From 390bbfd67f91b7792aa3d5ed16a06d3ba60bd321 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 13 Oct 2020 12:01:47 +0300 Subject: [PATCH 197/463] UI: Fixed disabled form in edit device-profile --- ...ce-profile-provision-configuration.component.ts | 14 +++++++++----- .../components/profile/device-profile.component.ts | 2 -- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index 9793af2748..e25c07e005 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, OnInit } from "@angular/core"; +import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { ControlValueAccessor, FormBuilder, @@ -25,13 +25,13 @@ import { ValidationErrors, Validator, Validators -} from "@angular/forms"; -import { coerceBooleanProperty } from "@angular/cdk/coercion"; +} from '@angular/forms'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { DeviceProvisionConfiguration, DeviceProvisionType, deviceProvisionTypeTranslationMap -} from "@shared/models/device.models"; +} from '@shared/models/device.models'; import { generateSecret, isDefinedAndNotNull } from '@core/utils'; @Component({ @@ -126,7 +126,11 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu if (this.disabled){ this.provisionConfigurationFormGroup.disable(); } else { - this.provisionConfigurationFormGroup.enable({emitEvent: false}); + if (this.provisionConfigurationFormGroup.get('type').value !== DeviceProvisionType.DISABLED) { + this.provisionConfigurationFormGroup.enable({emitEvent: false}); + } else { + this.provisionConfigurationFormGroup.get('type').enable({emitEvent: false}); + } } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts index 09223491e1..e837caf660 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts @@ -94,8 +94,6 @@ export class DeviceProfileComponent extends EntityComponent { name: [entity ? entity.name : '', [Validators.required]], type: [entity ? entity.type : null, [Validators.required]], transportType: [entity ? entity.transportType : null, [Validators.required]], - provisionType: [deviceProvisionConfiguration.type, [Validators.required]], - provisionDeviceKey: [deviceProvisionConfiguration.provisionDeviceKey], profileData: this.fb.group({ configuration: [entity && !this.isAdd ? entity.profileData?.configuration : {}, Validators.required], transportConfiguration: [entity && !this.isAdd ? entity.profileData?.transportConfiguration : {}, Validators.required], From 8c29339f1364a066553db06444933cd4c0975f51 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 13 Oct 2020 13:20:54 +0300 Subject: [PATCH 198/463] Add support for port in oauth2 domain name. Update proxy.conf to support oauth2 urls --- .../server/controller/OAuth2Controller.java | 2 +- .../org/thingsboard/server/utils/MiscUtils.java | 16 ++++++++++++++++ ui-ngx/proxy.conf.js | 8 ++++++++ .../pages/admin/oauth2-settings.component.ts | 3 ++- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index ecd74bef39..323eb19bcd 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -40,7 +40,7 @@ public class OAuth2Controller extends BaseController { @ResponseBody public List getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { try { - return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainName(request)); + return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request)); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java b/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java index ad242c96e5..e2d9302d79 100644 --- a/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java +++ b/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java @@ -68,6 +68,22 @@ public class MiscUtils { return request.getServerName(); } + public static String getDomainNameAndPort(HttpServletRequest request){ + String domainName = getDomainName(request); + String scheme = getScheme(request); + int port = MiscUtils.getPort(request); + if (needsPort(scheme, port)) { + domainName += ":" + port; + } + return domainName; + } + + private static boolean needsPort(String scheme, int port) { + boolean isHttpDefault = "http".equals(scheme.toLowerCase()) && port == 80; + boolean isHttpsDefault = "https".equals(scheme.toLowerCase()) && port == 443; + return !isHttpDefault && !isHttpsDefault; + } + public static int getPort(HttpServletRequest request){ String forwardedProto = request.getHeader("x-forwarded-proto"); diff --git a/ui-ngx/proxy.conf.js b/ui-ngx/proxy.conf.js index af558a7a7a..5be6bde1d1 100644 --- a/ui-ngx/proxy.conf.js +++ b/ui-ngx/proxy.conf.js @@ -27,6 +27,14 @@ const PROXY_CONFIG = { "target": ruleNodeUiforwardUrl, "secure": false, }, + "/oauth2": { + "target": forwardUrl, + "secure": false, + }, + "/login/oauth2": { + "target": forwardUrl, + "secure": false, + }, "/static": { "target": forwardUrl, "secure": false, diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index 400b2d3fc2..58cbe536d7 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -52,6 +52,7 @@ import { OAuth2Service } from '@core/http/oauth2.service'; export class OAuth2SettingsComponent extends PageComponent implements OnInit, HasConfirmForm, OnDestroy { private URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.,?+=&%@\-/]*)?$/; + private DOMAIN_AND_PORT_REGEXP = /^(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?$/; private subscriptions: Subscription[] = []; private templates = new Map(); private defaultProvider = { @@ -233,7 +234,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha const domain = this.fb.group({ name: [domainInfo ? domainInfo.name : this.window.location.hostname, [ Validators.required, - Validators.pattern('((?![:/]).)*$')]], + Validators.pattern(this.DOMAIN_AND_PORT_REGEXP)]], scheme: [domainInfo?.scheme ? domainInfo.scheme : DomainSchema.HTTPS, Validators.required] }, {validators: this.uniqueDomainValidator}); return domain; From 64cc74c26cc34f832ba33dff03a91f82eca47eb2 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 13 Oct 2020 13:57:38 +0300 Subject: [PATCH 199/463] cleanup code --- ...ttDeviceProfileTransportConfiguration.java | 7 ------ ...toDeviceProfileTransportConfiguration.java | 8 ------- ...qttTransportConfigurationDeserializer.java | 5 ++-- .../mqtt/adaptors/ProtoMqttAdaptor.java | 24 ++++++++++++------- .../mqtt/session/DeviceSessionCtx.java | 10 +++++--- ...ofile-transport-configuration.component.ts | 2 -- 6 files changed, 26 insertions(+), 30 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java index 8699a793ed..3395e398e8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java @@ -19,18 +19,11 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.squareup.wire.schema.Location; -import com.squareup.wire.schema.internal.parser.MessageElement; -import com.squareup.wire.schema.internal.parser.ProtoFileElement; -import com.squareup.wire.schema.internal.parser.ProtoParser; -import com.squareup.wire.schema.internal.parser.TypeElement; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.DeviceTransportType; -import org.springframework.util.CollectionUtils; -import java.util.List; @Slf4j @Data diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java index f4185bd2e2..c3c9fe263a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.common.data.device.profile; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.github.os72.protobuf.dynamic.DynamicSchema; @@ -40,7 +39,6 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; @Slf4j @@ -56,12 +54,6 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf private String deviceTelemetryProtoSchema; private String deviceAttributesProtoSchema; - @JsonIgnore - private Descriptors.Descriptor telemetryMsgDescriptor; - - @JsonIgnore - private Descriptors.Descriptor attributesMsgDescriptor; - @Override public DeviceTransportType getType() { return super.getType(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java index 0089420ed2..15f52f603e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java @@ -18,10 +18,10 @@ package org.thingsboard.server.common.data.device.profile; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.TransportPayloadType; import java.io.IOException; @@ -36,7 +36,8 @@ public class MqttTransportConfigurationDeserializer extends StdDeserializer { From 7a09f8c7185cbd6c00e90211852cc3fb1d1bf70b Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 13 Oct 2020 14:46:35 +0300 Subject: [PATCH 200/463] Minor improvemetns --- .../service/install/update/DefaultDataUpdateService.java | 2 +- .../server/transport/mqtt/MqttTransportHandler.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index bc86857c4d..aded7868bd 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -170,7 +170,7 @@ public class DefaultDataUpdateService implements DataUpdateService { ruleChainService.saveRuleChainMetaData(tenant.getId(), md); } } catch (Exception e) { - log.error("Unable to update Tenant", e); + log.error("[{}] Unable to update Tenant: {}", tenant.getId(), tenant.getName(), e); } } }; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index fab0579bb2..3723b19f6d 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -263,12 +263,12 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private void processDevicePublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, String topicName, int msgId) { try { MqttTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor(); - if (deviceSessionCtx.isDeviceTelemetryTopic(topicName)) { - TransportProtos.PostTelemetryMsg postTelemetryMsg = payloadAdaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg); - transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg)); - } else if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) { + if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) { TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg)); + } else if (deviceSessionCtx.isDeviceTelemetryTopic(topicName)) { + TransportProtos.PostTelemetryMsg postTelemetryMsg = payloadAdaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg); + transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg)); } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) { TransportProtos.GetAttributeRequestMsg getAttributeMsg = payloadAdaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg)); From b0126a9d47f435922bf9bfe08c85754f67afced1 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 13 Oct 2020 15:17:18 +0300 Subject: [PATCH 201/463] added new APIs --- .../thingsboard/rest/client/RestClient.java | 392 +++++++++++++++++- 1 file changed, 388 insertions(+), 4 deletions(-) diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index d2ba4d43b4..0a92d4b1a8 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -38,10 +38,18 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceInfo; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileInfo; +import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.EntityViewInfo; import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.TenantInfo; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.UpdateMessage; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; @@ -50,6 +58,7 @@ import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.asset.AssetSearchQuery; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.AuditLog; @@ -60,28 +69,41 @@ import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.kv.Aggregation; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.data.query.AlarmData; +import org.thingsboard.server.common.data.query.AlarmDataQuery; +import org.thingsboard.server.common.data.query.EntityCountQuery; +import org.thingsboard.server.common.data.query.EntityData; +import org.thingsboard.server.common.data.query.EntityDataQuery; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelationInfo; import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.data.rule.DefaultRuleChainCreateRequest; import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainData; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; @@ -325,6 +347,19 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } + public Optional getAssetInfoById(AssetId assetId) { + try { + ResponseEntity asset = restTemplate.getForEntity(baseURL + "/api/asset/info/{assetId}", AssetInfo.class, assetId.getId()); + return Optional.ofNullable(asset.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + public Asset saveAsset(Asset asset) { return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody(); } @@ -390,6 +425,20 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return assets.getBody(); } + public PageData getTenantAssetInfos(PageLink pageLink, String assetType) { + Map params = new HashMap<>(); + params.put("type", assetType); + addPageLinkToParam(params, pageLink); + + ResponseEntity> assets = restTemplate.exchange( + baseURL + "/api/tenant/assetInfos?type={type}&" + getUrlParams(pageLink), + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params); + return assets.getBody(); + } + public Optional getTenantAsset(String assetName) { try { ResponseEntity asset = restTemplate.getForEntity(baseURL + "/api/tenant/assets?assetName={assetName}", Asset.class, assetName); @@ -419,6 +468,22 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return assets.getBody(); } + public PageData getCustomerAssetInfos(CustomerId customerId, PageLink pageLink, String assetType) { + Map params = new HashMap<>(); + params.put("customerId", customerId.getId().toString()); + params.put("type", assetType); + addPageLinkToParam(params, pageLink); + + ResponseEntity> assets = restTemplate.exchange( + baseURL + "/api/customer/{customerId}/assetInfos?type={type}&" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params); + return assets.getBody(); + } + public List getAssetsByIds(List assetIds) { return restTemplate.exchange( baseURL + "/api/assets?assetIds={assetIds}", @@ -593,11 +658,15 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } public Optional activateUser(UserId userId, String password) { + return activateUser(userId, password, true); + } + + public Optional activateUser(UserId userId, String password, boolean sendActivationMail) { ObjectNode activateRequest = objectMapper.createObjectNode(); activateRequest.put("activateToken", getActivateToken(userId)); activateRequest.put("password", password); try { - ResponseEntity jsonNode = restTemplate.postForEntity(baseURL + "/api/noauth/activate", activateRequest, JsonNode.class); + ResponseEntity jsonNode = restTemplate.postForEntity(baseURL + "/api/noauth/activate?sendActivationMail={sendActivationMail}", activateRequest, JsonNode.class, sendActivationMail); return Optional.ofNullable(jsonNode.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -936,8 +1005,25 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } + public Optional getDeviceInfoById(DeviceId deviceId) { + try { + ResponseEntity device = restTemplate.getForEntity(baseURL + "/api/device/info/{deviceId}", DeviceInfo.class, deviceId); + return Optional.ofNullable(device.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + public Device saveDevice(Device device) { - return restTemplate.postForEntity(baseURL + "/api/device", device, Device.class).getBody(); + return saveDevice(device, null); + } + + public Device saveDevice(Device device, String accessToken) { + return restTemplate.postForEntity(baseURL + "/api/device?accessToken={accessToken}", device, Device.class, accessToken).getBody(); } public void deleteDevice(DeviceId deviceId) { @@ -1011,6 +1097,18 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { }, params).getBody(); } + public PageData getTenantDeviceInfos(String type, DeviceProfileId deviceProfileId, PageLink pageLink) { + Map params = new HashMap<>(); + params.put("type", type); + params.put("deviceProfileId", deviceProfileId != null ? deviceProfileId.toString() : null); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/tenant/deviceInfos?type={type}&deviceProfileId={deviceProfileId}&" + getUrlParams(pageLink), + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + public Optional getTenantDevice(String deviceName) { try { ResponseEntity device = restTemplate.getForEntity(baseURL + "/api/tenant/devices?deviceName={deviceName}", Device.class, deviceName); @@ -1036,6 +1134,19 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { }, params).getBody(); } + public PageData getCustomerDeviceInfos(CustomerId customerId, String deviceType, DeviceProfileId deviceProfileId, PageLink pageLink) { + Map params = new HashMap<>(); + params.put("customerId", customerId.toString()); + params.put("type", deviceType); + params.put("deviceProfileId", deviceProfileId != null ? deviceProfileId.toString() : null); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/customer/{customerId}/devices?type={type}&deviceProfileId={deviceProfileId}&" + getUrlParams(pageLink), + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + public List getDevicesByIds(List deviceIds) { return restTemplate.exchange(baseURL + "/api/devices?deviceIds={deviceIds}", HttpMethod.GET, @@ -1074,6 +1185,12 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { restTemplate.delete(baseURL + "/api/customer/device/{deviceName}/claim", deviceName); } + public Device assignDeviceToTenant(TenantId tenantId, DeviceId deviceId) { + return restTemplate.postForEntity( + baseURL + "/api/tenant/{tenantId}/device/{deviceId}", + HttpEntity.EMPTY, Device.class, tenantId, deviceId).getBody(); + } + @Deprecated public Device createDevice(String name, String type) { Device device = new Device(); @@ -1138,6 +1255,91 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { customerId.toString(), deviceId.toString()).getBody(); } + public Optional getDeviceProfileById(DeviceProfileId deviceProfileId) { + try { + ResponseEntity deviceProfile = restTemplate.getForEntity(baseURL + "/api/deviceProfile/{deviceProfileId}", DeviceProfile.class, deviceProfileId); + return Optional.ofNullable(deviceProfile.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional getDeviceProfileInfoById(DeviceProfileId deviceProfileId) { + try { + ResponseEntity deviceProfileInfo = restTemplate.getForEntity(baseURL + "/api/deviceProfileInfo/{deviceProfileId}", DeviceProfileInfo.class, deviceProfileId); + return Optional.ofNullable(deviceProfileInfo.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public DeviceProfileInfo getDefaultDeviceProfileInfo() { + return restTemplate.getForEntity(baseURL + "/api/deviceProfileInfo/default", DeviceProfileInfo.class).getBody(); + } + + public DeviceProfile saveDeviceProfile(DeviceProfile deviceProfile) { + return restTemplate.postForEntity(baseURL + "/api/deviceProfile", deviceProfile, DeviceProfile.class).getBody(); + } + + public void deleteDeviceProfile(DeviceProfileId deviceProfileId) { + restTemplate.delete(baseURL + "/api/deviceProfile/{deviceProfileId}", deviceProfileId); + } + + public DeviceProfile setDefaultDeviceProfile(DeviceProfileId deviceProfileId) { + return restTemplate.postForEntity( + baseURL + "/api/deviceProfile/{deviceProfileId}/default", + HttpEntity.EMPTY, DeviceProfile.class, deviceProfileId).getBody(); + } + + public PageData getTenantDevices(PageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/deviceProfiles?" + getUrlParams(pageLink), + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + + public PageData getDeviceProfileInfos(PageLink pageLink, DeviceTransportType deviceTransportType) { + Map params = new HashMap<>(); + params.put("deviceTransportType", deviceTransportType != null ? deviceTransportType.name() : null); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/deviceProfileInfos?deviceTransportType={deviceTransportType}&" + getUrlParams(pageLink), + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + + public Long countEntitiesByQuery(EntityCountQuery query) { + return restTemplate.postForObject(baseURL + "/api/entitiesQuery/count", query, Long.class); + } + + public PageData findEntityDataByQuery(EntityDataQuery query) { + return restTemplate.exchange( + baseURL + "/api/entitiesQuery/find", + HttpMethod.POST, new HttpEntity<>(query), + new ParameterizedTypeReference>() { + }).getBody(); + } + + public PageData findAlarmDataByQuery(AlarmDataQuery query) { + return restTemplate.exchange( + baseURL + "/api/alarmsQuery/find", + HttpMethod.POST, new HttpEntity<>(query), + new ParameterizedTypeReference>() { + }).getBody(); + } + public void saveRelation(EntityRelation relation) { restTemplate.postForLocation(baseURL + "/api/relation", relation); } @@ -1313,6 +1515,19 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } + public Optional getEntityViewInfoById(EntityViewId entityViewId) { + try { + ResponseEntity entityView = restTemplate.getForEntity(baseURL + "/api/entityView/info/{entityViewId}", EntityViewInfo.class, entityViewId); + return Optional.ofNullable(entityView.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + public EntityView saveEntityView(EntityView entityView) { return restTemplate.postForEntity(baseURL + "/api/entityView", entityView, EntityView.class).getBody(); } @@ -1373,6 +1588,19 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { }, params).getBody(); } + public PageData getCustomerEntityViewInfos(CustomerId customerId, String entityViewType, PageLink pageLink) { + Map params = new HashMap<>(); + params.put("customerId", customerId.toString()); + params.put("type", entityViewType); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/customer/{customerId}/entityViewInfos?type={type}&" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + public PageData getTenantEntityViews(String entityViewType, PageLink pageLink) { Map params = new HashMap<>(); params.put("type", entityViewType); @@ -1385,6 +1613,18 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { }, params).getBody(); } + public PageData getTenantEntityViewInfos(String entityViewType, PageLink pageLink) { + Map params = new HashMap<>(); + params.put("type", entityViewType); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/tenant/entityViewInfos?type={type}&" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + public List findByQuery(EntityViewSearchQuery query) { return restTemplate.exchange(baseURL + "/api/entityViews", HttpMethod.POST, new HttpEntity<>(query), new ParameterizedTypeReference>() { }).getBody(); @@ -1437,8 +1677,41 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { - }, - params).getBody(); + }, params).getBody(); + } + + public OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { + return restTemplate.postForEntity(baseURL + "/api/oauth2/config/template", clientRegistrationTemplate, OAuth2ClientRegistrationTemplate.class).getBody(); + } + + public void deleteClientRegistrationTemplate(OAuth2ClientRegistrationTemplateId oAuth2ClientRegistrationTemplateId) { + restTemplate.delete(baseURL + "/api/oauth2/config/template/{clientRegistrationTemplateId}", oAuth2ClientRegistrationTemplateId); + } + + public List getClientRegistrationTemplates() { + return restTemplate.exchange( + baseURL + "/api/oauth2/config/template", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }).getBody(); + } + + public List getOAuth2Clients() { + return restTemplate.exchange( + baseURL + "/api/noauth/oauth2Clients", + HttpMethod.POST, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }).getBody(); + } + + public OAuth2ClientsParams getCurrentOAuth2Params() { + return restTemplate.getForEntity(baseURL + "/api/oauth2/config", OAuth2ClientsParams.class).getBody(); + } + + public OAuth2ClientsParams saveOAuth2Params(OAuth2ClientsParams oauth2Params) { + return restTemplate.postForEntity(baseURL + "/api/oauth2/config", oauth2Params, OAuth2ClientsParams.class).getBody(); } public void handleOneWayDeviceRPCRequest(DeviceId deviceId, JsonNode requestBody) { @@ -1485,6 +1758,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return restTemplate.postForEntity(baseURL + "/api/ruleChain", ruleChain, RuleChain.class).getBody(); } + public RuleChain saveRuleChain(DefaultRuleChainCreateRequest request) { + return restTemplate.postForEntity(baseURL + "/api/ruleChain/device/default", request, RuleChain.class).getBody(); + } + public Optional setRootRuleChain(RuleChainId ruleChainId) { try { ResponseEntity ruleChain = restTemplate.postForEntity(baseURL + "/api/ruleChain/{ruleChainId}/root", null, RuleChain.class, ruleChainId.getId()); @@ -1544,6 +1821,14 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } + public RuleChainData exportRuleChains(int limit) { + return restTemplate.getForEntity(baseURL + "/api/ruleChains/export?limit=" + limit, RuleChainData.class).getBody(); + } + + public void importRuleChains(RuleChainData ruleChainData, boolean overwrite) { + restTemplate.postForLocation(baseURL + "/api/ruleChains/import?overwrite=" + overwrite, ruleChainData); + } + public List getAttributeKeys(EntityId entityId) { return restTemplate.exchange( baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/keys/attributes", @@ -1805,6 +2090,19 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } + public Optional getTenantInfoById(TenantId tenantId) { + try { + ResponseEntity tenant = restTemplate.getForEntity(baseURL + "/api/tenant/info/{tenantId}", TenantInfo.class, tenantId); + return Optional.ofNullable(tenant.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + public Tenant saveTenant(Tenant tenant) { return restTemplate.postForEntity(baseURL + "/api/tenant", tenant, Tenant.class).getBody(); } @@ -1824,6 +2122,81 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { }, params).getBody(); } + public PageData getTenantInfos(PageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/tenantInfos?" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + + public Optional getTenantProfileById(TenantProfileId tenantProfileId) { + try { + ResponseEntity tenantProfile = restTemplate.getForEntity(baseURL + "/api/tenantProfile/{tenantProfileId}", TenantProfile.class, tenantProfileId); + return Optional.ofNullable(tenantProfile.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional getTenantProfileInfoById(TenantProfileId tenantProfileId) { + try { + ResponseEntity entityInfo = restTemplate.getForEntity(baseURL + "/api/tenantProfileInfo/{tenantProfileId}", EntityInfo.class, tenantProfileId); + return Optional.ofNullable(entityInfo.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public EntityInfo getDefaultTenantProfileInfo() { + return restTemplate.getForEntity(baseURL + "/api/tenantProfileInfo/default", EntityInfo.class).getBody(); + } + + public TenantProfile saveTenantProfile(TenantProfile tenantProfile) { + return restTemplate.postForEntity(baseURL + "/api/tenantProfile", tenantProfile, TenantProfile.class).getBody(); + } + + public void deleteTenantProfile(TenantProfileId tenantProfileId) { + restTemplate.delete(baseURL + "/api/tenantProfile/{tenantProfileId}", tenantProfileId); + } + + public TenantProfile setDefaultTenantProfile(TenantProfileId tenantProfileId) { + return restTemplate.exchange(baseURL + "/api/tenantProfile/{tenantProfileId}/default", HttpMethod.POST, HttpEntity.EMPTY, TenantProfile.class, tenantProfileId).getBody(); + } + + public PageData getTenantProfiles(PageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/tenantProfiles" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + + public PageData getTenantProfileInfos(PageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/tenantProfileInfos" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + public Optional getUserById(UserId userId) { try { ResponseEntity user = restTemplate.getForEntity(baseURL + "/api/user/{userId}", User.class, userId.getId()); @@ -1870,6 +2243,17 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { restTemplate.delete(baseURL + "/api/user/{userId}", userId.getId()); } + public PageData getUsers(PageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/users" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + public PageData getTenantAdmins(TenantId tenantId, PageLink pageLink) { Map params = new HashMap<>(); params.put("tenantId", tenantId.getId().toString()); From d11a1ba7e63e8a03df0f9e98cc69c89387f80769 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 13 Oct 2020 16:06:18 +0300 Subject: [PATCH 202/463] cleanup code --- .../controller/DeviceProfileController.java | 8 ++-- common/data/pom.xml | 4 -- ...ttDeviceProfileTransportConfiguration.java | 4 +- ...toDeviceProfileTransportConfiguration.java | 37 +++++++++++++------ .../transport/mqtt/MqttTransportHandler.java | 14 +++---- .../mqtt/adaptors/ProtoMqttAdaptor.java | 2 - .../mqtt/session/DeviceSessionCtx.java | 2 +- 7 files changed, 38 insertions(+), 33 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index 48c5ab6978..fef67ccc72 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -99,12 +99,10 @@ public class DeviceProfileController extends BaseController { DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); - if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { - if (transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration) { - MqttProtoDeviceProfileTransportConfiguration protoTransportConfiguration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; - if (protoTransportConfiguration.getTransportPayloadType().equals(TransportPayloadType.PROTOBUF)) + if (transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration) { + MqttProtoDeviceProfileTransportConfiguration protoTransportConfiguration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; + if (protoTransportConfiguration.getTransportPayloadType().equals(TransportPayloadType.PROTOBUF)) checkProtoSchemas(protoTransportConfiguration); - } } DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); diff --git a/common/data/pom.xml b/common/data/pom.xml index 00e53ff2bb..6d32a3df53 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -71,10 +71,6 @@ java-driver-core test - - org.springframework - spring-web - com.squareup.wire wire-schema diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java index 3395e398e8..720e81b8fd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java @@ -38,11 +38,11 @@ import org.thingsboard.server.common.data.DeviceTransportType; @JsonDeserialize(using = MqttTransportConfigurationDeserializer.class) public abstract class MqttDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { + public abstract TransportPayloadType getTransportPayloadType(); + protected String deviceTelemetryTopic = MqttTopics.DEVICE_TELEMETRY_TOPIC; protected String deviceAttributesTopic = MqttTopics.DEVICE_ATTRIBUTES_TOPIC; - public abstract TransportPayloadType getTransportPayloadType(); - @Override public DeviceTransportType getType() { return DeviceTransportType.MQTT; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java index c3c9fe263a..06e8d57c8e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java @@ -22,6 +22,7 @@ import com.github.os72.protobuf.dynamic.EnumDefinition; import com.github.os72.protobuf.dynamic.MessageDefinition; import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; +import com.squareup.wire.schema.Field; import com.squareup.wire.schema.Location; import com.squareup.wire.schema.internal.parser.EnumConstantElement; import com.squareup.wire.schema.internal.parser.EnumElement; @@ -33,8 +34,6 @@ import com.squareup.wire.schema.internal.parser.TypeElement; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -73,7 +72,7 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf throw new IllegalArgumentException("Failed to parse: " + schemaName + " due to: " + e.getMessage()); } List types = protoFileElement.getTypes(); - if (!CollectionUtils.isEmpty(types)) { + if (!types.isEmpty()) { if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) { throw new IllegalArgumentException("Invalid " + schemaName + " provided! At least one Message definition should exists!"); } @@ -86,7 +85,7 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf ProtoFileElement protoFileElement = getTransportProtoSchema(protoSchema); DynamicSchema.Builder schemaBuilder = DynamicSchema.newBuilder(); schemaBuilder.setName(schemaName); - schemaBuilder.setPackage(!StringUtils.isEmpty(protoFileElement.getPackageName()) ? + schemaBuilder.setPackage(!isEmptyStr(protoFileElement.getPackageName()) ? protoFileElement.getPackageName() : schemaName.toLowerCase()); List types = protoFileElement.getTypes(); @@ -101,11 +100,11 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf .map(typeElement -> (MessageElement) typeElement) .collect(Collectors.toList()); - if (!CollectionUtils.isEmpty(enumTypes)) { + if (!enumTypes.isEmpty()) { enumTypes.forEach(enumElement -> { List enumElementTypeConstants = enumElement.getConstants(); EnumDefinition.Builder enumDefinitionBuilder = EnumDefinition.newBuilder(enumElement.getName()); - if (!CollectionUtils.isEmpty(enumElementTypeConstants)) { + if (!enumElementTypeConstants.isEmpty()) { enumElementTypeConstants.forEach(constantElement -> enumDefinitionBuilder.addValue(constantElement.getName(), constantElement.getTag())); } EnumDefinition enumDefinition = enumDefinitionBuilder.build(); @@ -113,12 +112,21 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf }); } - if (!CollectionUtils.isEmpty(messageTypes)) { + if (!messageTypes.isEmpty()) { messageTypes.forEach(messageElement -> { List messageElementFields = messageElement.getFields(); MessageDefinition.Builder messageDefinitionBuilder = MessageDefinition.newBuilder(messageElement.getName()); - if (!CollectionUtils.isEmpty(messageElementFields)) { - messageElementFields.forEach(fieldElement -> messageDefinitionBuilder.addField(fieldElement.getType(), fieldElement.getName(), fieldElement.getTag(), fieldElement.getDefaultValue())); + if (!messageElementFields.isEmpty()) { + messageElementFields.forEach(fieldElement -> { + Field.Label label = fieldElement.getLabel(); + String labelStr = label != null ? label.name() : null; + messageDefinitionBuilder.addField( + labelStr, + fieldElement.getType(), + fieldElement.getName(), + fieldElement.getTag(), + fieldElement.getDefaultValue()); + }); } MessageDefinition messageDefinition = messageDefinitionBuilder.build(); schemaBuilder.addMessageDefinition(messageDefinition); @@ -129,16 +137,21 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf DynamicMessage.Builder builder = dynamicSchema.newMessageBuilder(lastMsg.getName()); return builder.getDescriptorForType(); } catch (Descriptors.DescriptorValidationException e) { - throw new RuntimeException(e); + log.error("Failed to create dynamic schema due to: ", e); + return null; } } else { - throw new RuntimeException("Failed to get Message Descriptor! Message types is empty for " + schemaName + " schema!"); + log.error("Failed to get Message Descriptor! Message types is empty for {} schema!", schemaName); + return null; } } - private ProtoFileElement getTransportProtoSchema(String protoSchema) { return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); } + private boolean isEmptyStr(String str) { + return str == null || "".equals(str); + } + } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 3b3f80e89f..fab0579bb2 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -262,24 +262,24 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private void processDevicePublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, String topicName, int msgId) { try { - MqttTransportAdaptor adaptor = deviceSessionCtx.getPayloadAdaptor(); + MqttTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor(); if (deviceSessionCtx.isDeviceTelemetryTopic(topicName)) { - TransportProtos.PostTelemetryMsg postTelemetryMsg = adaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg); + TransportProtos.PostTelemetryMsg postTelemetryMsg = payloadAdaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg)); } else if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) { - TransportProtos.PostAttributeMsg postAttributeMsg = adaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg); + TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg)); } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) { - TransportProtos.GetAttributeRequestMsg getAttributeMsg = adaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg); + TransportProtos.GetAttributeRequestMsg getAttributeMsg = payloadAdaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg)); } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_RESPONSE_TOPIC)) { - TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = adaptor.convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg); + TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = payloadAdaptor.convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(ctx, msgId, rpcResponseMsg)); } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_REQUESTS_TOPIC)) { - TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = adaptor.convertToServerRpcRequest(deviceSessionCtx, mqttMsg); + TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = payloadAdaptor.convertToServerRpcRequest(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequestMsg, getPubAckCallback(ctx, msgId, rpcRequestMsg)); } else if (topicName.equals(MqttTopics.DEVICE_CLAIM_TOPIC)) { - TransportProtos.ClaimDeviceMsg claimDeviceMsg = adaptor.convertToClaimDevice(deviceSessionCtx, mqttMsg); + TransportProtos.ClaimDeviceMsg claimDeviceMsg = payloadAdaptor.convertToClaimDevice(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(ctx, msgId, claimDeviceMsg)); } else { transportService.reportActivity(deviceSessionCtx.getSessionInfo()); diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java index 532115a765..aaa8983a0c 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java @@ -38,7 +38,6 @@ import org.thingsboard.server.common.transport.adaptor.ProtoConverter; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; -import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext; import java.util.Optional; @@ -122,7 +121,6 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { @Override public TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage mqttMsg) throws AdaptorException { byte[] bytes = toBytes(mqttMsg.payload()); - String topicName = mqttMsg.variableHeader().topicName(); try { return ProtoConverter.convertToProvisionRequestMsg(bytes); } catch (InvalidProtocolBufferException ex) { diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index f595df7721..6ecea5a77c 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -121,7 +121,7 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { payloadType = mqttConfig.getTransportPayloadType(); telemetryTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceTelemetryTopic()); attributesTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesTopic()); - if (payloadType.equals(TransportPayloadType.PROTOBUF) && mqttConfig instanceof MqttProtoDeviceProfileTransportConfiguration) { + if (mqttConfig instanceof MqttProtoDeviceProfileTransportConfiguration) { updateDynamicMessageDescriptors(mqttConfig); } } else { From 1f2719e321b0fb1771a1444272214231bbc9936a Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 13 Oct 2020 16:17:39 +0300 Subject: [PATCH 203/463] fix typos --- .../server/controller/DeviceProfileController.java | 3 +-- .../server/transport/mqtt/adaptors/ProtoMqttAdaptor.java | 2 +- .../server/transport/mqtt/session/DeviceSessionCtx.java | 4 +--- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index fef67ccc72..f55ac89ab5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -101,8 +101,7 @@ public class DeviceProfileController extends BaseController { if (transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration) { MqttProtoDeviceProfileTransportConfiguration protoTransportConfiguration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; - if (protoTransportConfiguration.getTransportPayloadType().equals(TransportPayloadType.PROTOBUF)) - checkProtoSchemas(protoTransportConfiguration); + checkProtoSchemas(protoTransportConfiguration); } DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java index aaa8983a0c..e4a35a435f 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java @@ -67,7 +67,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { Descriptors.Descriptor attributesDynamicMessage = getDescriptor(deviceSessionCtx.getAttributesDynamicMessageDescriptor()); try { return JsonConverter.convertToAttributesProto(new JsonParser().parse(dynamicMsgToJson(bytes, attributesDynamicMessage))); - } catch (InvalidProtocolBufferException | IllegalArgumentException e) { + } catch (Exception e) { throw new AdaptorException(e); } } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index 6ecea5a77c..79147c0935 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -80,9 +80,7 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { return telemetryTopicFilter.filter(topicName); } - public boolean isDeviceAttributesTopic(String topicName) { - return attributesTopicFilter.filter(topicName); - } + public boolean isDeviceAttributesTopic(String topicName) { return attributesTopicFilter.filter(topicName); } public MqttTransportAdaptor getPayloadAdaptor() { return payloadType.equals(TransportPayloadType.JSON) ? context.getJsonMqttAdaptor() : context.getProtoMqttAdaptor(); From f89fd60e7c99825ae34b7844ac30cb4312aaf8af Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 13 Oct 2020 16:21:57 +0300 Subject: [PATCH 204/463] fix typo --- .../device/profile/MqttDeviceProfileTransportConfiguration.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java index 720e81b8fd..d76cd55f8c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java @@ -20,12 +20,10 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Data; -import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.DeviceTransportType; -@Slf4j @Data @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo( From 8bdf990ed2ac9c30d1c8c447b94c963ef8cd27dc Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 13 Oct 2020 16:23:23 +0300 Subject: [PATCH 205/463] revert changes --- .../profile/MqttDeviceProfileTransportConfiguration.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java index d76cd55f8c..66174e3d29 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java @@ -23,8 +23,6 @@ import lombok.Data; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.DeviceTransportType; - -@Data @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, @@ -34,6 +32,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; @JsonSubTypes.Type(value = MqttJsonDeviceProfileTransportConfiguration.class, name = "JSON"), @JsonSubTypes.Type(value = MqttProtoDeviceProfileTransportConfiguration.class, name = "PROTOBUF")}) @JsonDeserialize(using = MqttTransportConfigurationDeserializer.class) +@Data public abstract class MqttDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { public abstract TransportPayloadType getTransportPayloadType(); From 52e6e76ac6c46cbead43f17b6c53b7ebc967dba6 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 13 Oct 2020 19:48:46 +0300 Subject: [PATCH 206/463] Oauth2 - set provider name to created user additionalInfo. UI: Improve device profile alarm rules. --- .../oauth2/AbstractOAuth2ClientMapper.java | 22 ++++++--- .../auth/oauth2/BasicOAuth2ClientMapper.java | 6 ++- .../auth/oauth2/CustomOAuth2ClientMapper.java | 6 ++- .../auth/oauth2/GithubOAuth2ClientMapper.java | 6 ++- .../auth/oauth2/OAuth2ClientMapper.java | 4 +- .../Oauth2AuthenticationSuccessHandler.java | 4 +- .../entity/add-entity-dialog.component.html | 10 ++--- .../filter/filter-text.component.html | 2 +- .../filter/filter-text.component.scss | 5 +++ .../filter/filter-text.component.ts | 3 ++ .../add-device-profile-dialog.component.html | 45 +++++++++---------- .../add-device-profile-dialog.component.scss | 4 +- .../alarm/alarm-rule-condition.component.html | 25 +++++------ .../alarm/alarm-rule-condition.component.scss | 7 +-- .../profile/alarm/alarm-rule.component.html | 45 +++++++------------ .../profile/alarm/alarm-rule.component.scss | 30 ++++--------- .../profile/alarm/alarm-rule.component.ts | 3 +- .../alarm/alarm-schedule-info.component.html | 25 +++++------ .../alarm/alarm-schedule-info.component.scss | 10 ++--- .../alarm/alarm-schedule-info.component.ts | 2 +- .../alarm/create-alarm-rules.component.html | 4 +- .../alarm/device-profile-alarm.component.html | 2 +- .../edit-alarm-details-dialog.component.html | 10 ++--- .../edit-alarm-details-dialog.component.ts | 4 ++ .../device-wizard-dialog.component.html | 43 +++++++++--------- .../device-wizard-dialog.component.scss | 3 +- .../assets/locale/locale.constant-en_US.json | 5 ++- ui-ngx/src/styles.scss | 5 +-- 28 files changed, 165 insertions(+), 175 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java index 651e234f0a..f76521ac48 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java @@ -31,6 +31,8 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.IdBased; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; @@ -76,12 +78,15 @@ public abstract class AbstractOAuth2ClientMapper { private final Lock userCreationLock = new ReentrantLock(); - protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, boolean allowUserCreation, boolean activateUser) { + protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2ClientRegistrationInfo clientRegistration) { + + OAuth2MapperConfig config = clientRegistration.getMapperConfig(); + UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, oauth2User.getEmail()); User user = userService.findUserByEmail(TenantId.SYS_TENANT_ID, oauth2User.getEmail()); - if (user == null && !allowUserCreation) { + if (user == null && !config.isAllowUserCreation()) { throw new UsernameNotFoundException("User not found: " + oauth2User.getEmail()); } @@ -106,21 +111,28 @@ public abstract class AbstractOAuth2ClientMapper { user.setFirstName(oauth2User.getFirstName()); user.setLastName(oauth2User.getLastName()); + ObjectNode additionalInfo = objectMapper.createObjectNode(); + if (!StringUtils.isEmpty(oauth2User.getDefaultDashboardName())) { Optional dashboardIdOpt = user.getAuthority() == Authority.TENANT_ADMIN ? getDashboardId(tenantId, oauth2User.getDefaultDashboardName()) : getDashboardId(tenantId, customerId, oauth2User.getDefaultDashboardName()); if (dashboardIdOpt.isPresent()) { - ObjectNode additionalInfo = objectMapper.createObjectNode(); additionalInfo.put("defaultDashboardFullscreen", oauth2User.isAlwaysFullScreen()); additionalInfo.put("defaultDashboardId", dashboardIdOpt.get().getId().toString()); - user.setAdditionalInfo(additionalInfo); } } + if (clientRegistration.getAdditionalInfo() != null && + clientRegistration.getAdditionalInfo().has("providerName")) { + additionalInfo.put("authProviderName", clientRegistration.getAdditionalInfo().get("providerName").asText()); + } + + user.setAdditionalInfo(additionalInfo); + user = userService.saveUser(user); - if (activateUser) { + if (config.isActivateUser()) { UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId()); userService.activateUserCredentials(user.getTenantId(), userCredentials.getActivateToken(), passwordEncoder.encode("")); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java index 73da9e539f..bd6434f50a 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.security.auth.oauth2; import lombok.extern.slf4j.Slf4j; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.service.security.model.SecurityUser; @@ -29,11 +30,12 @@ import java.util.Map; public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { @Override - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) { + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) { + OAuth2MapperConfig config = clientRegistration.getMapperConfig(); Map attributes = token.getPrincipal().getAttributes(); String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); - return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser()); + return getOrCreateSecurityUserFromOAuth2User(oauth2User, clientRegistration); } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java index a85da830b0..0cd292f76f 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java @@ -23,6 +23,7 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.dao.oauth2.OAuth2User; @@ -38,9 +39,10 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); @Override - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) { + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) { + OAuth2MapperConfig config = clientRegistration.getMapperConfig(); OAuth2User oauth2User = getOAuth2User(token, providerAccessToken, config.getCustom()); - return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser()); + return getOrCreateSecurityUserFromOAuth2User(oauth2User, clientRegistration); } private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2CustomMapperConfig custom) { diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java index dcca2b71a1..f259720626 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java @@ -23,6 +23,7 @@ import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.dao.oauth2.OAuth2Configuration; import org.thingsboard.server.dao.oauth2.OAuth2User; @@ -45,12 +46,13 @@ public class GithubOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme private OAuth2Configuration oAuth2Configuration; @Override - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) { + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) { + OAuth2MapperConfig config = clientRegistration.getMapperConfig(); Map githubMapperConfig = oAuth2Configuration.getGithubMapper(); String email = getEmail(githubMapperConfig.get(EMAIL_URL_KEY), providerAccessToken); Map attributes = token.getPrincipal().getAttributes(); OAuth2User oAuth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); - return getOrCreateSecurityUserFromOAuth2User(oAuth2User, config.isAllowUserCreation(), config.isActivateUser()); + return getOrCreateSecurityUserFromOAuth2User(oAuth2User, clientRegistration); } private synchronized String getEmail(String emailUrl, String oauth2Token) { diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java index 27b24043a5..4c9804930e 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java @@ -16,9 +16,9 @@ package org.thingsboard.server.service.security.auth.oauth2; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; import org.thingsboard.server.service.security.model.SecurityUser; public interface OAuth2ClientMapper { - SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config); + SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index 5bb02b9a63..4840f03cf2 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -74,7 +74,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS token.getPrincipal().getName()); OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(clientRegistration.getMapperConfig().getType()); SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(), - clientRegistration.getMapperConfig()); + clientRegistration); JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); @@ -85,4 +85,4 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8.toString())); } } -} \ No newline at end of file +} diff --git a/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.html b/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.html index f1e427445f..78cad36bc5 100644 --- a/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.html @@ -33,11 +33,6 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-text.component.html b/ui-ngx/src/app/modules/home/components/filter/filter-text.component.html index 8aeacc51a2..b8d7103a3b 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-text.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filter-text.component.html @@ -15,5 +15,5 @@ limitations under the License. --> -
diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-text.component.scss b/ui-ngx/src/app/modules/home/components/filter/filter-text.component.scss index 3f232f3ce2..aaa95f0f3f 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-text.component.scss +++ b/ui-ngx/src/app/modules/home/components/filter/filter-text.component.scss @@ -21,6 +21,11 @@ } &.required { color: #f44336; + padding: 0 4px; + } + &.nowrap { + white-space: nowrap; + overflow: hidden; } } } diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-text.component.ts b/ui-ngx/src/app/modules/home/components/filter/filter-text.component.ts index e6eb5955b0..5499cdc765 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-text.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/filter-text.component.ts @@ -54,6 +54,9 @@ export class FilterTextComponent implements ControlValueAccessor, OnInit { @Input() addFilterPrompt = this.translate.instant('filter.add-filter-prompt'); + @Input() + nowrap = false; + requiredClass = false; private filterText: string; diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html index b5e4fd9795..fd5b5b17c2 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+

device-profile.add

@@ -106,28 +106,25 @@
-
-
- -
-
- - -
- - -
-
+
+ + + +
+ +
+ +
diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.scss b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.scss index b8b811ca3d..d2a716bb50 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.scss @@ -28,7 +28,7 @@ display: flex; flex-direction: column; height: 100%; - padding: 24px 24px 8px !important; + padding: 0 !important; .mat-stepper-horizontal { display: flex; @@ -45,7 +45,7 @@ } } .mat-horizontal-content-container { - height: 350px; + height: 530px; max-height: 100%; width: 100%;; overflow-y: auto; diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.html index 15d6173e0e..7c09f3ba1c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.html @@ -15,25 +15,22 @@ limitations under the License. --> -
- +
- +
- +
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.scss index 0b1ccabd65..733a80ada3 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.scss @@ -22,13 +22,10 @@ } .tb-alarm-rule-condition { cursor: pointer; + min-width: 0; .tb-alarm-rule-condition-spec { - margin-top: 1em; - line-height: 1.8em; + opacity: 0.7; padding: 4px; - &.disabled { - opacity: 0.7; - } } } } diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html index 3c49f61cf0..c7c77a7f3c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html @@ -16,36 +16,23 @@ -->
- + - - + - -
-
- - - - {{ 'action.edit' | translate }} - -
- +
+ + {{ alarmRuleFormGroup.get('alarmDetails').value ? ('device-profile.alarm-rule-details' | translate) + ': ' : ('device-profile.add-alarm-rule-details' | translate) }} + + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.scss index a52e6f312e..f69af45170 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.scss @@ -14,31 +14,19 @@ * limitations under the License. */ :host { + min-width: 0; .row { margin-top: 1em; } .tb-alarm-rule-details { - a.mat-button { - &:hover, &:focus { - border-bottom: none; - } - } - .tb-alarm-rule-details-content { - min-height: 33px; - overflow: hidden; - white-space: pre; - line-height: 1.8em; - padding: 4px; - cursor: pointer; - &.collapsed { - max-height: 33px; - white-space: nowrap; - text-overflow: ellipsis; - } - &.disabled { - opacity: 0.7; - cursor: auto; - } + padding: 4px; + cursor: pointer; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + &.title { + opacity: 0.7; + overflow: visible; } } } diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts index 4c86f7dfbd..1e7806b455 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts @@ -118,7 +118,8 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { - alarmDetails: this.alarmRuleFormGroup.get('alarmDetails').value + alarmDetails: this.alarmRuleFormGroup.get('alarmDetails').value, + readonly: this.disabled } }).afterClosed().subscribe((alarmDetails) => { if (isDefinedAndNotNull(alarmDetails)) { diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.html index a9ea0eb8a4..c8991ccc65 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.html @@ -15,19 +15,16 @@ limitations under the License. --> -
- - + {{('device-profile.schedule' | translate) + ': '}} + - + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.scss index 0849ab7f1d..ef5bb3ebad 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.scss @@ -21,14 +21,14 @@ } } .tb-alarm-rule-schedule { - line-height: 1.8em; padding: 4px; cursor: pointer; - &.disabled { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + &.title { opacity: 0.7; - } - .nowrap { - white-space: nowrap; + overflow: visible; } } } diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.ts index 0d2e5a0fee..0b9ce26163 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.ts @@ -101,7 +101,7 @@ export class AlarmScheduleInfoComponent implements ControlValueAccessor, OnInit for (const item of schedule.items) { if (item.enabled) { if (this.scheduleText.length) { - this.scheduleText += '
'; + this.scheduleText += ', '; } this.scheduleText += this.translate.instant(dayOfWeekTranslations[item.dayOfWeek - 1]); this.scheduleText += ' ' + getAlarmScheduleRangeText(utcTimestampToTimeOfDay(item.startsOn), diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html index e1883256bb..8eee1d980e 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html @@ -19,7 +19,7 @@
-
+
alarm.severity - +
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html index f1322ef9a9..cb1f7afb06 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html @@ -98,7 +98,7 @@ remove_circle_outline
-
+
device-profile.no-clear-alarm-rule
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.html index cd039f28aa..9f4a4addf7 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.html @@ -38,16 +38,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.ts index bc46bee51d..a6017a6d70 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.ts @@ -27,6 +27,7 @@ import { TranslateService } from '@ngx-translate/core'; export interface EditAlarmDetailsDialogData { alarmDetails: string; + readonly: boolean; } @Component({ @@ -57,6 +58,9 @@ export class EditAlarmDetailsDialogComponent extends DialogComponent
-
-
- -
-
- - -
- - -
-
+
+ + + +
+ +
+ +
diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss index 1426e0f201..55878d638b 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss @@ -29,6 +29,7 @@ display: flex; flex-direction: column; height: 100%; + padding: 0 !important; .mat-stepper-horizontal { display: flex; @@ -45,7 +46,7 @@ } } .mat-horizontal-content-container { - height: 450px; + height: 530px; max-height: 100%; width: 100%;; overflow-y: auto; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index bc7c84981b..b5b34f8fe5 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -923,6 +923,7 @@ "condition-duration-time-unit-required": "Time unit is required.", "advanced-settings": "Advanced settings", "alarm-rule-details": "Details", + "add-alarm-rule-details": "Add details", "propagate-alarm": "Propagate alarm", "alarm-rule-relation-types-list": "Relation types to propagate", "alarm-rule-relation-types-list-hint": "If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.", @@ -944,14 +945,14 @@ "condition-type": "Condition type", "condition-type-simple": "Simple", "condition-type-duration": "Duration", - "condition-during": "During {{during}}", + "condition-during": "During {{during}}", "condition-type-repeating": "Repeating", "condition-type-required": "Condition type is required.", "condition-repeating-value": "Count of events", "condition-repeating-value-range": "Count of events should be in a range from 1 to 2147483647.", "condition-repeating-value-pattern": "Count of events should be integers.", "condition-repeating-value-required": "Count of events is required.", - "condition-repeat-times": "Repeats { count, plural, 1 {1 time} other {# times} }", + "condition-repeat-times": "Repeats { count, plural, 1 {1 time} other {# times} }", "schedule-type": "Scheduler type", "schedule-type-required": "Scheduler type is required.", "schedule": "Schedule", diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index c6c3b3af3a..ac717e783e 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -869,10 +869,7 @@ mat-label { } .mat-dialog-actions { margin-bottom: 0; - padding: 8px 8px 8px 16px; - button:last-of-type{ - margin-right: 20px; - } + padding: 8px; } } } From 356d4ff26cc6101b66df347e8516857dc42716f7 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 14 Oct 2020 12:07:19 +0300 Subject: [PATCH 207/463] Profile Node improvements in cluster mode --- .../server/actors/ActorSystemContext.java | 6 +- .../actors/ruleChain/DefaultTbContext.java | 11 ++++ .../controller/DeviceProfileController.java | 2 - .../profile/DefaultTbDeviceProfileCache.java | 49 +++++++++++--- .../profile/DefaultTbTenantProfileCache.java | 12 ++-- .../service/profile/TbDeviceProfileCache.java | 3 +- .../service/profile/TbTenantProfileCache.java | 4 -- .../queue/DefaultTbClusterService.java | 3 +- .../processing/AbstractConsumerService.java | 2 +- .../api/RuleEngineDeviceProfileCache.java | 7 ++ .../rule/engine/api/TbContext.java | 5 ++ .../engine/profile/TbDeviceProfileNode.java | 64 +++++++++++-------- 12 files changed, 117 insertions(+), 51 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index d35dadd16f..7f028932f2 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -544,7 +544,11 @@ public class ActorSystemContext { public void scheduleMsgWithDelay(TbActorRef ctx, TbActorMsg msg, long delayInMs) { log.debug("Scheduling msg {} with delay {} ms", msg, delayInMs); - getScheduler().schedule(() -> ctx.tell(msg), delayInMs, TimeUnit.MILLISECONDS); + if (delayInMs > 0) { + getScheduler().schedule(() -> ctx.tell(msg), delayInMs, TimeUnit.MILLISECONDS); + } else { + ctx.tell(msg); + } } } diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index b02bc302de..c8bb083577 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -35,6 +35,7 @@ import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.EntityId; @@ -477,6 +478,16 @@ class DefaultTbContext implements TbContext { mainCtx.getRuleNodeStateService().removeByRuleNodeId(getTenantId(), getSelfId()); } + @Override + public void addProfileListener(Consumer listener) { + mainCtx.getDeviceProfileCache().addListener(getTenantId(), getSelfId(), listener); + } + + @Override + public void removeProfileListener() { + mainCtx.getDeviceProfileCache().removeListener(getTenantId(), getSelfId()); + } + private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("ruleNodeId", ruleNodeId.toString()); diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index efb5c2e6d6..fcb34530b5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -94,7 +94,6 @@ public class DeviceProfileController extends BaseController { DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); - deviceProfileCache.put(savedDeviceProfile); tbClusterService.onDeviceProfileChange(savedDeviceProfile, null); tbClusterService.onEntityStateChange(deviceProfile.getTenantId(), savedDeviceProfile.getId(), created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); @@ -120,7 +119,6 @@ public class DeviceProfileController extends BaseController { DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); DeviceProfile deviceProfile = checkDeviceProfileId(deviceProfileId, Operation.DELETE); deviceProfileService.deleteDeviceProfile(getTenantId(), deviceProfileId); - deviceProfileCache.evict(deviceProfileId); tbClusterService.onDeviceProfileDelete(deviceProfile, null); tbClusterService.onEntityStateChange(deviceProfile.getTenantId(), deviceProfile.getId(), ComponentLifecycleEvent.DELETED); diff --git a/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java b/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java index b6af0d7496..2d0861f636 100644 --- a/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java +++ b/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java @@ -21,14 +21,17 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; @Service @Slf4j @@ -40,6 +43,7 @@ public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache { private final ConcurrentMap deviceProfilesMap = new ConcurrentHashMap<>(); private final ConcurrentMap devicesMap = new ConcurrentHashMap<>(); + private final ConcurrentMap>> listeners = new ConcurrentHashMap<>(); public DefaultTbDeviceProfileCache(DeviceProfileService deviceProfileService, DeviceService deviceService) { this.deviceProfileService = deviceProfileService; @@ -50,19 +54,21 @@ public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache { public DeviceProfile get(TenantId tenantId, DeviceProfileId deviceProfileId) { DeviceProfile profile = deviceProfilesMap.get(deviceProfileId); if (profile == null) { - profile = deviceProfilesMap.get(deviceProfileId); - if (profile == null) { - deviceProfileFetchLock.lock(); - try { + deviceProfileFetchLock.lock(); + try { + profile = deviceProfilesMap.get(deviceProfileId); + if (profile == null) { profile = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId); if (profile != null) { deviceProfilesMap.put(deviceProfileId, profile); + log.info("[{}] Fetch device profile into cache: {}", profile.getId(), profile); } - } finally { - deviceProfileFetchLock.unlock(); } + } finally { + deviceProfileFetchLock.unlock(); } } + log.trace("[{}] Found device profile in cache: {}", deviceProfileId, profile); return profile; } @@ -85,12 +91,19 @@ public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache { public void put(DeviceProfile profile) { if (profile.getId() != null) { deviceProfilesMap.put(profile.getId(), profile); + log.info("[{}] pushed device profile to cache: {}", profile.getId(), profile); + notifyListeners(profile); } } @Override - public void evict(DeviceProfileId profileId) { - deviceProfilesMap.remove(profileId); + public void evict(TenantId tenantId, DeviceProfileId profileId) { + DeviceProfile oldProfile = deviceProfilesMap.remove(profileId); + log.info("[{}] evict device profile from cache: {}", profileId, oldProfile); + DeviceProfile newProfile = get(tenantId, profileId); + if (newProfile != null) { + notifyListeners(newProfile); + } } @Override @@ -98,4 +111,24 @@ public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache { devicesMap.remove(deviceId); } + @Override + public void addListener(TenantId tenantId, EntityId listenerId, Consumer listener) { + listeners.computeIfAbsent(tenantId, id -> new ConcurrentHashMap<>()).put(listenerId, listener); + } + + @Override + public void removeListener(TenantId tenantId, EntityId listenerId) { + ConcurrentMap> tenantListeners = listeners.get(tenantId); + if (tenantListeners != null) { + tenantListeners.remove(listenerId); + } + } + + private void notifyListeners(DeviceProfile profile) { + ConcurrentMap> tenantListeners = listeners.get(profile.getTenantId()); + if (tenantListeners != null) { + tenantListeners.forEach((id, listener) -> listener.accept(profile)); + } + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java b/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java index 4260e36400..42c09c104c 100644 --- a/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java +++ b/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java @@ -55,17 +55,17 @@ public class DefaultTbTenantProfileCache implements TbTenantProfileCache { public TenantProfile get(TenantProfileId tenantProfileId) { TenantProfile profile = tenantProfilesMap.get(tenantProfileId); if (profile == null) { - profile = tenantProfilesMap.get(tenantProfileId); - if (profile == null) { - tenantProfileFetchLock.lock(); - try { + tenantProfileFetchLock.lock(); + try { + profile = tenantProfilesMap.get(tenantProfileId); + if (profile == null) { profile = tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, tenantProfileId); if (profile != null) { tenantProfilesMap.put(tenantProfileId, profile); } - } finally { - tenantProfileFetchLock.unlock(); } + } finally { + tenantProfileFetchLock.unlock(); } } return profile; diff --git a/application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java b/application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java index ec19eb1da6..c067ead6d2 100644 --- a/application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java +++ b/application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java @@ -19,12 +19,13 @@ import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.TenantId; public interface TbDeviceProfileCache extends RuleEngineDeviceProfileCache { void put(DeviceProfile profile); - void evict(DeviceProfileId id); + void evict(TenantId tenantId, DeviceProfileId id); void evict(DeviceId id); diff --git a/application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java b/application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java index d9f944012f..de69b5f51c 100644 --- a/application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java +++ b/application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java @@ -15,11 +15,7 @@ */ package org.thingsboard.server.service.profile; -import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; -import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index 28cd998e46..a4de2d9737 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -221,7 +221,8 @@ public class DefaultTbClusterService implements TbClusterService { byte[] msgBytes = encodingService.encode(msg); TbQueueProducer> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer(); Set tbRuleEngineServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)); - if (msg.getEntityId().getEntityType().equals(EntityType.TENANT)) { + if (msg.getEntityId().getEntityType().equals(EntityType.TENANT) + || msg.getEntityId().getEntityType().equals(EntityType.DEVICE_PROFILE)) { TbQueueProducer> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer(); Set tbCoreServices = partitionService.getAllServiceIds(ServiceType.TB_CORE); for (String serviceId : tbCoreServices) { diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java index 3f6595f81e..0869dbc356 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java @@ -144,7 +144,7 @@ public abstract class AbstractConsumerService listener); + + void removeListener(TenantId tenantId, EntityId listenerId); + } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index ab2e341e52..3d45fcf445 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -20,6 +20,7 @@ import org.springframework.data.redis.core.RedisTemplate; import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.EntityId; @@ -223,4 +224,8 @@ public interface TbContext { RuleNodeState saveRuleNodeState(RuleNodeState state); void clearRuleNodeStates(); + + void addProfileListener(Consumer listener); + + void removeProfileListener(); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java index 4b0b87043a..3fff9dbc2e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java @@ -39,6 +39,7 @@ import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.dao.util.mapping.JacksonUtil; import java.util.Map; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -57,16 +58,20 @@ import java.util.concurrent.TimeUnit; ) public class TbDeviceProfileNode implements TbNode { private static final String PERIODIC_MSG_TYPE = "TbDeviceProfilePeriodicMsg"; + private static final String PROFILE_UPDATE_MSG_TYPE = "TbDeviceProfileUpdateMsg"; private TbDeviceProfileNodeConfiguration config; private RuleEngineDeviceProfileCache cache; + private TbContext ctx; private final Map deviceStates = new ConcurrentHashMap<>(); @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbDeviceProfileNodeConfiguration.class); this.cache = ctx.getDeviceProfileCache(); + this.ctx = ctx; scheduleAlarmHarvesting(ctx); + ctx.addProfileListener(this::onProfileUpdate); if (config.isFetchAlarmRulesStateOnStart()) { log.info("[{}] Fetching alarm rule state", ctx.getSelfId()); int fetchCount = 0; @@ -95,15 +100,14 @@ public class TbDeviceProfileNode implements TbNode { } } - /** - * TODO: Dynamic values evaluation; - */ @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { EntityType originatorType = msg.getOriginator().getEntityType(); if (msg.getType().equals(PERIODIC_MSG_TYPE)) { scheduleAlarmHarvesting(ctx); harvestAlarms(ctx, System.currentTimeMillis()); + } else if (msg.getType().equals(PROFILE_UPDATE_MSG_TYPE)) { + updateProfile(ctx, new DeviceProfileId(UUID.fromString(msg.getData()))); } else { if (EntityType.DEVICE.equals(originatorType)) { DeviceId deviceId = new DeviceId(msg.getOriginator().getId()); @@ -119,36 +123,12 @@ public class TbDeviceProfileNode implements TbNode { ctx.tellFailure(msg, new IllegalStateException("Device profile for device [" + deviceId + "] not found!")); } } - } else if (EntityType.DEVICE_PROFILE.equals(originatorType)) { - log.info("[{}] Received device profile update notification: {}", ctx.getSelfId(), msg.getData()); - if (msg.getType().equals("ENTITY_UPDATED")) { - DeviceProfile deviceProfile = JacksonUtil.fromString(msg.getData(), DeviceProfile.class); - if (deviceProfile != null) { - for (DeviceState state : deviceStates.values()) { - if (deviceProfile.getId().equals(state.getProfileId())) { - state.updateProfile(ctx, deviceProfile); - } - } - } - } - ctx.tellSuccess(msg); } else { ctx.tellSuccess(msg); } } } - public void invalidateDeviceProfileCache(DeviceId deviceId, String deviceJson) { - DeviceState deviceState = deviceStates.get(deviceId); - if (deviceState != null) { - DeviceProfileId currentProfileId = deviceState.getProfileId(); - Device device = JacksonUtil.fromString(deviceJson, Device.class); - if (!currentProfileId.equals(device.getDeviceProfileId())) { - deviceStates.remove(deviceId); - } - } - } - @Override public void onPartitionChangeMsg(TbContext ctx, PartitionChangeMsg msg) { // Cleanup the cache for all entities that are no longer assigned to current server partitions @@ -157,6 +137,7 @@ public class TbDeviceProfileNode implements TbNode { @Override public void destroy() { + ctx.removeProfileListener(); deviceStates.clear(); } @@ -183,4 +164,33 @@ public class TbDeviceProfileNode implements TbNode { } } + protected void updateProfile(TbContext ctx, DeviceProfileId deviceProfileId) throws ExecutionException, InterruptedException { + DeviceProfile deviceProfile = cache.get(ctx.getTenantId(), deviceProfileId); + if (deviceProfile != null) { + log.info("[{}] Received device profile update notification: {}", ctx.getSelfId(), deviceProfile); + for (DeviceState state : deviceStates.values()) { + if (deviceProfile.getId().equals(state.getProfileId())) { + state.updateProfile(ctx, deviceProfile); + } + } + } else { + log.info("[{}] Received stale profile update notification: [{}]", ctx.getSelfId(), deviceProfileId); + } + } + + protected void onProfileUpdate(DeviceProfile profile) { + ctx.tellSelf(TbMsg.newMsg(PROFILE_UPDATE_MSG_TYPE, ctx.getTenantId(), TbMsgMetaData.EMPTY, profile.getId().getId().toString()), 0L); + } + + protected void invalidateDeviceProfileCache(DeviceId deviceId, String deviceJson) { + DeviceState deviceState = deviceStates.get(deviceId); + if (deviceState != null) { + DeviceProfileId currentProfileId = deviceState.getProfileId(); + Device device = JacksonUtil.fromString(deviceJson, Device.class); + if (!currentProfileId.equals(device.getDeviceProfileId())) { + deviceStates.remove(deviceId); + } + } + } + } From ed090b3e6cedb5b1ecdb0a283c9b616740641c31 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 14 Oct 2020 12:32:07 +0300 Subject: [PATCH 208/463] UI: Improve dialog actions style --- .../alarm/alarm-details-dialog.component.html | 17 ++++--- .../alias/entity-alias-dialog.component.html | 10 ++--- .../entity-aliases-dialog.component.html | 10 ++--- .../add-attribute-dialog.component.html | 10 ++--- ...-widget-to-dashboard-dialog.component.html | 15 ++++--- .../edit-attribute-value-panel.component.html | 1 - .../select-target-state-dialog.component.html | 10 ++--- .../event/event-content-dialog.component.html | 1 - ...lex-filter-predicate-dialog.component.html | 12 ++--- .../filter/filter-dialog.component.html | 10 ++--- .../filter-user-info-dialog.component.html | 12 ++--- .../filter/filters-dialog.component.html | 10 ++--- .../filter/key-filter-dialog.component.html | 12 ++--- .../filter/user-filter-dialog.component.html | 10 ++--- .../import-dialog.component.html | 10 ++--- ...alarm-rule-condition-dialog.component.html | 12 ++--- .../alarm-schedule-dialog.component.html | 12 ++--- .../alarm/create-alarm-rules.component.html | 4 +- .../alarm/create-alarm-rules.component.ts | 21 +++++++-- .../alarm/device-profile-alarm.component.ts | 4 +- .../alarm/device-profile-alarms.component.ts | 9 ++-- .../device-profile-dialog.component.html | 12 ++--- .../profile/device-profile.component.html | 2 +- .../tenant-profile-dialog.component.html | 12 ++--- .../relation/relation-dialog.component.html | 11 +++-- .../widget-action-dialog.component.html | 10 ++--- .../data-key-config-dialog.component.html | 10 ++--- .../lib/alarm-filter-panel.component.html | 11 +++-- .../date-range-navigator-panel.component.html | 1 - ...entities-to-customer-dialog.component.html | 12 ++--- .../assign-to-customer-dialog.component.html | 12 ++--- .../add-widget-dialog.component.html | 10 ++--- .../dashboard-settings-dialog.component.html | 12 ++--- ...ge-dashboard-layouts-dialog.component.html | 10 ++--- ...ake-dashboard-public-dialog.component.html | 1 - ...-dashboard-customers-dialog.component.html | 13 +++--- .../dashboard-state-dialog.component.html | 10 ++--- ...age-dashboard-states-dialog.component.html | 10 ++--- .../device-profile-tabs.component.html | 2 +- .../device-credentials-dialog.component.html | 12 ++--- .../change-password-dialog.component.html | 10 ++--- .../add-rule-node-dialog.component.html | 10 ++--- .../add-rule-node-link-dialog.component.html | 10 ++--- .../pages/user/add-user-dialog.component.html | 10 ++--- .../save-widget-type-as-dialog.component.html | 12 ++--- .../dialog/color-picker-dialog.component.html | 1 - .../json-object-edit-dialog.component.html | 11 +++-- .../node-script-test-dialog.component.html | 11 +++-- .../time/timewindow-panel.component.html | 13 +++--- ui-ngx/src/app/shared/models/device.models.ts | 44 ++++++++++++++++++- .../assets/locale/locale.constant-en_US.json | 1 + ui-ngx/src/styles.scss | 3 ++ 52 files changed, 285 insertions(+), 236 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html index 47c9ef8645..64d40df581 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html @@ -88,14 +88,20 @@
-
+ + +
@@ -109,12 +115,5 @@ {{ 'alarm.clear' | translate }}
- -
diff --git a/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.html b/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.html index b7f0ff21fc..bb89ff479e 100644 --- a/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.html @@ -59,16 +59,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.html b/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.html index f817762f95..18f3e1a795 100644 --- a/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.html @@ -95,11 +95,6 @@ {{ 'alias.add' | translate }} - +
diff --git a/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html b/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html index 072858455c..f815da49e7 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html @@ -44,16 +44,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.html b/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.html index 60fe5e30d7..4899a893e5 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.html @@ -55,21 +55,22 @@
-
+
+ style="margin-bottom: 0;"> {{ 'dashboard.open-dashboard' | translate }} - + +
diff --git a/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.html b/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.html index 8e2264fae1..fe48694983 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.html @@ -26,7 +26,6 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.html b/ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.html index 29ee7364eb..e4ee790806 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.html @@ -31,7 +31,6 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-dialog.component.html b/ui-ngx/src/app/modules/home/components/filter/filter-dialog.component.html index ab49dae445..717e8765de 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filter-dialog.component.html @@ -55,16 +55,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.html b/ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.html index a8d205479f..587504da45 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.html @@ -46,12 +46,6 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html b/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html index 91bbc11425..c573267d2a 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html @@ -88,11 +88,6 @@ {{ 'filter.add' | translate }} - + diff --git a/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.html b/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.html index f601814920..5a9f8858c3 100644 --- a/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.html @@ -79,12 +79,6 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/filter/user-filter-dialog.component.html b/ui-ngx/src/app/modules/home/components/filter/user-filter-dialog.component.html index 2a0cf0be24..bf38483244 100644 --- a/ui-ngx/src/app/modules/home/components/filter/user-filter-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/user-filter-dialog.component.html @@ -65,16 +65,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.html b/ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.html index e53ff360ca..a8fe3a1f46 100644 --- a/ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.html @@ -43,16 +43,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html index 7be952c68d..493763756b 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html @@ -108,17 +108,17 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-dialog.component.html index abe511fc3f..64a808fe98 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-dialog.component.html @@ -37,17 +37,17 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html index 8eee1d980e..5c992d9e04 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html @@ -47,9 +47,9 @@ remove_circle_outline -
+
device-profile.no-create-alarm-rules + class="tb-prompt required">device-profile.add-create-alarm-rule-prompt
- +
diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html index 4fbc413d98..f74b19891e 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html @@ -41,7 +41,7 @@
-
+
device-profile.name diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-dialog.component.html index 79bf7983a7..75f75906d0 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-dialog.component.html @@ -15,7 +15,7 @@ limitations under the License. --> - +

{{ (isAdd ? 'tenant-profile.add' : 'tenant-profile.edit' ) | translate }}

@@ -37,11 +37,6 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html index 2d637c4066..f1d6790ae8 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html @@ -53,17 +53,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html index a61b3a80c6..46a0d213a4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html @@ -147,16 +147,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.html index d09b7aae24..8127b77e5d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.html @@ -38,16 +38,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarm-filter-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/alarm-filter-panel.component.html index b150110fca..3a834621e6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarm-filter-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarm-filter-panel.component.html @@ -50,17 +50,16 @@
+ -
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.html index 2ee1222168..05aabbb7a7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.html @@ -28,7 +28,6 @@
diff --git a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.html b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.html index 0b33828bc4..2fcc346477 100644 --- a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.html +++ b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.html @@ -40,17 +40,17 @@
- +
diff --git a/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.html b/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.html index 5c98c02fa1..18d71968fd 100644 --- a/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.html +++ b/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.html @@ -39,17 +39,17 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/add-widget-dialog.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/add-widget-dialog.component.html index 614c63d980..fb0d17e73a 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/add-widget-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/add-widget-dialog.component.html @@ -41,11 +41,6 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-settings-dialog.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-settings-dialog.component.html index 082ff62633..80d5412226 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-settings-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-settings-dialog.component.html @@ -152,17 +152,17 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/layout/manage-dashboard-layouts-dialog.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/layout/manage-dashboard-layouts-dialog.component.html index 8c539a8cb8..0014fcb0cb 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/layout/manage-dashboard-layouts-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/layout/manage-dashboard-layouts-dialog.component.html @@ -54,11 +54,6 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.html index 0a20f6a48e..3097e0e303 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.html @@ -54,7 +54,6 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/dashboard-state-dialog.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/states/dashboard-state-dialog.component.html index cc27d98d9a..52788ca0e8 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/dashboard-state-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/dashboard-state-dialog.component.html @@ -52,16 +52,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.html index d75b4f41c3..7f4f486278 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.html @@ -136,16 +136,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html index d927e5222c..a803d35b1d 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html @@ -70,7 +70,7 @@ - diff --git a/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html index 0c99fd7040..2936b60911 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html @@ -36,17 +36,17 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.html b/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.html index 9cd30ab768..926c4d7bcc 100644 --- a/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.html @@ -46,16 +46,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.html index e8df333ecd..75ccbb764e 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.html @@ -40,16 +40,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-link-dialog.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-link-dialog.component.html index 3016ab6d70..12de2706f5 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-link-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-link-dialog.component.html @@ -39,16 +39,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html index 52ac55275c..a66a3ab995 100644 --- a/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html @@ -41,11 +41,6 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/widget/save-widget-type-as-dialog.component.html b/ui-ngx/src/app/modules/home/pages/widget/save-widget-type-as-dialog.component.html index 8af9a379d9..0ae1e24a07 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/save-widget-type-as-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/widget/save-widget-type-as-dialog.component.html @@ -46,17 +46,17 @@
- +
diff --git a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html index 8e1b64381d..4de07b1f80 100644 --- a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html @@ -34,7 +34,6 @@ diff --git a/ui-ngx/src/app/shared/components/dialog/json-object-edit-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/json-object-edit-dialog.component.html index 8dc4f5b1d2..9d5ee6c6ef 100644 --- a/ui-ngx/src/app/shared/components/dialog/json-object-edit-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/json-object-edit-dialog.component.html @@ -41,17 +41,16 @@
- +
diff --git a/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html index 698ecb75ae..365445ed02 100644 --- a/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html @@ -109,18 +109,17 @@ {{ 'rulenode.test' | translate }} - + diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index c462c150c0..398d5dd9d1 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -140,19 +140,18 @@
+ -
diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 9e92defa45..f6850dbc57 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -26,7 +26,7 @@ import { EntityInfoData } from '@shared/models/entity.models'; import { KeyFilter } from '@shared/models/query/query.models'; import { TimeUnit } from '@shared/models/time/time.models'; import * as _moment from 'moment-timezone'; -import { AbstractControl, FormGroup } from '@angular/forms'; +import { AbstractControl, FormGroup, ValidationErrors } from '@angular/forms'; export enum DeviceProfileType { DEFAULT = 'DEFAULT' @@ -87,7 +87,7 @@ export const deviceProvisionTypeTranslationMap = new Map( [ @@ -303,6 +303,18 @@ export interface AlarmRule { schedule?: AlarmSchedule; } +export function alarmRuleValidator(control: AbstractControl): ValidationErrors | null { + const alarmRule: AlarmRule = control.value; + return alarmRuleValid(alarmRule) ? null : {alarmRule: true}; +} + +function alarmRuleValid(alarmRule: AlarmRule): boolean { + if (!alarmRule || !alarmRule.condition || !alarmRule.condition.condition || !alarmRule.condition.condition.length) { + return false; + } + return true; +} + export interface DeviceProfileAlarm { id: string; alarmType: string; @@ -312,6 +324,34 @@ export interface DeviceProfileAlarm { propagateRelationTypes?: Array; } +export function deviceProfileAlarmValidator(control: AbstractControl): ValidationErrors | null { + const deviceProfileAlarm: DeviceProfileAlarm = control.value; + if (deviceProfileAlarm && deviceProfileAlarm.id && deviceProfileAlarm.alarmType && + deviceProfileAlarm.createRules) { + const severities = Object.keys(deviceProfileAlarm.createRules); + if (severities.length) { + let alarmRulesValid = true; + for (const severity of severities) { + const alarmRule = deviceProfileAlarm.createRules[severity]; + if (!alarmRuleValid(alarmRule)) { + alarmRulesValid = false; + break; + } + } + if (alarmRulesValid) { + if (deviceProfileAlarm.clearRule && !alarmRuleValid(deviceProfileAlarm.clearRule)) { + alarmRulesValid = false; + } + } + if (alarmRulesValid) { + return null; + } + } + } + return {deviceProfileAlarm: true}; +} + + export interface DeviceProfileData { configuration: DeviceProfileConfiguration; transportConfiguration: DeviceProfileTransportConfiguration; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index b5b34f8fe5..453d5d2abd 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -908,6 +908,7 @@ "create-alarm-pattern": "Create {{alarmType}} alarm", "create-alarm-rules": "Create alarm rules", "no-create-alarm-rules": "No create conditions configured", + "add-create-alarm-rule-prompt": "Please add create alarm rule", "clear-alarm-rule": "Clear alarm rule", "no-clear-alarm-rule": "No clear condition configured", "add-create-alarm-rule": "Add create condition", diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index ac717e783e..190fbb1ba7 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -331,6 +331,9 @@ pre.tb-highlight { font-weight: 400; line-height: 18px; color: rgba(0, 0, 0, .38); + &.required { + color: rgb(221, 44, 0); + } } .tb-fullscreen { From c5459fe9e534706d05f7b8cb5a3727c2c15bc69d Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Wed, 14 Oct 2020 14:34:56 +0300 Subject: [PATCH 209/463] Entities table: fix data displaying and sorting, when labels with custom translations are used --- .../components/widget/lib/entities-table-widget.component.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts index dae33f3cfe..b9791b5ae9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts @@ -352,7 +352,8 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni } dataKeys.push(dataKey); - dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label); + dataKey.label = this.utils.customTranslation(dataKey.label, dataKey.label); + dataKey.title = dataKey.label; dataKey.def = 'def' + this.columns.length; const keySettings: TableWidgetDataKeySettings = dataKey.settings; if (dataKey.type === DataKeyType.entityField && @@ -374,7 +375,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni } if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) { - this.defaultSortOrder = this.settings.defaultSortOrder; + this.defaultSortOrder = this.utils.customTranslation(this.settings.defaultSortOrder, this.settings.defaultSortOrder); } this.pageLink.sortOrder = entityDataSortOrderFromString(this.defaultSortOrder, this.columns); From 7565afca3a478928f52ad10d4f4774af4a742dc0 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 14 Oct 2020 16:43:07 +0300 Subject: [PATCH 210/463] Several OAuth2 improvements --- .../github_config.json | 1 + .../server/controller/OAuth2Controller.java | 18 ++++ .../oauth2/OAuth2ClientsDomainParams.java | 6 +- .../data/oauth2/OAuth2ClientsParams.java | 6 +- .../server/dao/oauth2/OAuth2Utils.java | 12 +-- .../dao/service/BaseOAuth2ServiceTest.java | 93 ++++++++++--------- ui-ngx/src/app/core/http/oauth2.service.ts | 4 + .../home/pages/admin/admin-routing.module.ts | 31 ++++++- .../pages/admin/oauth2-settings.component.ts | 31 +++++-- 9 files changed, 134 insertions(+), 68 deletions(-) diff --git a/application/src/main/data/json/system/oauth2_config_templates/github_config.json b/application/src/main/data/json/system/oauth2_config_templates/github_config.json index 0a1ae5779b..439043d961 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/github_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/github_config.json @@ -10,6 +10,7 @@ "mapperConfig": { "type": "GITHUB", "basic": { + "firstNameAttributeKey": "name", "tenantNameStrategy": "DOMAIN" } }, diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index 323eb19bcd..fc5e0a1426 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -16,6 +16,7 @@ package org.thingsboard.server.controller; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @@ -23,6 +24,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; import org.thingsboard.server.common.data.oauth2.SchemeType; +import org.thingsboard.server.dao.oauth2.OAuth2Configuration; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -36,6 +38,10 @@ import java.util.List; @RequestMapping("/api") @Slf4j public class OAuth2Controller extends BaseController { + + @Autowired + private OAuth2Configuration oAuth2Configuration; + @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) @ResponseBody public List getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { @@ -70,4 +76,16 @@ public class OAuth2Controller extends BaseController { throw handleException(e); } } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + @RequestMapping(value = "/oauth2/loginProcessingUrl", method = RequestMethod.GET) + @ResponseBody + public String getLoginProcessingUrl() throws ThingsboardException { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ); + return "\"" + oAuth2Configuration.getLoginProcessingUrl() + "\""; + } catch (Exception e) { + throw handleException(e); + } + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java index d93401f5cc..4e932fad85 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java @@ -27,6 +27,6 @@ import java.util.Set; @NoArgsConstructor @AllArgsConstructor public class OAuth2ClientsDomainParams { - private Set domainInfos; - private Set clientRegistrations; -} \ No newline at end of file + private List domainInfos; + private List clientRegistrations; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java index ee20021aca..ad5d5305bd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java @@ -16,6 +16,8 @@ package org.thingsboard.server.common.data.oauth2; import lombok.*; + +import java.util.List; import java.util.Set; @EqualsAndHashCode @@ -26,5 +28,5 @@ import java.util.Set; @AllArgsConstructor public class OAuth2ClientsParams { private boolean enabled; - private Set domainsParams; -} \ No newline at end of file + private List domainsParams; +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java index a96a0d36c8..1ed0332bf8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java @@ -32,19 +32,19 @@ public class OAuth2Utils { } public static OAuth2ClientsParams toOAuth2Params(List extendedOAuth2ClientRegistrationInfos) { - Map> domainsByInfoId = new HashMap<>(); - Map infoById = new HashMap<>(); + Map> domainsByInfoId = new LinkedHashMap<>(); + Map infoById = new LinkedHashMap<>(); for (ExtendedOAuth2ClientRegistrationInfo extendedClientRegistrationInfo : extendedOAuth2ClientRegistrationInfos) { String domainName = extendedClientRegistrationInfo.getDomainName(); SchemeType domainScheme = extendedClientRegistrationInfo.getDomainScheme(); - domainsByInfoId.computeIfAbsent(extendedClientRegistrationInfo.getId(), key -> new HashSet<>()) + domainsByInfoId.computeIfAbsent(extendedClientRegistrationInfo.getId(), key -> new ArrayList<>()) .add(new DomainInfo(domainScheme, domainName)); infoById.put(extendedClientRegistrationInfo.getId(), extendedClientRegistrationInfo); } - Map, OAuth2ClientsDomainParams> domainParamsMap = new HashMap<>(); + Map, OAuth2ClientsDomainParams> domainParamsMap = new HashMap<>(); domainsByInfoId.forEach((clientRegistrationInfoId, domainInfos) -> { domainParamsMap.computeIfAbsent(domainInfos, - key -> new OAuth2ClientsDomainParams(key, new HashSet<>()) + key -> new OAuth2ClientsDomainParams(key, new ArrayList<>()) ) .getClientRegistrations() .add(toClientRegistrationDto(infoById.get(clientRegistrationInfoId))); @@ -52,7 +52,7 @@ public class OAuth2Utils { boolean enabled = extendedOAuth2ClientRegistrationInfos.stream() .map(OAuth2ClientRegistrationInfo::isEnabled) .findFirst().orElse(false); - return new OAuth2ClientsParams(enabled, new HashSet<>(domainParamsMap.values())); + return new OAuth2ClientsParams(enabled, new ArrayList<>(domainParamsMap.values())); } public static ClientRegistrationDto toClientRegistrationDto(OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo) { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java index b54505a3d7..a927f1e78c 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.service; +import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.junit.After; import org.junit.Assert; @@ -29,7 +30,7 @@ import java.util.*; import java.util.stream.Collectors; public class BaseOAuth2ServiceTest extends AbstractServiceTest { - private static final OAuth2ClientsParams EMPTY_PARAMS = new OAuth2ClientsParams(false, new HashSet<>()); + private static final OAuth2ClientsParams EMPTY_PARAMS = new OAuth2ClientsParams(false, new ArrayList<>()); @Autowired protected OAuth2Service oAuth2Service; @@ -48,14 +49,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test(expected = DataValidationException.class) public void testSaveHttpAndMixedDomainsTogether() { - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto(), validClientRegistrationDto() @@ -67,14 +68,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test(expected = DataValidationException.class) public void testSaveHttpsAndMixedDomainsTogether() { - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build(), DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto(), validClientRegistrationDto() @@ -131,20 +132,20 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { Assert.assertNotNull(foundClientsParams); Assert.assertEquals(clientsParams, foundClientsParams); - OAuth2ClientsParams newClientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsParams newClientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("another-domain").scheme(SchemeType.HTTPS).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto() )) .build(), OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("test-domain").scheme(SchemeType.MIXED).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto() )) .build() @@ -157,22 +158,22 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testGetOAuth2Clients() { - Set firstGroup = Sets.newHashSet( + List firstGroup = Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto(), validClientRegistrationDto(), validClientRegistrationDto() ); - Set secondGroup = Sets.newHashSet( + List secondGroup = Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto() ); - Set thirdGroup = Sets.newHashSet( + List thirdGroup = Lists.newArrayList( validClientRegistrationDto() ); - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() @@ -180,14 +181,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { .clientRegistrations(firstGroup) .build(), OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() )) .clientRegistrations(secondGroup) .build(), OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() )) @@ -285,15 +286,15 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testGetOAuth2ClientsForHttpAndHttps() { - Set firstGroup = Sets.newHashSet( + List firstGroup = Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto(), validClientRegistrationDto(), validClientRegistrationDto() ); - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build() @@ -335,25 +336,25 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testGetDisabledOAuth2Clients() { - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto(), validClientRegistrationDto() )) .build(), OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto() )) @@ -374,35 +375,35 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testFindAllClientRegistrationInfos() { - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto(), validClientRegistrationDto() )) .build(), OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto() )) .build(), OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto() )) .build() @@ -423,35 +424,35 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testFindClientRegistrationById() { - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto(), validClientRegistrationDto() )) .build(), OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto() )) .build(), OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto() )) .build() @@ -466,14 +467,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { } private OAuth2ClientsParams createDefaultClientsParams() { - return new OAuth2ClientsParams(true, Sets.newHashSet( + return new OAuth2ClientsParams(true, Lists.newArrayList( OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto(), validClientRegistrationDto(), @@ -481,11 +482,11 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { )) .build(), OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto() )) diff --git a/ui-ngx/src/app/core/http/oauth2.service.ts b/ui-ngx/src/app/core/http/oauth2.service.ts index a8f890223d..2eff9fa339 100644 --- a/ui-ngx/src/app/core/http/oauth2.service.ts +++ b/ui-ngx/src/app/core/http/oauth2.service.ts @@ -41,4 +41,8 @@ export class OAuth2Service { return this.http.post('/api/oauth2/config', OAuth2Setting, defaultHttpOptionsFromConfig(config)); } + + public getLoginProcessingUrl(config?: RequestConfig): Observable { + return this.http.get(`/api/oauth2/loginProcessingUrl`, defaultHttpOptionsFromConfig(config)); + } } diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts index af7dc98e19..7fefbfb521 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts @@ -14,8 +14,8 @@ /// limitations under the License. /// -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; +import { Injectable, NgModule } from '@angular/core'; +import { Resolve, RouterModule, Routes } from '@angular/router'; import { MailServerComponent } from '@modules/home/pages/admin/mail-server.component'; import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; @@ -23,6 +23,25 @@ import { Authority } from '@shared/models/authority.enum'; import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component'; import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component'; import { OAuth2SettingsComponent } from '@home/pages/admin/oauth2-settings.component'; +import { User } from '@shared/models/user.model'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { UserService } from '@core/http/user.service'; +import { Observable } from 'rxjs'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { OAuth2Service } from '@core/http/oauth2.service'; +import { UserProfileResolver } from '@home/pages/profile/profile-routing.module'; + +@Injectable() +export class OAuth2LoginProcessingUrlResolver implements Resolve { + + constructor(private oauth2Service: OAuth2Service) { + } + + resolve(): Observable { + return this.oauth2Service.getLoginProcessingUrl(); + } +} const routes: Routes = [ { @@ -90,6 +109,9 @@ const routes: Routes = [ label: 'admin.oauth2.oauth2', icon: 'security' } + }, + resolve: { + loginProcessingUrl: OAuth2LoginProcessingUrlResolver } } ] @@ -98,6 +120,9 @@ const routes: Routes = [ @NgModule({ imports: [RouterModule.forChild(routes)], - exports: [RouterModule] + exports: [RouterModule], + providers: [ + OAuth2LoginProcessingUrlResolver + ] }) export class AdminRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index 58cbe536d7..37ae0433d3 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -43,6 +43,7 @@ import { DialogService } from '@core/services/dialog.service'; import { TranslateService } from '@ngx-translate/core'; import { isDefined, isDefinedAndNotNull } from '@core/utils'; import { OAuth2Service } from '@core/http/oauth2.service'; +import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'tb-oauth2-settings', @@ -87,7 +88,10 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha templateProvider = ['Custom']; + private loginProcessingUrl: string = this.route.snapshot.data.loginProcessingUrl; + constructor(protected store: Store, + private route: ActivatedRoute, private oauth2Service: OAuth2Service, private fb: FormBuilder, private dialogService: DialogService, @@ -130,7 +134,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha return this.oauth2SettingsForm.get('domainsParams') as FormArray; } - private formBasicGroup(mapperConfigBasic?: MapperConfigBasic): FormGroup { + private formBasicGroup(type: MapperConfigType, mapperConfigBasic?: MapperConfigBasic): FormGroup { let tenantNamePattern; if (mapperConfigBasic?.tenantNamePattern) { tenantNamePattern = mapperConfigBasic.tenantNamePattern; @@ -138,16 +142,20 @@ 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 : ''], tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : TenantNameStrategy.DOMAIN], tenantNamePattern: [tenantNamePattern, Validators.required], customerNamePattern: [mapperConfigBasic?.customerNamePattern ? mapperConfigBasic.customerNamePattern : null], defaultDashboardName: [mapperConfigBasic?.defaultDashboardName ? mapperConfigBasic.defaultDashboardName : null], - alwaysFullScreen: [mapperConfigBasic?.alwaysFullScreen ? mapperConfigBasic.alwaysFullScreen : false] + alwaysFullScreen: [isDefinedAndNotNull(mapperConfigBasic?.alwaysFullScreen) ? mapperConfigBasic.alwaysFullScreen : false] }); + if (MapperConfigType.GITHUB !== type) { + basicGroup.addControl('emailAttributeKey', + this.fb.control( mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required)); + } + this.subscriptions.push(basicGroup.get('tenantNameStrategy').valueChanges.subscribe((domain) => { if (domain === 'CUSTOM') { basicGroup.get('tenantNamePattern').enable(); @@ -279,9 +287,12 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha clientRegistration?.userNameAttributeName ? clientRegistration.userNameAttributeName : 'email', Validators.required], mapperConfig: this.fb.group({ allowUserCreation: [ - clientRegistration?.mapperConfig?.allowUserCreation ? clientRegistration.mapperConfig.allowUserCreation : true + isDefinedAndNotNull(clientRegistration?.mapperConfig?.allowUserCreation) ? + clientRegistration.mapperConfig.allowUserCreation : true + ], + activateUser: [ + isDefinedAndNotNull(clientRegistration?.mapperConfig?.activateUser) ? clientRegistration.mapperConfig.activateUser : false ], - activateUser: [clientRegistration?.mapperConfig?.activateUser ? clientRegistration.mapperConfig.activateUser : false], type: [ clientRegistration?.mapperConfig?.type ? clientRegistration.mapperConfig.type : MapperConfigType.BASIC, Validators.required ] @@ -308,7 +319,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha return clientRegistrationFormGroup; } - private validateScope (control: AbstractControl): ValidationErrors | null { + private validateScope(control: AbstractControl): ValidationErrors | null { const scope: string[] = control.value; if (!scope || !scope.length) { return { @@ -347,7 +358,11 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom)); } else { mapperConfig.removeControl('custom'); - mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic)); + if (mapperConfig.get('basic')) { + mapperConfig.setControl('basic', this.formBasicGroup(type, predefinedValue?.basic)); + } else { + mapperConfig.addControl('basic', this.formBasicGroup(type, predefinedValue?.basic)); + } } } @@ -490,7 +505,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } else { protocol = domainInfo.scheme === DomainSchema.MIXED ? DomainSchema.HTTPS.toLowerCase() : domainInfo.scheme.toLowerCase(); } - return `${protocol}://${domainInfo.name}/login/oauth2/code/`; + return `${protocol}://${domainInfo.name}${this.loginProcessingUrl}`; } return ''; } From 9a7d5a437e2cc165ed7fe6976a92d7a268779ea0 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 14 Oct 2020 18:22:03 +0300 Subject: [PATCH 211/463] Fix OAuth2 domain params order --- .../java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java index 1ed0332bf8..00b4f7e941 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java @@ -41,7 +41,7 @@ public class OAuth2Utils { .add(new DomainInfo(domainScheme, domainName)); infoById.put(extendedClientRegistrationInfo.getId(), extendedClientRegistrationInfo); } - Map, OAuth2ClientsDomainParams> domainParamsMap = new HashMap<>(); + Map, OAuth2ClientsDomainParams> domainParamsMap = new LinkedHashMap<>(); domainsByInfoId.forEach((clientRegistrationInfoId, domainInfos) -> { domainParamsMap.computeIfAbsent(domainInfos, key -> new OAuth2ClientsDomainParams(key, new ArrayList<>()) From 965870a980ec613076e0ec1aa311d5bff85f1c40 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 15 Oct 2020 12:16:14 +0300 Subject: [PATCH 212/463] UI: Fixed clear content for ace editor and show toast error --- .../shared/components/js-func.component.ts | 10 ++-- .../components/json-content.component.ts | 49 +++++++++++-------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/ui-ngx/src/app/shared/components/js-func.component.ts b/ui-ngx/src/app/shared/components/js-func.component.ts index 72e30462e5..5515ba055f 100644 --- a/ui-ngx/src/app/shared/components/js-func.component.ts +++ b/ui-ngx/src/app/shared/components/js-func.component.ts @@ -36,7 +36,6 @@ import { TranslateService } from '@ngx-translate/core'; import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; import { ResizeObserver } from '@juggle/resize-observer'; import { TbEditorCompleter } from '@shared/models/ace/completion.models'; -import { widgetEditorCompleter } from '@home/pages/widget/widget-editor.models'; @Component({ selector: 'tb-js-func', @@ -64,6 +63,7 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, private jsEditor: ace.Ace.Editor; private editorsResizeCaf: CancelAnimationFrame; private editorResize$: ResizeObserver; + private ignoreChange = false; toastTargetId = `jsFuncEditor-${guid()}`; @@ -154,8 +154,10 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, this.jsEditor.session.setUseWrapMode(true); this.jsEditor.setValue(this.modelValue ? this.modelValue : '', -1); this.jsEditor.on('change', () => { - this.cleanupJsErrors(); - this.updateView(); + if (!this.ignoreChange) { + this.cleanupJsErrors(); + this.updateView(); + } }); if (this.editorCompleter) { this.jsEditor.completers = [this.editorCompleter, ...(this.jsEditor.completers || [])]; @@ -332,7 +334,9 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, writeValue(value: string): void { this.modelValue = value; if (this.jsEditor) { + this.ignoreChange = true; this.jsEditor.setValue(this.modelValue ? this.modelValue : '', -1); + this.ignoreChange = false; } } diff --git a/ui-ngx/src/app/shared/components/json-content.component.ts b/ui-ngx/src/app/shared/components/json-content.component.ts index 242b4b6359..3339863cd2 100644 --- a/ui-ngx/src/app/shared/components/json-content.component.ts +++ b/ui-ngx/src/app/shared/components/json-content.component.ts @@ -61,6 +61,7 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid private jsonEditor: ace.Ace.Editor; private editorsResizeCaf: CancelAnimationFrame; private editorResize$: ResizeObserver; + private ignoreChange = false; toastTargetId = `jsonContentEditor-${guid()}`; @@ -140,8 +141,13 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid this.jsonEditor.session.setUseWrapMode(true); this.jsonEditor.setValue(this.contentBody ? this.contentBody : '', -1); this.jsonEditor.on('change', () => { - this.cleanupJsonErrors(); - this.updateView(); + if (!this.ignoreChange) { + this.cleanupJsonErrors(); + this.updateView(); + } + }); + this.jsonEditor.on('blur', () => { + this.contentValid = !this.validateContent || this.doValidate(true); }); this.editorResize$ = new ResizeObserver(() => { this.onAceEditorResize(); @@ -210,34 +216,36 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid this.cleanupJsonErrors(); this.contentValid = true; this.propagateChange(this.contentBody); - this.contentValid = this.doValidate(); + this.contentValid = this.doValidate(true); this.propagateChange(this.contentBody); } } - private doValidate(): boolean { + private doValidate(showErrorToast = false): boolean { try { if (this.validateContent && this.contentType === ContentType.JSON) { JSON.parse(this.contentBody); } return true; } catch (ex) { - let errorInfo = 'Error:'; - if (ex.name) { - errorInfo += ' ' + ex.name + ':'; - } - if (ex.message) { - errorInfo += ' ' + ex.message; + if (showErrorToast) { + let errorInfo = 'Error:'; + if (ex.name) { + errorInfo += ' ' + ex.name + ':'; + } + if (ex.message) { + errorInfo += ' ' + ex.message; + } + this.store.dispatch(new ActionNotificationShow( + { + message: errorInfo, + type: 'error', + target: this.toastTargetId, + verticalPosition: 'bottom', + horizontalPosition: 'left' + })); + this.errorShowed = true; } - this.store.dispatch(new ActionNotificationShow( - { - message: errorInfo, - type: 'error', - target: this.toastTargetId, - verticalPosition: 'bottom', - horizontalPosition: 'left' - })); - this.errorShowed = true; return false; } } @@ -256,8 +264,9 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid this.contentBody = value; this.contentValid = true; if (this.jsonEditor) { + this.ignoreChange = true; this.jsonEditor.setValue(this.contentBody ? this.contentBody : '', -1); - // this.jsonEditor. + this.ignoreChange = false; } } From a29aa64497400535c22de442ba933c1e6a5c7948 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 15 Oct 2020 12:51:02 +0300 Subject: [PATCH 213/463] Add OAuth2 settings menu to SysAdmin home page --- ui-ngx/src/app/core/services/menu.service.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index e44c3c6ec2..4e98f375c2 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -191,6 +191,11 @@ export class MenuService { name: 'admin.security-settings', icon: 'security', path: '/settings/security-settings' + }, + { + name: 'admin.oauth2.oauth2', + icon: 'security', + path: '/settings/oauth2' } ] } From 02932ce2531a670a2c41757164674c0e55cb3363 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 15 Oct 2020 15:23:39 +0300 Subject: [PATCH 214/463] UI: Fixed null id in entity-select component for aliasType CURRENT_TENANT, CURRENT_USER, CURRENT_USER_OWNER --- .../entity/entity-select.component.ts | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts index fb1c23e93a..0fc3d95086 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts @@ -97,10 +97,6 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte ngOnInit() { this.entitySelectFormGroup.get('entityType').valueChanges.subscribe( (value) => { - if(value === AliasEntityType.CURRENT_TENANT || value === AliasEntityType.CURRENT_USER || - value === AliasEntityType.CURRENT_USER_OWNER) { - this.modelValue.id = NULL_UUID; - } this.updateView(value, this.modelValue.id); } ); @@ -140,20 +136,23 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte } updateView(entityType: EntityType | AliasEntityType | null, entityId: string | null) { - if (this.modelValue.entityType !== entityType || - this.modelValue.id !== entityId) { - this.modelValue = { - entityType, - id: this.modelValue.entityType !== entityType ? null : entityId - }; - if (this.modelValue.entityType && (this.modelValue.id || - this.modelValue.entityType === AliasEntityType.CURRENT_TENANT || - this.modelValue.entityType === AliasEntityType.CURRENT_USER || - this.modelValue.entityType === AliasEntityType.CURRENT_USER_OWNER)) { - this.propagateChange(this.modelValue); - } else { - this.propagateChange(null); - } + if (this.modelValue.entityType !== entityType || this.modelValue.id !== entityId) { + this.modelValue = { + entityType, + id: this.modelValue.entityType !== entityType ? null : entityId + }; + + if (this.modelValue.entityType === AliasEntityType.CURRENT_TENANT + || this.modelValue.entityType === AliasEntityType.CURRENT_USER + || this.modelValue.entityType === AliasEntityType.CURRENT_USER_OWNER) { + this.modelValue.id = NULL_UUID; + } + + if (this.modelValue.entityType && this.modelValue.id) { + this.propagateChange(this.modelValue); + } else { + this.propagateChange(null); + } } } } From 6ea39e835e3203bfb4bcc6d6e6f7cf7e4552d16d Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 15 Oct 2020 18:16:27 +0300 Subject: [PATCH 215/463] Transport Rate Limits are now configurable via Tenant Profile --- .../device_profile/rule_chain_template.json | 3 +- .../server/controller/TenantController.java | 4 +- .../controller/TenantProfileController.java | 5 +- .../profile/DefaultTbDeviceProfileCache.java | 16 +- .../service/profile/TbDeviceProfileCache.java | 3 + .../queue/DefaultTbClusterService.java | 56 ++++-- .../service/queue/TbClusterService.java | 13 +- .../transport/DefaultTransportApiService.java | 63 +++---- .../src/main/resources/thingsboard.yml | 6 +- common/queue/src/main/proto/queue.proto | 47 +++-- ....java => TransportDeviceProfileCache.java} | 2 +- .../common/transport/TransportService.java | 9 +- .../TransportTenantProfileCache.java | 38 +++++ .../DefaultTransportRateLimitFactory.java | 47 +++++ .../DefaultTransportRateLimitService.java | 115 +++++++++++++ .../limits/DummyTransportRateLimit.java | 30 ++++ .../limits/SimpleTransportRateLimit.java | 34 ++++ .../transport/limits/TransportRateLimit.java | 24 +++ .../limits/TransportRateLimitFactory.java | 24 +++ .../limits/TransportRateLimitService.java | 36 ++++ .../limits/TransportRateLimitType.java | 33 ++++ .../profile/TenantProfileUpdateResult.java | 30 ++++ ...> DefaultTransportDeviceProfileCache.java} | 6 +- .../service/DefaultTransportService.java | 160 ++++++++---------- .../DefaultTransportTenantProfileCache.java | 154 +++++++++++++++++ .../TransportTenantRoutingInfoService.java | 24 +-- .../util/DataDecodingEncodingService.java | 2 - .../src/main/resources/tb-coap-transport.yml | 4 - .../src/main/resources/tb-http-transport.yml | 4 - .../src/main/resources/tb-mqtt-transport.yml | 4 - 30 files changed, 785 insertions(+), 211 deletions(-) rename common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/{TransportProfileCache.java => TransportDeviceProfileCache.java} (95%) create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportTenantProfileCache.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitFactory.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DummyTransportRateLimit.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimit.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitFactory.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/profile/TenantProfileUpdateResult.java rename common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/{DefaultTransportProfileCache.java => DefaultTransportDeviceProfileCache.java} (91%) create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java diff --git a/application/src/main/data/json/tenant/device_profile/rule_chain_template.json b/application/src/main/data/json/tenant/device_profile/rule_chain_template.json index 3d076ff812..da9a95b423 100644 --- a/application/src/main/data/json/tenant/device_profile/rule_chain_template.json +++ b/application/src/main/data/json/tenant/device_profile/rule_chain_template.json @@ -94,7 +94,8 @@ "name": "Device Profile Node", "debugMode": false, "configuration": { - "persistAlarmRulesState": false + "persistAlarmRulesState": false, + "fetchAlarmRulesStateOnStart": false } } ], diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantController.java b/application/src/main/java/org/thingsboard/server/controller/TenantController.java index 7c23545374..83e3d98b1b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantController.java @@ -92,6 +92,7 @@ public class TenantController extends BaseController { installScripts.createDefaultRuleChains(tenant.getId()); } tenantProfileCache.evict(tenant.getId()); + tbClusterService.onTenantChange(tenant, null); return tenant; } catch (Exception e) { throw handleException(e); @@ -105,9 +106,10 @@ public class TenantController extends BaseController { checkParameter("tenantId", strTenantId); try { TenantId tenantId = new TenantId(toUUID(strTenantId)); - checkTenantId(tenantId, Operation.DELETE); + Tenant tenant = checkTenantId(tenantId, Operation.DELETE); tenantService.deleteTenant(tenantId); tenantProfileCache.evict(tenantId); + tbClusterService.onTenantDelete(tenant, null); tbClusterService.onEntityStateChange(tenantId, tenantId, ComponentLifecycleEvent.DELETED); } catch (Exception e) { throw handleException(e); diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java index 4c9d541ea6..0941fac725 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -96,6 +97,7 @@ public class TenantProfileController extends BaseController { tenantProfile = checkNotNull(tenantProfileService.saveTenantProfile(getTenantId(), tenantProfile)); tenantProfileCache.put(tenantProfile); + tbClusterService.onTenantProfileChange(tenantProfile, null); tbClusterService.onEntityStateChange(TenantId.SYS_TENANT_ID, tenantProfile.getId(), newTenantProfile ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); return tenantProfile; @@ -111,8 +113,9 @@ public class TenantProfileController extends BaseController { checkParameter("tenantProfileId", strTenantProfileId); try { TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId)); - checkTenantProfileId(tenantProfileId, Operation.DELETE); + TenantProfile profile = checkTenantProfileId(tenantProfileId, Operation.DELETE); tenantProfileService.deleteTenantProfile(getTenantId(), tenantProfileId); + tbClusterService.onTenantProfileDelete(profile, null); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java b/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java index 2d0861f636..6784405adb 100644 --- a/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java +++ b/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java @@ -61,7 +61,7 @@ public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache { profile = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId); if (profile != null) { deviceProfilesMap.put(deviceProfileId, profile); - log.info("[{}] Fetch device profile into cache: {}", profile.getId(), profile); + log.debug("[{}] Fetch device profile into cache: {}", profile.getId(), profile); } } } finally { @@ -91,7 +91,7 @@ public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache { public void put(DeviceProfile profile) { if (profile.getId() != null) { deviceProfilesMap.put(profile.getId(), profile); - log.info("[{}] pushed device profile to cache: {}", profile.getId(), profile); + log.debug("[{}] pushed device profile to cache: {}", profile.getId(), profile); notifyListeners(profile); } } @@ -99,7 +99,7 @@ public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache { @Override public void evict(TenantId tenantId, DeviceProfileId profileId) { DeviceProfile oldProfile = deviceProfilesMap.remove(profileId); - log.info("[{}] evict device profile from cache: {}", profileId, oldProfile); + log.debug("[{}] evict device profile from cache: {}", profileId, oldProfile); DeviceProfile newProfile = get(tenantId, profileId); if (newProfile != null) { notifyListeners(newProfile); @@ -116,6 +116,16 @@ public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache { listeners.computeIfAbsent(tenantId, id -> new ConcurrentHashMap<>()).put(listenerId, listener); } + @Override + public DeviceProfile find(DeviceProfileId deviceProfileId) { + return deviceProfileService.findDeviceProfileById(TenantId.SYS_TENANT_ID, deviceProfileId); + } + + @Override + public DeviceProfile findOrCreateDeviceProfile(TenantId tenantId, String profileName) { + return deviceProfileService.findOrCreateDeviceProfile(tenantId, profileName); + } + @Override public void removeListener(TenantId tenantId, EntityId listenerId) { ConcurrentMap> tenantListeners = listeners.get(tenantId); diff --git a/application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java b/application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java index c067ead6d2..e65f297d53 100644 --- a/application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java +++ b/application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java @@ -29,4 +29,7 @@ public interface TbDeviceProfileCache extends RuleEngineDeviceProfileCache { void evict(DeviceId id); + DeviceProfile find(DeviceProfileId deviceProfileId); + + DeviceProfile findOrCreateDeviceProfile(TenantId tenantId, String deviceType); } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index a4de2d9737..9451b58e9f 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -23,11 +23,15 @@ import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; @@ -189,21 +193,51 @@ public class DefaultTbClusterService implements TbClusterService { @Override public void onDeviceProfileChange(DeviceProfile deviceProfile, TbQueueCallback callback) { - log.trace("[{}][{}] Processing device profile [{}] change event", deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile.getName()); - TransportProtos.DeviceProfileUpdateMsg profileUpdateMsg = TransportProtos.DeviceProfileUpdateMsg.newBuilder() - .setData(ByteString.copyFrom(encodingService.encode(deviceProfile))).build(); - ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setDeviceProfileUpdateMsg(profileUpdateMsg).build(); - broadcast(transportMsg); + onEntityChange(deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile, callback); + } + + @Override + public void onTenantProfileChange(TenantProfile tenantProfile, TbQueueCallback callback) { + onEntityChange(TenantId.SYS_TENANT_ID, tenantProfile.getId(), tenantProfile, callback); + } + + @Override + public void onTenantChange(Tenant tenant, TbQueueCallback callback) { + onEntityChange(TenantId.SYS_TENANT_ID, tenant.getId(), tenant, callback); + } + + @Override + public void onDeviceProfileDelete(DeviceProfile entity, TbQueueCallback callback) { + onEntityDelete(entity.getTenantId(), entity.getId(), entity.getName(), callback); } @Override - public void onDeviceProfileDelete(DeviceProfile deviceProfile, TbQueueCallback callback) { - log.trace("[{}][{}] Processing device profile [{}] delete event", deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile.getName()); - TransportProtos.DeviceProfileDeleteMsg profileDeleteMsg = TransportProtos.DeviceProfileDeleteMsg.newBuilder() - .setProfileIdMSB(deviceProfile.getId().getId().getMostSignificantBits()) - .setProfileIdLSB(deviceProfile.getId().getId().getLeastSignificantBits()) + public void onTenantProfileDelete(TenantProfile entity, TbQueueCallback callback) { + onEntityDelete(TenantId.SYS_TENANT_ID, entity.getId(), entity.getName(), callback); + } + + @Override + public void onTenantDelete(Tenant entity, TbQueueCallback callback) { + onEntityDelete(TenantId.SYS_TENANT_ID, entity.getId(), entity.getName(), callback); + } + + public void onEntityChange(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) { + log.trace("[{}][{}][{}] Processing [{}] change event", tenantId, entityid.getEntityType(), entityid.getId(), entity.getName()); + TransportProtos.EntityUpdateMsg entityUpdateMsg = TransportProtos.EntityUpdateMsg.newBuilder() + .setEntityType(entityid.getEntityType().name()) + .setData(ByteString.copyFrom(encodingService.encode(entity))).build(); + ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setEntityUpdateMsg(entityUpdateMsg).build(); + broadcast(transportMsg); + } + + private void onEntityDelete(TenantId tenantId, EntityId entityId, String name, TbQueueCallback callback) { + log.trace("[{}][{}][{}] Processing [{}] delete event", tenantId, entityId.getEntityType(), entityId.getId(), name); + TransportProtos.EntityDeleteMsg entityDeleteMsg = TransportProtos.EntityDeleteMsg.newBuilder() + .setEntityType(entityId.getEntityType().name()) + .setEntityIdMSB(entityId.getId().getMostSignificantBits()) + .setEntityIdLSB(entityId.getId().getLeastSignificantBits()) .build(); - ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setDeviceProfileDeleteMsg(profileDeleteMsg).build(); + ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setEntityDeleteMsg(entityDeleteMsg).build(); broadcast(transportMsg); } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java index cf212a06f5..838dbe0eef 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java @@ -17,7 +17,8 @@ package org.thingsboard.server.service.queue; import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; @@ -53,5 +54,13 @@ public interface TbClusterService { void onDeviceProfileChange(DeviceProfile deviceProfile, TbQueueCallback callback); - void onDeviceProfileDelete(DeviceProfile deviceProfileId, TbQueueCallback callback); + void onDeviceProfileDelete(DeviceProfile deviceProfile, TbQueueCallback callback); + + void onTenantProfileChange(TenantProfile tenantProfile, TbQueueCallback callback); + + void onTenantProfileDelete(TenantProfile tenantProfile, TbQueueCallback callback); + + void onTenantChange(Tenant tenant, TbQueueCallback callback); + + void onTenantDelete(Tenant tenant, TbQueueCallback callback); } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index c806757ddb..10360769a7 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -28,6 +28,7 @@ import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData; @@ -45,21 +46,19 @@ import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.dao.device.DeviceCredentialsService; -import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceProvisionService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; import org.thingsboard.server.dao.relation.RelationService; -import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; @@ -70,6 +69,7 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.service.executors.DbCallbackExecutorService; +import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.state.DeviceStateService; @@ -90,9 +90,7 @@ public class DefaultTransportApiService implements TransportApiService { private static final ObjectMapper mapper = new ObjectMapper(); - //TODO: Constructor dependencies; - private final DeviceProfileService deviceProfileService; - private final TenantService tenantService; + private final TbDeviceProfileCache deviceProfileCache; private final TbTenantProfileCache tenantProfileCache; private final DeviceService deviceService; private final RelationService relationService; @@ -103,17 +101,15 @@ public class DefaultTransportApiService implements TransportApiService { private final DataDecodingEncodingService dataDecodingEncodingService; private final DeviceProvisionService deviceProvisionService; - private final ConcurrentMap deviceCreationLocks = new ConcurrentHashMap<>(); - public DefaultTransportApiService(DeviceProfileService deviceProfileService, TenantService tenantService, + public DefaultTransportApiService(TbDeviceProfileCache deviceProfileCache, TbTenantProfileCache tenantProfileCache, DeviceService deviceService, RelationService relationService, DeviceCredentialsService deviceCredentialsService, DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService, TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService, DeviceProvisionService deviceProvisionService) { - this.deviceProfileService = deviceProfileService; - this.tenantService = tenantService; + this.deviceProfileCache = deviceProfileCache; this.tenantProfileCache = tenantProfileCache; this.deviceService = deviceService; this.relationService = relationService; @@ -143,11 +139,8 @@ public class DefaultTransportApiService implements TransportApiService { } else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) { return Futures.transform(handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()), value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); - } else if (transportApiRequestMsg.hasGetTenantRoutingInfoRequestMsg()) { - return Futures.transform(handle(transportApiRequestMsg.getGetTenantRoutingInfoRequestMsg()), - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); - } else if (transportApiRequestMsg.hasGetDeviceProfileRequestMsg()) { - return Futures.transform(handle(transportApiRequestMsg.getGetDeviceProfileRequestMsg()), + } else if (transportApiRequestMsg.hasEntityProfileRequestMsg()) { + return Futures.transform(handle(transportApiRequestMsg.getEntityProfileRequestMsg()), value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); } else if (transportApiRequestMsg.hasProvisionDeviceRequestMsg()) { return Futures.transform(handle(transportApiRequestMsg.getProvisionDeviceRequestMsg()), @@ -238,7 +231,7 @@ public class DefaultTransportApiService implements TransportApiService { device.setName(requestMsg.getDeviceName()); device.setType(requestMsg.getDeviceType()); device.setCustomerId(gateway.getCustomerId()); - DeviceProfile deviceProfile = deviceProfileService.findOrCreateDeviceProfile(gateway.getTenantId(), requestMsg.getDeviceType()); + DeviceProfile deviceProfile = deviceProfileCache.findOrCreateDeviceProfile(gateway.getTenantId(), requestMsg.getDeviceType()); device.setDeviceProfileId(deviceProfile.getId()); device = deviceService.saveDevice(device); relationService.saveRelationAsync(TenantId.SYS_TENANT_ID, new EntityRelation(gateway.getId(), device.getId(), "Created")); @@ -258,7 +251,7 @@ public class DefaultTransportApiService implements TransportApiService { } GetOrCreateDeviceFromGatewayResponseMsg.Builder builder = GetOrCreateDeviceFromGatewayResponseMsg.newBuilder() .setDeviceInfo(getDeviceInfoProto(device)); - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); + DeviceProfile deviceProfile = deviceProfileCache.get(device.getTenantId(), device.getDeviceProfileId()); if (deviceProfile != null) { builder.setProfileBody(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile))); } else { @@ -320,23 +313,21 @@ public class DefaultTransportApiService implements TransportApiService { .build(); } - private ListenableFuture handle(GetTenantRoutingInfoRequestMsg requestMsg) { - TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB())); - - ListenableFuture tenantProfileFuture = Futures.immediateFuture(tenantProfileCache.get(tenantId)); - return Futures.transform(tenantProfileFuture, tenantProfile -> TransportApiResponseMsg.newBuilder() - .setGetTenantRoutingInfoResponseMsg(GetTenantRoutingInfoResponseMsg.newBuilder().setIsolatedTbCore(tenantProfile.isIsolatedTbCore()) - .setIsolatedTbRuleEngine(tenantProfile.isIsolatedTbRuleEngine()).build()).build(), dbCallbackExecutorService); - } - - private ListenableFuture handle(TransportProtos.GetDeviceProfileRequestMsg requestMsg) { - DeviceProfileId profileId = new DeviceProfileId(new UUID(requestMsg.getProfileIdMSB(), requestMsg.getProfileIdLSB())); - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(TenantId.SYS_TENANT_ID, profileId); - return Futures.immediateFuture(TransportApiResponseMsg.newBuilder() - .setGetDeviceProfileResponseMsg( - TransportProtos.GetDeviceProfileResponseMsg.newBuilder() - .setData(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile))) - .build()).build()); + private ListenableFuture handle(GetEntityProfileRequestMsg requestMsg) { + EntityType entityType = EntityType.valueOf(requestMsg.getEntityType()); + UUID entityUuid = new UUID(requestMsg.getEntityIdMSB(), requestMsg.getEntityIdLSB()); + ByteString data; + if (entityType.equals(EntityType.DEVICE_PROFILE)) { + DeviceProfileId deviceProfileId = new DeviceProfileId(entityUuid); + DeviceProfile deviceProfile = deviceProfileCache.find(deviceProfileId); + data = ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile)); + } else if (entityType.equals(EntityType.TENANT)) { + TenantProfile tenantProfile = tenantProfileCache.get(new TenantId(entityUuid)); + data = ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile)); + } else { + throw new RuntimeException("Invalid entity profile request: " + entityType); + } + return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setEntityProfileResponseMsg(GetEntityProfileResponseMsg.newBuilder().setData(data).build()).build()); } private ListenableFuture getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) { @@ -348,7 +339,7 @@ public class DefaultTransportApiService implements TransportApiService { try { ValidateDeviceCredentialsResponseMsg.Builder builder = ValidateDeviceCredentialsResponseMsg.newBuilder(); builder.setDeviceInfo(getDeviceInfoProto(device)); - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); + DeviceProfile deviceProfile = deviceProfileCache.get(device.getTenantId(), device.getDeviceProfileId()); if (deviceProfile != null) { builder.setProfileBody(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile))); } else { diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index d0ddeb611a..deb28ffd99 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -502,7 +502,7 @@ js: remote: # Maximum allowed JavaScript execution errors before JavaScript will be blacklisted max_errors: "${REMOTE_JS_SANDBOX_MAX_ERRORS:3}" - # Maximum time in seconds for black listed function to stay in the list. + # Maximum time in seconds for black listed function to stay in 1:the list. max_black_list_duration_sec: "${REMOTE_JS_SANDBOX_MAX_BLACKLIST_DURATION_SEC:60}" stats: enabled: "${TB_JS_REMOTE_STATS_ENABLED:false}" @@ -512,10 +512,6 @@ transport: sessions: inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" - rate_limits: - enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}" - tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}" - device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}" json: # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 166f94b864..01d8ca4524 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -177,32 +177,26 @@ message GetOrCreateDeviceFromGatewayResponseMsg { bytes profileBody = 2; } -message GetTenantRoutingInfoRequestMsg { - int64 tenantIdMSB = 1; - int64 tenantIdLSB = 2; -} - -message GetTenantRoutingInfoResponseMsg { - bool isolatedTbCore = 1; - bool isolatedTbRuleEngine = 2; -} - -message GetDeviceProfileRequestMsg { - int64 profileIdMSB = 1; - int64 profileIdLSB = 2; +message GetEntityProfileRequestMsg { + string entityType = 1; + int64 entityIdMSB = 2; + int64 entityIdLSB = 3; } -message GetDeviceProfileResponseMsg { - bytes data = 1; +message GetEntityProfileResponseMsg { + string entityType = 1; + bytes data = 2; } -message DeviceProfileUpdateMsg { - bytes data = 1; +message EntityUpdateMsg { + string entityType = 1; + bytes data = 2; } -message DeviceProfileDeleteMsg { - int64 profileIdMSB = 1; - int64 profileIdLSB = 2; +message EntityDeleteMsg { + string entityType = 1; + int64 entityIdMSB = 2; + int64 entityIdLSB = 3; } message SessionCloseNotificationProto { @@ -482,8 +476,7 @@ message TransportApiRequestMsg { ValidateDeviceTokenRequestMsg validateTokenRequestMsg = 1; ValidateDeviceX509CertRequestMsg validateX509CertRequestMsg = 2; GetOrCreateDeviceFromGatewayRequestMsg getOrCreateDeviceRequestMsg = 3; - GetTenantRoutingInfoRequestMsg getTenantRoutingInfoRequestMsg = 4; - GetDeviceProfileRequestMsg getDeviceProfileRequestMsg = 5; + GetEntityProfileRequestMsg entityProfileRequestMsg = 4; ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6; ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7; } @@ -492,9 +485,8 @@ message TransportApiRequestMsg { message TransportApiResponseMsg { ValidateDeviceCredentialsResponseMsg validateCredResponseMsg = 1; GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2; - GetTenantRoutingInfoResponseMsg getTenantRoutingInfoResponseMsg = 4; - GetDeviceProfileResponseMsg getDeviceProfileResponseMsg = 5; - ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 6; + GetEntityProfileResponseMsg entityProfileResponseMsg = 3; + ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4; } /* Messages that are handled by ThingsBoard Core Service */ @@ -535,7 +527,8 @@ message ToTransportMsg { AttributeUpdateNotificationMsg attributeUpdateNotification = 5; ToDeviceRpcRequestMsg toDeviceRequest = 6; ToServerRpcResponseMsg toServerResponse = 7; - DeviceProfileUpdateMsg deviceProfileUpdateMsg = 8; - DeviceProfileDeleteMsg deviceProfileDeleteMsg = 9; + /* For Tenant, TenantProfile and DeviceProfile */ + EntityUpdateMsg entityUpdateMsg = 8; + EntityDeleteMsg entityDeleteMsg = 9; ProvisionDeviceResponseMsg provisionResponse = 10; } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportDeviceProfileCache.java similarity index 95% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportProfileCache.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportDeviceProfileCache.java index ee05e59010..d56c0291b7 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportProfileCache.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportDeviceProfileCache.java @@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; import java.util.Optional; -public interface TransportProfileCache { +public interface TransportDeviceProfileCache { DeviceProfile getOrCreate(DeviceProfileId id, ByteString profileBody); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 775a91f720..9a2fd75d47 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -20,11 +20,12 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; @@ -47,7 +48,7 @@ import java.util.concurrent.ScheduledExecutorService; */ public interface TransportService { - GetTenantRoutingInfoResponseMsg getRoutingInfo(GetTenantRoutingInfoRequestMsg msg); + GetEntityProfileResponseMsg getRoutingInfo(GetEntityProfileRequestMsg msg); void process(DeviceTransportType transportType, ValidateDeviceTokenRequestMsg msg, TransportServiceCallback callback); @@ -64,8 +65,6 @@ public interface TransportService { void process(ProvisionDeviceRequestMsg msg, TransportServiceCallback callback); - void getDeviceProfile(DeviceProfileId deviceProfileId, TransportServiceCallback callback); - void onProfileUpdate(DeviceProfile deviceProfile); boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportTenantProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportTenantProfileCache.java new file mode 100644 index 0000000000..1f6b31a356 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportTenantProfileCache.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport; + +import com.google.protobuf.ByteString; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; +import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; +import org.thingsboard.server.queue.discovery.TenantRoutingInfo; +import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; + +import java.util.Set; + +public interface TransportTenantProfileCache { + + TenantProfile get(TenantId tenantId); + + TenantProfileUpdateResult put(ByteString profileBody); + + boolean put(TenantId tenantId, TenantProfileId profileId); + + Set remove(TenantProfileId profileId); + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitFactory.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitFactory.java new file mode 100644 index 0000000000..362502bf17 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitFactory.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.limits; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.msg.tools.TbRateLimits; + +@Slf4j +@Component +public class DefaultTransportRateLimitFactory implements TransportRateLimitFactory { + + private static final DummyTransportRateLimit ALWAYS_TRUE = new DummyTransportRateLimit(); + + @Override + public TransportRateLimit create(TransportRateLimitType type, Object configuration) { + if (!StringUtils.isEmpty(configuration)) { + try { + return new SimpleTransportRateLimit(new TbRateLimits(configuration.toString()), configuration.toString()); + } catch (Exception e) { + log.warn("[{}] Failed to init rate limit with configuration: {}", type, configuration, e); + return ALWAYS_TRUE; + } + } else { + return ALWAYS_TRUE; + } + } + + @Override + public TransportRateLimit createDefault(TransportRateLimitType type) { + return ALWAYS_TRUE; + } +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java new file mode 100644 index 0000000000..9446022ec5 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java @@ -0,0 +1,115 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.limits; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.TenantProfileData; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.transport.TransportTenantProfileCache; +import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +@Service +@Slf4j +public class DefaultTransportRateLimitService implements TransportRateLimitService { + + private final ConcurrentMap perTenantLimits = new ConcurrentHashMap<>(); + private final ConcurrentMap perDeviceLimits = new ConcurrentHashMap<>(); + + private final TransportRateLimitFactory rateLimitFactory; + private final TransportTenantProfileCache tenantProfileCache; + + public DefaultTransportRateLimitService(TransportRateLimitFactory rateLimitFactory, TransportTenantProfileCache tenantProfileCache) { + this.rateLimitFactory = rateLimitFactory; + this.tenantProfileCache = tenantProfileCache; + } + + @Override + public TransportRateLimit getRateLimit(TenantId tenantId, TransportRateLimitType limitType) { + TransportRateLimit[] limits = perTenantLimits.get(tenantId); + if (limits == null) { + limits = fetchProfileAndInit(tenantId); + perTenantLimits.put(tenantId, limits); + } + return limits[limitType.ordinal()]; + } + + @Override + public TransportRateLimit getRateLimit(TenantId tenantId, DeviceId deviceId, TransportRateLimitType limitType) { + TransportRateLimit[] limits = perDeviceLimits.get(deviceId); + if (limits == null) { + limits = fetchProfileAndInit(tenantId); + perDeviceLimits.put(deviceId, limits); + } + return limits[limitType.ordinal()]; + } + + @Override + public void update(TenantProfileUpdateResult update) { + TransportRateLimit[] newLimits = createTransportRateLimits(update.getProfile()); + for (TenantId tenantId : update.getAffectedTenants()) { + mergeLimits(tenantId, newLimits); + } + } + + @Override + public void update(TenantId tenantId) { + mergeLimits(tenantId, fetchProfileAndInit(tenantId)); + } + + public void mergeLimits(TenantId tenantId, TransportRateLimit[] newRateLimits) { + TransportRateLimit[] oldRateLimits = perTenantLimits.get(tenantId); + if (oldRateLimits == null) { + perTenantLimits.put(tenantId, newRateLimits); + } else { + for (int i = 0; i < TransportRateLimitType.values().length; i++) { + TransportRateLimit newLimit = newRateLimits[i]; + TransportRateLimit oldLimit = oldRateLimits[i]; + if (newLimit != null && (oldLimit == null || !oldLimit.getConfiguration().equals(newLimit.getConfiguration()))) { + oldRateLimits[i] = newLimit; + } + } + } + } + + @Override + public void remove(TenantId tenantId) { + perTenantLimits.remove(tenantId); + } + + @Override + public void remove(DeviceId deviceId) { + perDeviceLimits.remove(deviceId); + } + + private TransportRateLimit[] fetchProfileAndInit(TenantId tenantId) { + return perTenantLimits.computeIfAbsent(tenantId, tmp -> createTransportRateLimits(tenantProfileCache.get(tenantId))); + } + + private TransportRateLimit[] createTransportRateLimits(TenantProfile tenantProfile) { + TenantProfileData profileData = tenantProfile.getProfileData(); + TransportRateLimit[] rateLimits = new TransportRateLimit[TransportRateLimitType.values().length]; + for (TransportRateLimitType type : TransportRateLimitType.values()) { + rateLimits[type.ordinal()] = rateLimitFactory.create(type, profileData.getProperties().get(type.getConfigurationKey())); + } + return rateLimits; + } +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DummyTransportRateLimit.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DummyTransportRateLimit.java new file mode 100644 index 0000000000..d6d58d55ba --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DummyTransportRateLimit.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.limits; + +public class DummyTransportRateLimit implements TransportRateLimit { + + @Override + public String getConfiguration() { + return ""; + } + + @Override + public boolean tryConsume() { + return true; + } + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java new file mode 100644 index 0000000000..08fbb7ec5d --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.limits; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.thingsboard.server.common.msg.tools.TbRateLimits; + +@RequiredArgsConstructor +public class SimpleTransportRateLimit implements TransportRateLimit { + + private final TbRateLimits rateLimit; + @Getter + private final String configuration; + + @Override + public boolean tryConsume() { + return rateLimit.tryConsume(); + } + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimit.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimit.java new file mode 100644 index 0000000000..0901e1becc --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimit.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.limits; + +public interface TransportRateLimit { + + String getConfiguration(); + + boolean tryConsume(); + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitFactory.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitFactory.java new file mode 100644 index 0000000000..54d83610c2 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitFactory.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.limits; + +public interface TransportRateLimitFactory { + + TransportRateLimit create(TransportRateLimitType type, Object config); + + TransportRateLimit createDefault(TransportRateLimitType type); + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java new file mode 100644 index 0000000000..2f2e808a36 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.limits; + +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; + +public interface TransportRateLimitService { + + TransportRateLimit getRateLimit(TenantId tenantId, TransportRateLimitType limit); + + TransportRateLimit getRateLimit(TenantId tenantId, DeviceId deviceId, TransportRateLimitType limit); + + void update(TenantProfileUpdateResult update); + + void update(TenantId tenantId); + + void remove(TenantId tenantId); + + void remove(DeviceId deviceId); + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java new file mode 100644 index 0000000000..becc5fbc86 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.limits; + +import lombok.Getter; + +public enum TransportRateLimitType { + + TENANT_MAX_MSGS("transport.tenant.max.msg"), + TENANT_MAX_DATA_POINTS("transport.tenant.max.dataPoints"), + DEVICE_MAX_MSGS("transport.device.max.msg"), + DEVICE_MAX_DATA_POINTS("transport.device.max.dataPoints"); + + @Getter + private final String configurationKey; + + TransportRateLimitType(String configurationKey) { + this.configurationKey = configurationKey; + } +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/profile/TenantProfileUpdateResult.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/profile/TenantProfileUpdateResult.java new file mode 100644 index 0000000000..53950e6de8 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/profile/TenantProfileUpdateResult.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.profile; + +import lombok.Data; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.id.TenantId; + +import java.util.Set; + +@Data +public class TenantProfileUpdateResult { + + private final TenantProfile profile; + private final Set affectedTenants; + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java similarity index 91% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportProfileCache.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java index 4d955de70c..3b44fd6a54 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportProfileCache.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java @@ -21,7 +21,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.transport.TransportProfileCache; +import org.thingsboard.server.common.transport.TransportDeviceProfileCache; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import java.util.Optional; @@ -31,13 +31,13 @@ import java.util.concurrent.ConcurrentMap; @Slf4j @Component @ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") -public class DefaultTransportProfileCache implements TransportProfileCache { +public class DefaultTransportDeviceProfileCache implements TransportDeviceProfileCache { private final ConcurrentMap deviceProfiles = new ConcurrentHashMap<>(); private final DataDecodingEncodingService dataDecodingEncodingService; - public DefaultTransportProfileCache(DataDecodingEncodingService dataDecodingEncodingService) { + public DefaultTransportDeviceProfileCache(DataDecodingEncodingService dataDecodingEncodingService) { this.dataDecodingEncodingService = dataDecodingEncodingService; } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 47abc07286..7631658602 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -29,25 +29,35 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceQueue; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.common.msg.tools.TbRateLimitsException; +import org.thingsboard.server.common.stats.MessagesStats; +import org.thingsboard.server.common.stats.StatsFactory; +import org.thingsboard.server.common.stats.StatsType; import org.thingsboard.server.common.transport.SessionMsgListener; -import org.thingsboard.server.common.transport.TransportProfileCache; +import org.thingsboard.server.common.transport.TransportDeviceProfileCache; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.common.transport.limits.TransportRateLimit; +import org.thingsboard.server.common.transport.limits.TransportRateLimitService; +import org.thingsboard.server.common.transport.limits.TransportRateLimitType; +import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; +import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; @@ -69,15 +79,13 @@ import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbTransportQueueFactory; -import org.thingsboard.server.common.stats.MessagesStats; -import org.thingsboard.server.common.stats.StatsFactory; -import org.thingsboard.server.common.stats.StatsType; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -98,12 +106,6 @@ import java.util.concurrent.atomic.AtomicInteger; @ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") public class DefaultTransportService implements TransportService { - @Value("${transport.rate_limits.enabled}") - private boolean rateLimitEnabled; - @Value("${transport.rate_limits.tenant}") - private String perTenantLimitsConf; - @Value("${transport.rate_limits.device}") - private String perDevicesLimitsConf; @Value("${transport.sessions.inactivity_timeout}") private long sessionInactivityTimeout; @Value("${transport.sessions.report_timeout}") @@ -119,7 +121,10 @@ public class DefaultTransportService implements TransportService { private final PartitionService partitionService; private final TbServiceInfoProvider serviceInfoProvider; private final StatsFactory statsFactory; - private final TransportProfileCache transportProfileCache; + private final TransportDeviceProfileCache deviceProfileCache; + private final TransportTenantProfileCache tenantProfileCache; + private final TransportRateLimitService rateLimitService; + private final DataDecodingEncodingService dataDecodingEncodingService; protected TbQueueRequestTemplate, TbProtoQueueMsg> transportApiRequestTemplate; protected TbQueueProducer> ruleEngineMsgProducer; @@ -132,14 +137,11 @@ public class DefaultTransportService implements TransportService { protected ScheduledExecutorService schedulerExecutor; protected ExecutorService transportCallbackExecutor; + private ExecutorService mainConsumerExecutor; private final ConcurrentMap sessions = new ConcurrentHashMap<>(); private final Map toServerRpcPendingMap = new ConcurrentHashMap<>(); - //TODO 3.2: @ybondarenko Implement cleanup of this maps. - private final ConcurrentMap perTenantLimits = new ConcurrentHashMap<>(); - private final ConcurrentMap perDeviceLimits = new ConcurrentHashMap<>(); - private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("transport-consumer")); private volatile boolean stopped = false; public DefaultTransportService(TbServiceInfoProvider serviceInfoProvider, @@ -147,22 +149,22 @@ public class DefaultTransportService implements TransportService { TbQueueProducerProvider producerProvider, PartitionService partitionService, StatsFactory statsFactory, - TransportProfileCache transportProfileCache) { + TransportDeviceProfileCache deviceProfileCache, + TransportTenantProfileCache tenantProfileCache, + TransportRateLimitService rateLimitService, DataDecodingEncodingService dataDecodingEncodingService) { this.serviceInfoProvider = serviceInfoProvider; this.queueProvider = queueProvider; this.producerProvider = producerProvider; this.partitionService = partitionService; this.statsFactory = statsFactory; - this.transportProfileCache = transportProfileCache; + this.deviceProfileCache = deviceProfileCache; + this.tenantProfileCache = tenantProfileCache; + this.rateLimitService = rateLimitService; + this.dataDecodingEncodingService = dataDecodingEncodingService; } @PostConstruct public void init() { - if (rateLimitEnabled) { - //Just checking the configuration parameters - new TbRateLimits(perTenantLimitsConf); - new TbRateLimits(perDevicesLimitsConf); - } this.ruleEngineProducerStats = statsFactory.createMessagesStats(StatsType.RULE_ENGINE.getName() + ".producer"); this.tbCoreProducerStats = statsFactory.createMessagesStats(StatsType.CORE.getName() + ".producer"); this.transportApiStats = statsFactory.createMessagesStats(StatsType.TRANSPORT.getName() + ".producer"); @@ -177,6 +179,7 @@ public class DefaultTransportService implements TransportService { TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_TRANSPORT, serviceInfoProvider.getServiceId()); transportNotificationsConsumer.subscribe(Collections.singleton(tpi)); transportApiRequestTemplate.init(); + mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("transport-consumer")); mainConsumerExecutor.execute(() -> { while (!stopped) { try { @@ -208,10 +211,6 @@ public class DefaultTransportService implements TransportService { @PreDestroy public void destroy() { - if (rateLimitEnabled) { - perTenantLimits.clear(); - perDeviceLimits.clear(); - } stopped = true; if (transportNotificationsConsumer != null) { @@ -232,7 +231,7 @@ public class DefaultTransportService implements TransportService { } @Override - public ScheduledExecutorService getSchedulerExecutor(){ + public ScheduledExecutorService getSchedulerExecutor() { return this.schedulerExecutor; } @@ -242,12 +241,12 @@ public class DefaultTransportService implements TransportService { } @Override - public TransportProtos.GetTenantRoutingInfoResponseMsg getRoutingInfo(TransportProtos.GetTenantRoutingInfoRequestMsg msg) { + public TransportProtos.GetEntityProfileResponseMsg getRoutingInfo(TransportProtos.GetEntityProfileRequestMsg msg) { TbProtoQueueMsg protoMsg = - new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setGetTenantRoutingInfoRequestMsg(msg).build()); + new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setEntityProfileRequestMsg(msg).build()); try { TbProtoQueueMsg response = transportApiRequestTemplate.send(protoMsg).get(); - return response.getValue().getGetTenantRoutingInfoResponseMsg(); + return response.getValue().getEntityProfileResponseMsg(); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } @@ -289,7 +288,7 @@ public class DefaultTransportService implements TransportService { result.deviceInfo(tdi); ByteString profileBody = msg.getProfileBody(); if (profileBody != null && !profileBody.isEmpty()) { - DeviceProfile profile = transportProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody); + DeviceProfile profile = deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody); if (transportType != DeviceTransportType.DEFAULT && profile != null && profile.getTransportType() != DeviceTransportType.DEFAULT && profile.getTransportType() != transportType) { log.debug("[{}] Device profile [{}] has different transport type: {}, expected: {}", tdi.getDeviceId(), tdi.getDeviceProfileId(), profile.getTransportType(), transportType); @@ -315,7 +314,7 @@ public class DefaultTransportService implements TransportService { result.deviceInfo(tdi); ByteString profileBody = msg.getProfileBody(); if (profileBody != null && !profileBody.isEmpty()) { - result.deviceProfile(transportProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody)); + result.deviceProfile(deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody)); } } return result.build(); @@ -339,8 +338,8 @@ public class DefaultTransportService implements TransportService { log.trace("Processing msg: {}", requestMsg); TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setProvisionDeviceRequestMsg(requestMsg).build()); ListenableFuture response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> - tmp.getValue().getProvisionDeviceResponseMsg() - , MoreExecutors.directExecutor()); + tmp.getValue().getProvisionDeviceResponseMsg() + , MoreExecutors.directExecutor()); AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor); } @@ -580,12 +579,11 @@ public class DefaultTransportService implements TransportService { if (log.isTraceEnabled()) { log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg); } - if (!rateLimitEnabled) { - return true; - } TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); - TbRateLimits rateLimits = perTenantLimits.computeIfAbsent(tenantId, id -> new TbRateLimits(perTenantLimitsConf)); - if (!rateLimits.tryConsume()) { + + TransportRateLimit tenantRateLimit = rateLimitService.getRateLimit(tenantId, TransportRateLimitType.TENANT_MAX_MSGS); + + if (!tenantRateLimit.tryConsume()) { if (callback != null) { callback.onError(new TbRateLimitsException(EntityType.TENANT)); } @@ -595,8 +593,8 @@ public class DefaultTransportService implements TransportService { return false; } DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); - rateLimits = perDeviceLimits.computeIfAbsent(deviceId, id -> new TbRateLimits(perDevicesLimitsConf)); - if (!rateLimits.tryConsume()) { + TransportRateLimit deviceRateLimit = rateLimitService.getRateLimit(tenantId, deviceId, TransportRateLimitType.DEVICE_MAX_MSGS); + if (!deviceRateLimit.tryConsume()) { if (callback != null) { callback.onError(new TbRateLimitsException(EntityType.DEVICE)); } @@ -637,16 +635,40 @@ public class DefaultTransportService implements TransportService { deregisterSession(md.getSessionInfo()); } } else { - if (toSessionMsg.hasDeviceProfileUpdateMsg()) { - DeviceProfile deviceProfile = transportProfileCache.put(toSessionMsg.getDeviceProfileUpdateMsg().getData()); - if (deviceProfile != null) { - onProfileUpdate(deviceProfile); + if (toSessionMsg.hasEntityUpdateMsg()) { + TransportProtos.EntityUpdateMsg msg = toSessionMsg.getEntityUpdateMsg(); + EntityType entityType = EntityType.valueOf(msg.getEntityType()); + if (EntityType.DEVICE_PROFILE.equals(entityType)) { + DeviceProfile deviceProfile = deviceProfileCache.put(msg.getData()); + if (deviceProfile != null) { + onProfileUpdate(deviceProfile); + } + } else if (EntityType.TENANT_PROFILE.equals(entityType)) { + TenantProfileUpdateResult update = tenantProfileCache.put(msg.getData()); + rateLimitService.update(update); + } else if (EntityType.TENANT.equals(entityType)) { + Optional profileOpt = dataDecodingEncodingService.decode(msg.getData().toByteArray()); + if (profileOpt.isPresent()) { + Tenant tenant = profileOpt.get(); + boolean updated = tenantProfileCache.put(tenant.getId(), tenant.getTenantProfileId()); + if (updated) { + rateLimitService.update(tenant.getId()); + } + } + } + } else if (toSessionMsg.hasEntityDeleteMsg()) { + TransportProtos.EntityDeleteMsg msg = toSessionMsg.getEntityDeleteMsg(); + EntityType entityType = EntityType.valueOf(msg.getEntityType()); + UUID entityUuid = new UUID(msg.getEntityIdMSB(), msg.getEntityIdLSB()); + if (EntityType.DEVICE_PROFILE.equals(entityType)) { + deviceProfileCache.evict(new DeviceProfileId(new UUID(msg.getEntityIdMSB(), msg.getEntityIdLSB()))); + } else if (EntityType.TENANT_PROFILE.equals(entityType)) { + tenantProfileCache.remove(new TenantProfileId(entityUuid)); + } else if (EntityType.TENANT.equals(entityType)) { + rateLimitService.remove(new TenantId(entityUuid)); + } else if (EntityType.DEVICE.equals(entityType)) { + rateLimitService.remove(new DeviceId(entityUuid)); } - } else if (toSessionMsg.hasDeviceProfileDeleteMsg()) { - transportProfileCache.evict(new DeviceProfileId(new UUID( - toSessionMsg.getDeviceProfileDeleteMsg().getProfileIdMSB(), - toSessionMsg.getDeviceProfileDeleteMsg().getProfileIdLSB() - ))); } else { //TODO: should we notify the device actor about missed session? log.debug("[{}] Missing session.", sessionId); @@ -654,38 +676,6 @@ public class DefaultTransportService implements TransportService { } } - @Override - public void getDeviceProfile(DeviceProfileId deviceProfileId, TransportServiceCallback callback) { - DeviceProfile deviceProfile = transportProfileCache.get(deviceProfileId); - if (deviceProfile != null) { - callback.onSuccess(deviceProfile); - } else { - log.trace("Processing device profile request: [{}]", deviceProfileId); - TransportProtos.GetDeviceProfileRequestMsg msg = TransportProtos.GetDeviceProfileRequestMsg.newBuilder() - .setProfileIdMSB(deviceProfileId.getId().getMostSignificantBits()) - .setProfileIdLSB(deviceProfileId.getId().getLeastSignificantBits()) - .build(); - TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), - TransportApiRequestMsg.newBuilder().setGetDeviceProfileRequestMsg(msg).build()); - AsyncCallbackTemplate.withCallback(transportApiRequestTemplate.send(protoMsg), - response -> { - ByteString devProfileBody = response.getValue().getGetDeviceProfileResponseMsg().getData(); - if (devProfileBody != null && !devProfileBody.isEmpty()) { - DeviceProfile profile = transportProfileCache.put(devProfileBody); - if (profile != null) { - callback.onSuccess(profile); - } else { - log.warn("Failed to decode device profile: {}", devProfileBody); - callback.onError(new IllegalArgumentException("Failed to decode device profile!")); - } - } else { - log.warn("Failed to find device profile: [{}]", deviceProfileId); - callback.onError(new IllegalArgumentException("Failed to find device profile!")); - } - }, callback::onError, transportCallbackExecutor); - } - } - @Override public void onProfileUpdate(DeviceProfile deviceProfile) { long deviceProfileIdMSB = deviceProfile.getId().getId().getMostSignificantBits(); @@ -750,7 +740,7 @@ public class DefaultTransportService implements TransportService { private RuleChainId resolveRuleChainId(TransportProtos.SessionInfoProto sessionInfo) { DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); - DeviceProfile deviceProfile = transportProfileCache.get(deviceProfileId); + DeviceProfile deviceProfile = deviceProfileCache.get(deviceProfileId); RuleChainId ruleChainId; if (deviceProfile == null) { log.warn("[{}] Device profile is null!", deviceProfileId); @@ -779,7 +769,7 @@ public class DefaultTransportService implements TransportService { } } - private class StatsCallback implements TbQueueCallback { + private static class StatsCallback implements TbQueueCallback { private final TbQueueCallback callback; private final MessagesStats stats; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java new file mode 100644 index 0000000000..717627a60e --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java @@ -0,0 +1,154 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.service; + +import com.google.protobuf.ByteString; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportTenantProfileCache; +import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; +import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.discovery.TenantRoutingInfo; +import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; + +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +@Component +@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") +@Slf4j +public class DefaultTransportTenantProfileCache implements TransportTenantProfileCache { + + private final Lock tenantProfileFetchLock = new ReentrantLock(); + private final ConcurrentMap profiles = new ConcurrentHashMap<>(); + private final ConcurrentMap tenantIds = new ConcurrentHashMap<>(); + private final ConcurrentMap> tenantProfileIds = new ConcurrentHashMap<>(); + private final DataDecodingEncodingService dataDecodingEncodingService; + + private TransportService transportService; + + @Lazy + @Autowired + public void setTransportService(TransportService transportService) { + this.transportService = transportService; + } + + public DefaultTransportTenantProfileCache(DataDecodingEncodingService dataDecodingEncodingService) { + this.dataDecodingEncodingService = dataDecodingEncodingService; + } + + @Override + public TenantProfile get(TenantId tenantId) { + return getTenantProfile(tenantId); + } + + @Override + public TenantProfileUpdateResult put(ByteString profileBody) { + Optional profileOpt = dataDecodingEncodingService.decode(profileBody.toByteArray()); + if (profileOpt.isPresent()) { + TenantProfile newProfile = profileOpt.get(); + log.trace("[{}] put: {}", newProfile.getId(), newProfile); + return new TenantProfileUpdateResult(newProfile, tenantProfileIds.get(newProfile.getId())); + } else { + log.warn("Failed to decode profile: {}", profileBody.toString()); + return new TenantProfileUpdateResult(null, Collections.emptySet()); + } + } + + @Override + public boolean put(TenantId tenantId, TenantProfileId profileId) { + log.trace("[{}] put: {}", tenantId, profileId); + TenantProfileId oldProfileId = tenantIds.get(tenantId); + if (oldProfileId != null && !oldProfileId.equals(profileId)) { + tenantProfileIds.computeIfAbsent(oldProfileId, id -> ConcurrentHashMap.newKeySet()).remove(tenantId); + tenantIds.put(tenantId, profileId); + tenantProfileIds.computeIfAbsent(profileId, id -> ConcurrentHashMap.newKeySet()).add(tenantId); + return true; + } else { + return false; + } + } + + @Override + public Set remove(TenantProfileId profileId) { + Set tenants = tenantProfileIds.remove(profileId); + if (tenants != null) { + tenants.forEach(tenantIds::remove); + } + profiles.remove(profileId); + return tenants; + } + + private TenantProfile getTenantProfile(TenantId tenantId) { + TenantProfile profile = null; + TenantProfileId tenantProfileId = tenantIds.get(tenantId); + if (tenantProfileId != null) { + profile = profiles.get(tenantProfileId); + } + if (profile == null) { + tenantProfileFetchLock.lock(); + try { + tenantProfileId = tenantIds.get(tenantId); + if (tenantProfileId != null) { + profile = profiles.get(tenantProfileId); + } + if (profile == null) { + TransportProtos.GetEntityProfileRequestMsg msg = TransportProtos.GetEntityProfileRequestMsg.newBuilder() + .setEntityType(EntityType.TENANT.name()) + .setEntityIdMSB(tenantId.getId().getMostSignificantBits()) + .setEntityIdLSB(tenantId.getId().getLeastSignificantBits()) + .build(); + TransportProtos.GetEntityProfileResponseMsg routingInfo = transportService.getRoutingInfo(msg); + Optional profileOpt = dataDecodingEncodingService.decode(routingInfo.getData().toByteArray()); + if (profileOpt.isPresent()) { + profile = profileOpt.get(); + TenantProfile existingProfile = profiles.get(profile.getId()); + if (existingProfile != null) { + profile = existingProfile; + } else { + profiles.put(profile.getId(), profile); + } + tenantProfileIds.computeIfAbsent(profile.getId(), id -> ConcurrentHashMap.newKeySet()).add(tenantId); + tenantIds.put(tenantId, profile.getId()); + } else { + log.warn("[{}] Can't decode tenant profile: {}", tenantId, routingInfo.getData()); + throw new RuntimeException("Can't decode tenant profile!"); + } + } + } finally { + tenantProfileFetchLock.unlock(); + } + } + return profile; + } + + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportTenantRoutingInfoService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportTenantRoutingInfoService.java index f2534a64c9..55dee282ec 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportTenantRoutingInfoService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportTenantRoutingInfoService.java @@ -16,14 +16,11 @@ package org.thingsboard.server.common.transport.service; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.transport.TransportService; -import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg; +import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.queue.discovery.TenantRoutingInfo; import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; @@ -32,21 +29,16 @@ import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; @ConditionalOnExpression("'${service.type:null}'=='tb-transport'") public class TransportTenantRoutingInfoService implements TenantRoutingInfoService { - private TransportService transportService; + private TransportTenantProfileCache tenantProfileCache; - @Lazy - @Autowired - public void setTransportService(TransportService transportService) { - this.transportService = transportService; + public TransportTenantRoutingInfoService(TransportTenantProfileCache tenantProfileCache) { + this.tenantProfileCache = tenantProfileCache; } @Override public TenantRoutingInfo getRoutingInfo(TenantId tenantId) { - GetTenantRoutingInfoRequestMsg msg = GetTenantRoutingInfoRequestMsg.newBuilder() - .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) - .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) - .build(); - GetTenantRoutingInfoResponseMsg routingInfo = transportService.getRoutingInfo(msg); - return new TenantRoutingInfo(tenantId, routingInfo.getIsolatedTbCore(), routingInfo.getIsolatedTbRuleEngine()); + TenantProfile profile = tenantProfileCache.get(tenantId); + return new TenantRoutingInfo(tenantId, profile.isIsolatedTbCore(), profile.isIsolatedTbRuleEngine()); } + } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/DataDecodingEncodingService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/DataDecodingEncodingService.java index 1b10cb5dc3..bf70fa5ef1 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/DataDecodingEncodingService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/DataDecodingEncodingService.java @@ -15,8 +15,6 @@ */ package org.thingsboard.server.common.transport.util; -import org.thingsboard.server.common.msg.TbActorMsg; - import java.util.Optional; public interface DataDecodingEncodingService { diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index 3f57b32fab..229196d325 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -49,10 +49,6 @@ transport: sessions: inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" - rate_limits: - enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}" - tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}" - device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}" json: # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index 77d5f30fa7..6aaa42ca18 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -42,10 +42,6 @@ transport: sessions: inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" - rate_limits: - enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}" - tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}" - device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}" json: # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index f01b15c77a..f9341dfb86 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -71,10 +71,6 @@ transport: sessions: inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" - rate_limits: - enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}" - tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}" - device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}" json: # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" From 51ac96d0102bb299cf86bb1ccb272aadeb03dfa5 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 15 Oct 2020 19:32:02 +0300 Subject: [PATCH 216/463] Alarm Result State --- .../rule/engine/profile/AlarmEvalResult.java | 22 +++++++++++ .../rule/engine/profile/AlarmRuleState.java | 30 ++++++++------- .../rule/engine/profile/AlarmState.java | 38 +++++++++++++------ .../engine/profile/TbDeviceProfileNode.java | 4 +- 4 files changed, 67 insertions(+), 27 deletions(-) create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmEvalResult.java diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmEvalResult.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmEvalResult.java new file mode 100644 index 0000000000..7d510fe5fe --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmEvalResult.java @@ -0,0 +1,22 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.profile; + +public enum AlarmEvalResult { + + FALSE, NOT_YET_TRUE, TRUE; + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java index d9d08efbdc..8be2b81a59 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java @@ -123,17 +123,17 @@ class AlarmRuleState { } } - public boolean eval(DataSnapshot data) { + public AlarmEvalResult eval(DataSnapshot data) { boolean active = isActive(data.getTs()); switch (spec.getType()) { case SIMPLE: - return active && eval(alarmRule.getCondition(), data); + return (active && eval(alarmRule.getCondition(), data)) ? AlarmEvalResult.TRUE : AlarmEvalResult.FALSE; case DURATION: return evalDuration(data, active); case REPEATING: return evalRepeating(data, active); default: - return false; + return AlarmEvalResult.FALSE; } } @@ -203,17 +203,17 @@ class AlarmRuleState { } } - private boolean evalRepeating(DataSnapshot data, boolean active) { + private AlarmEvalResult evalRepeating(DataSnapshot data, boolean active) { if (active && eval(alarmRule.getCondition(), data)) { state.setEventCount(state.getEventCount() + 1); updateFlag = true; - return state.getEventCount() >= requiredRepeats; + return state.getEventCount() >= requiredRepeats ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE; } else { - return false; + return AlarmEvalResult.FALSE; } } - private boolean evalDuration(DataSnapshot data, boolean active) { + private AlarmEvalResult evalDuration(DataSnapshot data, boolean active) { if (active && eval(alarmRule.getCondition(), data)) { if (state.getLastEventTs() > 0) { if (data.getTs() > state.getLastEventTs()) { @@ -226,24 +226,28 @@ class AlarmRuleState { state.setDuration(0L); updateFlag = true; } - return state.getDuration() > requiredDurationInMs; + return state.getDuration() > requiredDurationInMs ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE; } else { - return false; + return AlarmEvalResult.FALSE; } } - public boolean eval(long ts) { + public AlarmEvalResult eval(long ts) { switch (spec.getType()) { case SIMPLE: case REPEATING: - return false; + return AlarmEvalResult.NOT_YET_TRUE; case DURATION: if (requiredDurationInMs > 0 && state.getLastEventTs() > 0 && ts > state.getLastEventTs()) { long duration = state.getDuration() + (ts - state.getLastEventTs()); - return duration > requiredDurationInMs && isActive(ts); + if (isActive(ts)) { + return duration > requiredDurationInMs ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE; + } else { + return AlarmEvalResult.FALSE; + } } default: - return false; + return AlarmEvalResult.FALSE; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 5fb2c2957c..df46a4bf88 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -72,7 +72,7 @@ class AlarmState { return createOrClearAlarms(ctx, ts, null, AlarmRuleState::eval); } - public boolean createOrClearAlarms(TbContext ctx, T data, SnapshotUpdate update, BiFunction evalFunction) { + public boolean createOrClearAlarms(TbContext ctx, T data, SnapshotUpdate update, BiFunction evalFunction) { boolean stateUpdate = false; AlarmSeverity resultSeverity = null; log.debug("[{}] processing update: {}", alarmDefinition.getId(), data); @@ -81,22 +81,28 @@ class AlarmState { log.debug("[{}][{}] Update is not valid for current rule state", alarmDefinition.getId(), state.getSeverity()); continue; } - boolean evalResult = evalFunction.apply(state, data); + AlarmEvalResult evalResult = evalFunction.apply(state, data); stateUpdate |= state.checkUpdate(); - if (evalResult) { + if (AlarmEvalResult.TRUE.equals(evalResult)) { resultSeverity = state.getSeverity(); break; + } else if (AlarmEvalResult.FALSE.equals(evalResult)) { + state.clear(); + stateUpdate |= state.checkUpdate(); } } if (resultSeverity != null) { - pushMsg(ctx, calculateAlarmResult(ctx, resultSeverity)); + TbAlarmResult result = calculateAlarmResult(ctx, resultSeverity); + if (result != null) { + pushMsg(ctx, result); + } } else if (currentAlarm != null && clearState != null) { if (!validateUpdate(update, clearState)) { log.debug("[{}] Update is not valid for current clear state", alarmDefinition.getId()); return stateUpdate; } - Boolean evalResult = evalFunction.apply(clearState, data); - if (evalResult) { + AlarmEvalResult evalResult = evalFunction.apply(clearState, data); + if (AlarmEvalResult.TRUE.equals(evalResult)) { stateUpdate |= clearState.checkUpdate(); for (AlarmRuleState state : createRulesSortedBySeverityDesc) { state.clear(); @@ -105,6 +111,9 @@ class AlarmState { ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), JacksonUtil.OBJECT_MAPPER.createObjectNode(), System.currentTimeMillis()); pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm)); currentAlarm = null; + } else if (AlarmEvalResult.FALSE.equals(evalResult)) { + clearState.clear(); + stateUpdate |= clearState.checkUpdate(); } } return stateUpdate; @@ -183,13 +192,18 @@ class AlarmState { // Maybe we should fetch alarm every time? currentAlarm.setEndTs(System.currentTimeMillis()); AlarmSeverity oldSeverity = currentAlarm.getSeverity(); - if (!oldSeverity.equals(severity)) { - currentAlarm.setSeverity(severity); - currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm); - return new TbAlarmResult(false, false, true, false, currentAlarm); + // Skip update if severity is decreased. + if (severity.ordinal() <= oldSeverity.ordinal()) { + if (!oldSeverity.equals(severity)) { + currentAlarm.setSeverity(severity); + currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm); + return new TbAlarmResult(false, false, true, false, currentAlarm); + } else { + currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm); + return new TbAlarmResult(false, true, false, false, currentAlarm); + } } else { - currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm); - return new TbAlarmResult(false, true, false, false, currentAlarm); + return null; } } else { currentAlarm = new Alarm(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java index 3fff9dbc2e..b4bbf08323 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java @@ -167,14 +167,14 @@ public class TbDeviceProfileNode implements TbNode { protected void updateProfile(TbContext ctx, DeviceProfileId deviceProfileId) throws ExecutionException, InterruptedException { DeviceProfile deviceProfile = cache.get(ctx.getTenantId(), deviceProfileId); if (deviceProfile != null) { - log.info("[{}] Received device profile update notification: {}", ctx.getSelfId(), deviceProfile); + log.debug("[{}] Received device profile update notification: {}", ctx.getSelfId(), deviceProfile); for (DeviceState state : deviceStates.values()) { if (deviceProfile.getId().equals(state.getProfileId())) { state.updateProfile(ctx, deviceProfile); } } } else { - log.info("[{}] Received stale profile update notification: [{}]", ctx.getSelfId(), deviceProfileId); + log.debug("[{}] Received stale profile update notification: [{}]", ctx.getSelfId(), deviceProfileId); } } From a17383c60f87378c19f58197cefd6a51dbdff2d7 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 16 Oct 2020 12:03:27 +0300 Subject: [PATCH 217/463] UI: Added a rule chain field in the device profile to the device wizard --- .../components/wizard/device-wizard-dialog.component.html | 6 ++++++ .../components/wizard/device-wizard-dialog.component.ts | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index 7805171fe3..0d224122d6 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -91,6 +91,12 @@ +
+ + +
{{ 'device.is-gateway' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts index 5c59eb448b..c4f100b30f 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts @@ -42,6 +42,7 @@ import { ErrorStateMatcher } from '@angular/material/core'; import { StepperSelectionEvent } from '@angular/cdk/stepper'; import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; +import { RuleChainId } from '@shared/models/id/rule-chain-id'; @Component({ selector: 'tb-device-wizard', @@ -103,6 +104,7 @@ export class DeviceWizardDialogComponent extends addProfileType: [0], deviceProfileId: [null, Validators.required], newDeviceProfileTitle: [{value: null, disabled: true}], + defaultRuleChainId: [{value: null, disabled: true}], description: [''] } ); @@ -114,6 +116,7 @@ export class DeviceWizardDialogComponent extends this.deviceWizardFormGroup.get('deviceProfileId').enable(); this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators(null); this.deviceWizardFormGroup.get('newDeviceProfileTitle').disable(); + this.deviceWizardFormGroup.get('defaultRuleChainId').disable(); this.deviceWizardFormGroup.updateValueAndValidity(); this.createProfile = false; this.createTransportConfiguration = false; @@ -122,6 +125,7 @@ export class DeviceWizardDialogComponent extends this.deviceWizardFormGroup.get('deviceProfileId').disable(); this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]); this.deviceWizardFormGroup.get('newDeviceProfileTitle').enable(); + this.deviceWizardFormGroup.get('defaultRuleChainId').enable(); this.deviceWizardFormGroup.updateValueAndValidity(); this.createProfile = true; this.createTransportConfiguration = this.deviceWizardFormGroup.get('transportType').value && @@ -274,6 +278,9 @@ export class DeviceWizardDialogComponent extends provisionConfiguration: deviceProvisionConfiguration } }; + if (this.deviceWizardFormGroup.get('defaultRuleChainId').value) { + deviceProfile.defaultRuleChainId = new RuleChainId(this.deviceWizardFormGroup.get('defaultRuleChainId').value); + } return this.deviceProfileService.saveDeviceProfile(deviceProfile).pipe( map(profile => profile.id), tap((profileId) => { From f8fdbcaf5b4c2695fbfad737a73309d520ef4f87 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 16 Oct 2020 12:31:34 +0300 Subject: [PATCH 218/463] Fix DefaultTransportRateLimitService - add conditional expression --- .../transport/limits/DefaultTransportRateLimitService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java index 9446022ec5..2bf2f6a697 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.transport.limits; import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfileData; @@ -28,6 +29,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @Service +@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") @Slf4j public class DefaultTransportRateLimitService implements TransportRateLimitService { From f181beec61b1517d0099c975f40048a4b094731b Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 16 Oct 2020 16:04:06 +0300 Subject: [PATCH 219/463] OAuth2 form improvements --- .../app/modules/home/pages/admin/oauth2-settings.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index 37ae0433d3..6ec372d3b6 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -347,7 +347,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha clientRegistration.get('authorizationUri').disable(); clientRegistration.get('jwkSetUri').disable(); clientRegistration.get('userInfoUri').disable(); - clientRegistration.patchValue(template); + clientRegistration.patchValue(template, {emitEvent: false}); } } From e9bf5bae29318d6b0a63dd104d365a522b24508d Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 19 Oct 2020 11:50:06 +0300 Subject: [PATCH 220/463] Rate limit improvements --- .../server/common/msg/tools/TbRateLimits.java | 4 + .../queue/util/TbTransportComponent.java | 26 +++++++ .../common/transport/TransportService.java | 3 + .../DefaultTransportRateLimitService.java | 74 +++++++++++-------- .../limits/DummyTransportRateLimit.java | 5 ++ .../limits/SimpleTransportRateLimit.java | 4 + .../transport/limits/TransportRateLimit.java | 2 + .../limits/TransportRateLimitService.java | 4 +- .../limits/TransportRateLimitType.java | 24 ++++-- .../DefaultTransportDeviceProfileCache.java | 3 +- .../service/DefaultTransportService.java | 44 +++++------ .../DefaultTransportTenantProfileCache.java | 3 +- 12 files changed, 136 insertions(+), 60 deletions(-) create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/util/TbTransportComponent.java diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/tools/TbRateLimits.java b/common/message/src/main/java/org/thingsboard/server/common/msg/tools/TbRateLimits.java index 3c79895c8b..3550bc44a9 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/tools/TbRateLimits.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/tools/TbRateLimits.java @@ -50,4 +50,8 @@ public class TbRateLimits { return bucket.tryConsume(1); } + public boolean tryConsume(long number) { + return bucket.tryConsume(number); + } + } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/util/TbTransportComponent.java b/common/queue/src/main/java/org/thingsboard/server/queue/util/TbTransportComponent.java new file mode 100644 index 0000000000..dde1c6a620 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/util/TbTransportComponent.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.util; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") +public @interface TbTransportComponent { +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 9a2fd75d47..8f57db3897 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.common.transport.limits.TransportRateLimitType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; @@ -69,6 +70,8 @@ public interface TransportService { boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback); + boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints, TransportRateLimitType... limits); + void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback callback); void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback callback); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java index 2bf2f6a697..569f5446db 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java @@ -16,7 +16,6 @@ package org.thingsboard.server.common.transport.limits; import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfileData; @@ -24,12 +23,13 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; +import org.thingsboard.server.queue.util.TbTransportComponent; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @Service -@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") +@TbTransportComponent @Slf4j public class DefaultTransportRateLimitService implements TransportRateLimitService { @@ -45,23 +45,21 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi } @Override - public TransportRateLimit getRateLimit(TenantId tenantId, TransportRateLimitType limitType) { - TransportRateLimit[] limits = perTenantLimits.get(tenantId); - if (limits == null) { - limits = fetchProfileAndInit(tenantId); - perTenantLimits.put(tenantId, limits); - } - return limits[limitType.ordinal()]; - } - - @Override - public TransportRateLimit getRateLimit(TenantId tenantId, DeviceId deviceId, TransportRateLimitType limitType) { - TransportRateLimit[] limits = perDeviceLimits.get(deviceId); - if (limits == null) { - limits = fetchProfileAndInit(tenantId); - perDeviceLimits.put(deviceId, limits); + public TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits) { + TransportRateLimit[] tenantLimits = getTenantRateLimits(tenantId); + TransportRateLimit[] deviceLimits = getDeviceRateLimits(tenantId, deviceId); + for (TransportRateLimitType limitType : limits) { + TransportRateLimit rateLimit; + if (limitType.isTenantLevel()) { + rateLimit = tenantLimits[limitType.ordinal()]; + } else { + rateLimit = deviceLimits[limitType.ordinal()]; + } + if (!rateLimit.tryConsume(limitType.isMessageLevel() ? 1L : dataPoints)) { + return limitType; + } } - return limits[limitType.ordinal()]; + return null; } @Override @@ -77,7 +75,17 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi mergeLimits(tenantId, fetchProfileAndInit(tenantId)); } - public void mergeLimits(TenantId tenantId, TransportRateLimit[] newRateLimits) { + @Override + public void remove(TenantId tenantId) { + perTenantLimits.remove(tenantId); + } + + @Override + public void remove(DeviceId deviceId) { + perDeviceLimits.remove(deviceId); + } + + private void mergeLimits(TenantId tenantId, TransportRateLimit[] newRateLimits) { TransportRateLimit[] oldRateLimits = perTenantLimits.get(tenantId); if (oldRateLimits == null) { perTenantLimits.put(tenantId, newRateLimits); @@ -92,16 +100,6 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi } } - @Override - public void remove(TenantId tenantId) { - perTenantLimits.remove(tenantId); - } - - @Override - public void remove(DeviceId deviceId) { - perDeviceLimits.remove(deviceId); - } - private TransportRateLimit[] fetchProfileAndInit(TenantId tenantId) { return perTenantLimits.computeIfAbsent(tenantId, tmp -> createTransportRateLimits(tenantProfileCache.get(tenantId))); } @@ -114,4 +112,22 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi } return rateLimits; } + + private TransportRateLimit[] getTenantRateLimits(TenantId tenantId) { + TransportRateLimit[] limits = perTenantLimits.get(tenantId); + if (limits == null) { + limits = fetchProfileAndInit(tenantId); + perTenantLimits.put(tenantId, limits); + } + return limits; + } + + private TransportRateLimit[] getDeviceRateLimits(TenantId tenantId, DeviceId deviceId) { + TransportRateLimit[] limits = perDeviceLimits.get(deviceId); + if (limits == null) { + limits = fetchProfileAndInit(tenantId); + perDeviceLimits.put(deviceId, limits); + } + return limits; + } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DummyTransportRateLimit.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DummyTransportRateLimit.java index d6d58d55ba..a93487632f 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DummyTransportRateLimit.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DummyTransportRateLimit.java @@ -22,6 +22,11 @@ public class DummyTransportRateLimit implements TransportRateLimit { return ""; } + @Override + public boolean tryConsume(long number) { + return true; + } + @Override public boolean tryConsume() { return true; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java index 08fbb7ec5d..3253272ded 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java @@ -31,4 +31,8 @@ public class SimpleTransportRateLimit implements TransportRateLimit { return rateLimit.tryConsume(); } + @Override + public boolean tryConsume(long number) { + return number <= 0 || rateLimit.tryConsume(number); + } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimit.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimit.java index 0901e1becc..a2eea81d3a 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimit.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimit.java @@ -21,4 +21,6 @@ public interface TransportRateLimit { boolean tryConsume(); + boolean tryConsume(long number); + } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java index 2f2e808a36..a97fbfc61d 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java @@ -21,9 +21,7 @@ import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult public interface TransportRateLimitService { - TransportRateLimit getRateLimit(TenantId tenantId, TransportRateLimitType limit); - - TransportRateLimit getRateLimit(TenantId tenantId, DeviceId deviceId, TransportRateLimitType limit); + TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits); void update(TenantProfileUpdateResult update); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java index becc5fbc86..a3e6da6683 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java @@ -19,15 +19,29 @@ import lombok.Getter; public enum TransportRateLimitType { - TENANT_MAX_MSGS("transport.tenant.max.msg"), - TENANT_MAX_DATA_POINTS("transport.tenant.max.dataPoints"), - DEVICE_MAX_MSGS("transport.device.max.msg"), - DEVICE_MAX_DATA_POINTS("transport.device.max.dataPoints"); + TENANT_MAX_MSGS("transport.tenant.msg", true, true), + TENANT_TELEMETRY_MSGS("transport.tenant.telemetry", true, true), + TENANT_MAX_DATA_POINTS("transport.tenant.dataPoints", true, false), + DEVICE_MAX_MSGS("transport.device.msg", false, true), + DEVICE_TELEMETRY_MSGS("transport.device.telemetry", false, true), + DEVICE_MAX_DATA_POINTS("transport.device.dataPoints", false, false); @Getter private final String configurationKey; + @Getter + private final boolean tenantLevel; + @Getter + private final boolean deviceLevel; + @Getter + private final boolean messageLevel; + @Getter + private final boolean dataPointLevel; - TransportRateLimitType(String configurationKey) { + TransportRateLimitType(String configurationKey, boolean tenantLevel, boolean messageLevel) { this.configurationKey = configurationKey; + this.tenantLevel = tenantLevel; + this.deviceLevel = !tenantLevel; + this.messageLevel = messageLevel; + this.dataPointLevel = !messageLevel; } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java index 3b44fd6a54..b12aab1a8c 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.transport.TransportDeviceProfileCache; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; +import org.thingsboard.server.queue.util.TbTransportComponent; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; @@ -30,7 +31,7 @@ import java.util.concurrent.ConcurrentMap; @Slf4j @Component -@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") +@TbTransportComponent public class DefaultTransportDeviceProfileCache implements TransportDeviceProfileCache { private final ConcurrentMap deviceProfiles = new ConcurrentHashMap<>(); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 7631658602..3f37685383 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -79,6 +79,7 @@ import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbTransportQueueFactory; +import org.thingsboard.server.queue.util.TbTransportComponent; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -103,7 +104,7 @@ import java.util.concurrent.atomic.AtomicInteger; */ @Slf4j @Service -@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") +@TbTransportComponent public class DefaultTransportService implements TransportService { @Value("${transport.sessions.inactivity_timeout}") @@ -363,7 +364,11 @@ public class DefaultTransportService implements TransportService { @Override public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback callback) { - if (checkLimits(sessionInfo, msg, callback)) { + int dataPoints = 0; + for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { + dataPoints += tsKv.getKvCount(); + } + if (checkLimits(sessionInfo, msg, callback, dataPoints, TELEMETRY)) { reportActivityInternal(sessionInfo); TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); @@ -384,7 +389,7 @@ public class DefaultTransportService implements TransportService { @Override public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback callback) { - if (checkLimits(sessionInfo, msg, callback)) { + if (checkLimits(sessionInfo, msg, callback, msg.getKvCount(), TELEMETRY)) { reportActivityInternal(sessionInfo); TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); @@ -574,37 +579,34 @@ public class DefaultTransportService implements TransportService { sessions.remove(toSessionId(sessionInfo)); } + private TransportRateLimitType[] DEFAULT = new TransportRateLimitType[]{TransportRateLimitType.TENANT_MAX_MSGS, TransportRateLimitType.DEVICE_MAX_MSGS}; + private TransportRateLimitType[] TELEMETRY = TransportRateLimitType.values(); + @Override public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback) { + return checkLimits(sessionInfo, msg, callback, 0, DEFAULT); + } + + @Override + public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints, TransportRateLimitType... limits) { if (log.isTraceEnabled()) { log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg); } TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); - - TransportRateLimit tenantRateLimit = rateLimitService.getRateLimit(tenantId, TransportRateLimitType.TENANT_MAX_MSGS); - - if (!tenantRateLimit.tryConsume()) { - if (callback != null) { - callback.onError(new TbRateLimitsException(EntityType.TENANT)); - } - if (log.isTraceEnabled()) { - log.trace("[{}][{}] Tenant level rate limit detected: {}", toSessionId(sessionInfo), tenantId, msg); - } - return false; - } DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); - TransportRateLimit deviceRateLimit = rateLimitService.getRateLimit(tenantId, deviceId, TransportRateLimitType.DEVICE_MAX_MSGS); - if (!deviceRateLimit.tryConsume()) { + + TransportRateLimitType limit = rateLimitService.checkLimits(tenantId, deviceId, 0, limits); + if (limit == null) { + return true; + } else { if (callback != null) { - callback.onError(new TbRateLimitsException(EntityType.DEVICE)); + callback.onError(new TbRateLimitsException(limit.isTenantLevel() ? EntityType.TENANT : EntityType.DEVICE)); } if (log.isTraceEnabled()) { - log.trace("[{}][{}] Device level rate limit detected: {}", toSessionId(sessionInfo), deviceId, msg); + log.trace("[{}][{}] {} rateLimit detected: {}", toSessionId(sessionInfo), tenantId, limit, msg); } return false; } - - return true; } protected void processToTransportMsg(TransportProtos.ToTransportMsg toSessionMsg) { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java index 717627a60e..784f7b3ae8 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.TenantRoutingInfo; import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; +import org.thingsboard.server.queue.util.TbTransportComponent; import java.util.Collections; import java.util.Optional; @@ -43,7 +44,7 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @Component -@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") +@TbTransportComponent @Slf4j public class DefaultTransportTenantProfileCache implements TransportTenantProfileCache { From c5cc55156feda1ced2f1befb2ba93b6131ad8811 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 19 Oct 2020 12:16:11 +0300 Subject: [PATCH 221/463] UI: Improvement used templated in OAuth2 --- .../pages/admin/oauth2-settings.component.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index 6ec372d3b6..ac26797899 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -142,6 +142,7 @@ 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 : ''], tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : TenantNameStrategy.DOMAIN], @@ -151,11 +152,6 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha alwaysFullScreen: [isDefinedAndNotNull(mapperConfigBasic?.alwaysFullScreen) ? mapperConfigBasic.alwaysFullScreen : false] }); - if (MapperConfigType.GITHUB !== type) { - basicGroup.addControl('emailAttributeKey', - this.fb.control( mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required)); - } - this.subscriptions.push(basicGroup.get('tenantNameStrategy').valueChanges.subscribe((domain) => { if (domain === 'CUSTOM') { basicGroup.get('tenantNamePattern').enable(); @@ -347,7 +343,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha clientRegistration.get('authorizationUri').disable(); clientRegistration.get('jwkSetUri').disable(); clientRegistration.get('userInfoUri').disable(); - clientRegistration.patchValue(template, {emitEvent: false}); + clientRegistration.patchValue(template); } } @@ -358,11 +354,15 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom)); } else { mapperConfig.removeControl('custom'); - if (mapperConfig.get('basic')) { - mapperConfig.setControl('basic', this.formBasicGroup(type, predefinedValue?.basic)); - } else { + if (!mapperConfig.get('basic')) { mapperConfig.addControl('basic', this.formBasicGroup(type, predefinedValue?.basic)); } + if (type === MapperConfigType.GITHUB) { + mapperConfig.get('basic.emailAttributeKey').disable(); + mapperConfig.get('basic.emailAttributeKey').patchValue(null, {emitEvent: false}); + } else { + mapperConfig.get('basic.emailAttributeKey').enable(); + } } } From 2e694e1d649d46c514d0bba86e3bc27054889d74 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 19 Oct 2020 15:39:15 +0300 Subject: [PATCH 222/463] Dao layer for UsageRecords --- .../dao/usagerecord/UsageRecordService.java | 28 +++++ .../server/common/data/EntityType.java | 2 +- .../server/common/data/UsageRecord.java | 65 ++++++++++ .../server/common/data/UsageRecordKey.java | 27 +++++ .../server/common/data/id/UsageRecordId.java | 42 +++++++ .../server/dao/model/ModelConstants.java | 8 ++ .../dao/model/sql/UsageRecordEntity.java | 93 +++++++++++++++ .../sql/usagerecord/JpaUsageRecordDao.java | 60 ++++++++++ .../usagerecord/UsageRecordRepository.java | 43 +++++++ .../server/dao/tenant/TenantServiceImpl.java | 6 + .../dao/usagerecord/UsageRecordDao.java | 51 ++++++++ .../usagerecord/UsageRecordServiceImpl.java | 112 ++++++++++++++++++ .../resources/sql/schema-entities-hsql.sql | 9 ++ .../main/resources/sql/schema-entities.sql | 9 ++ .../dao/service/AbstractServiceTest.java | 4 + .../service/BaseUsageRecordServiceTest.java | 56 +++++++++ .../sql/UsageRecordServiceSqlTest.java | 24 ++++ .../resources/sql/hsql/drop-all-tables.sql | 1 + .../resources/sql/psql/drop-all-tables.sql | 1 + .../sql/timescale/drop-all-tables.sql | 1 + .../org/thingsboard/mqtt/MqttClientImpl.java | 1 + 21 files changed, 642 insertions(+), 1 deletion(-) create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordService.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/UsageRecord.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/UsageRecordKey.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/UsageRecordId.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/UsageRecordEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaUsageRecordDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/UsageRecordRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordServiceImpl.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/BaseUsageRecordServiceTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/sql/UsageRecordServiceSqlTest.java diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordService.java new file mode 100644 index 0000000000..55adc257ad --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordService.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.usagerecord; + +import org.thingsboard.server.common.data.UsageRecord; +import org.thingsboard.server.common.data.id.TenantId; + +public interface UsageRecordService { + + UsageRecord findTenantUsageRecord(TenantId tenantId); + + void deleteUsageRecordsByTenantId(TenantId tenantId); + + void createDefaultUsageRecord(TenantId id); +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index cfe12a14cb..11bf45be76 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -19,5 +19,5 @@ package org.thingsboard.server.common.data; * @author Andrew Shvayka */ public enum EntityType { - TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE + TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, USAGE_RECORD; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/UsageRecord.java b/common/data/src/main/java/org/thingsboard/server/common/data/UsageRecord.java new file mode 100644 index 0000000000..e96cf2e452 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/UsageRecord.java @@ -0,0 +1,65 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UsageRecordId; +import org.thingsboard.server.common.data.id.UserId; + +@ToString +@EqualsAndHashCode(callSuper = true) +public class UsageRecord extends BaseData implements HasTenantId { + + private static final long serialVersionUID = 8250339805336035966L; + + private TenantId tenantId; + private EntityId entityId; + + public UsageRecord() { + super(); + } + + public UsageRecord(UsageRecordId id) { + super(id); + } + + public UsageRecord(UsageRecord ur) { + super(ur); + this.tenantId = ur.getTenantId(); + this.entityId = ur.getEntityId(); + } + + @Override + public TenantId getTenantId() { + return tenantId; + } + + public void setTenantId(TenantId tenantId) { + this.tenantId = tenantId; + } + + public EntityId getEntityId() { + return entityId; + } + + public void setEntityId(EntityId entityId) { + this.entityId = entityId; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/UsageRecordKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/UsageRecordKey.java new file mode 100644 index 0000000000..74a54cf713 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/UsageRecordKey.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +public enum UsageRecordKey { + + MSG_COUNT, + MSG_BYTES_COUNT, + DP_TRANSPORT_COUNT, + DP_STORAGE_COUNT, + RE_EXEC_COUNT, + JS_EXEC_COUNT + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/UsageRecordId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/UsageRecordId.java new file mode 100644 index 0000000000..76cfaaedf6 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/UsageRecordId.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; + +import java.util.UUID; + +public class UsageRecordId extends UUIDBased implements EntityId { + + @JsonCreator + public UsageRecordId(@JsonProperty("id") UUID id) { + super(id); + } + + public static UsageRecordId fromString(String userId) { + return new UsageRecordId(UUID.fromString(userId)); + } + + @JsonIgnore + @Override + public EntityType getEntityType() { + return EntityType.USAGE_RECORD; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 14ab9ed9e0..3ee942b0be 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -438,6 +438,14 @@ public class ModelConstants { public static final String OAUTH2_TEMPLATE_LOGIN_BUTTON_LABEL_PROPERTY = OAUTH2_LOGIN_BUTTON_LABEL_PROPERTY; public static final String OAUTH2_TEMPLATE_HELP_LINK_PROPERTY = "help_link"; + /** + * Usage Record constants. + */ + public static final String UR_TABLE_NAME = "usage_record"; + public static final String UR_TENANT_ID_COLUMN = TENANT_ID_PROPERTY; + public static final String UR_ENTITY_TYPE_COLUMN = ENTITY_TYPE_COLUMN; + public static final String UR_ENTITY_ID_COLUMN = ENTITY_ID_COLUMN; + /** * Cassandra attributes and timeseries constants. */ diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UsageRecordEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UsageRecordEntity.java new file mode 100644 index 0000000000..be5cc40621 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UsageRecordEntity.java @@ -0,0 +1,93 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.UsageRecord; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UsageRecordId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.model.BaseEntity; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.model.SearchTextEntity; +import org.thingsboard.server.dao.util.mapping.JsonStringType; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Table; +import java.util.UUID; + +/** + * Created by Valerii Sosliuk on 4/21/2017. + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@TypeDef(name = "json", typeClass = JsonStringType.class) +@Table(name = ModelConstants.UR_TABLE_NAME) +public class UsageRecordEntity extends BaseSqlEntity implements BaseEntity { + + @Column(name = ModelConstants.UR_TENANT_ID_COLUMN) + private UUID tenantId; + + @Column(name = ModelConstants.UR_ENTITY_TYPE_COLUMN) + private String entityType; + + @Column(name = ModelConstants.UR_ENTITY_ID_COLUMN) + private UUID entityId; + + public UsageRecordEntity() { + } + + public UsageRecordEntity(UsageRecord ur) { + if (ur.getId() != null) { + this.setUuid(ur.getId().getId()); + } + this.setCreatedTime(ur.getCreatedTime()); + if (ur.getTenantId() != null) { + this.tenantId = ur.getTenantId().getId(); + } + if (ur.getEntityId() != null) { + this.entityType = ur.getEntityId().getEntityType().name(); + this.entityId = ur.getEntityId().getId(); + } + } + + @Override + public UsageRecord toData() { + UsageRecord ur = new UsageRecord(new UsageRecordId(this.getUuid())); + ur.setCreatedTime(createdTime); + if (tenantId != null) { + ur.setTenantId(new TenantId(tenantId)); + } + if (entityId != null) { + ur.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId)); + } + return ur; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaUsageRecordDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaUsageRecordDao.java new file mode 100644 index 0000000000..8d54e6b53b --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaUsageRecordDao.java @@ -0,0 +1,60 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.usagerecord; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.UsageRecord; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.sql.UsageRecordEntity; +import org.thingsboard.server.dao.sql.JpaAbstractDao; +import org.thingsboard.server.dao.usagerecord.UsageRecordDao; + +import java.util.UUID; + +/** + * @author Andrii Shvaika + */ +@Component +public class JpaUsageRecordDao extends JpaAbstractDao implements UsageRecordDao { + + private final UsageRecordRepository usageRecordRepository; + + public JpaUsageRecordDao(UsageRecordRepository usageRecordRepository) { + this.usageRecordRepository = usageRecordRepository; + } + + @Override + protected Class getEntityClass() { + return UsageRecordEntity.class; + } + + @Override + protected CrudRepository getCrudRepository() { + return usageRecordRepository; + } + + @Override + public UsageRecord findTenantUsageRecord(UUID tenantId) { + return DaoUtil.getData(usageRecordRepository.findByTenantId(tenantId)); + } + + @Override + public void deleteUsageRecordsByTenantId(TenantId tenantId) { + usageRecordRepository.deleteUsageRecordsByTenantId(tenantId.getId()); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/UsageRecordRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/UsageRecordRepository.java new file mode 100644 index 0000000000..dc2b0d9ad7 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/UsageRecordRepository.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.usagerecord; + +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.common.data.UsageRecord; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.model.sql.UsageRecordEntity; +import org.thingsboard.server.dao.model.sql.UserEntity; + +import java.util.UUID; + +/** + * @author Valerii Sosliuk + */ +public interface UsageRecordRepository extends CrudRepository { + + @Query("SELECT ur FROM UsageRecordEntity ur WHERE ur.tenantId = :tenantId " + + "AND ur.entityId = :tenantId AND ur.entityType = 'TENANT' ") + UsageRecordEntity findByTenantId(@Param("tenantId") UUID tenantId); + + @Transactional + @Modifying + @Query("DELETE FROM UsageRecordEntity ur WHERE ur.tenantId = :tenantId") + void deleteUsageRecordsByTenantId(@Param("tenantId") UUID tenantId); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index 52761184fd..bd9ee321e0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -39,6 +39,7 @@ import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; +import org.thingsboard.server.dao.usagerecord.UsageRecordService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetsBundleService; @@ -72,6 +73,9 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe @Autowired private DeviceProfileService deviceProfileService; + @Autowired + private UsageRecordService usageRecordService; + @Autowired private EntityViewService entityViewService; @@ -117,6 +121,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe Tenant savedTenant = tenantDao.save(tenant.getId(), tenant); if (tenant.getId() == null) { deviceProfileService.createDefaultDeviceProfile(savedTenant.getId()); + usageRecordService.createDefaultUsageRecord(savedTenant.getId()); } return savedTenant; } @@ -134,6 +139,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe deviceProfileService.deleteDeviceProfilesByTenantId(tenantId); userService.deleteTenantAdmins(tenantId); ruleChainService.deleteRuleChainsByTenantId(tenantId); + usageRecordService.deleteUsageRecordsByTenantId(tenantId); tenantDao.removeById(tenantId, tenantId.getId()); deleteEntityRelations(tenantId, tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordDao.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordDao.java new file mode 100644 index 0000000000..3b71e4ce20 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordDao.java @@ -0,0 +1,51 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.usagerecord; + +import org.thingsboard.server.common.data.UsageRecord; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.Dao; + +import java.util.UUID; + +public interface UsageRecordDao extends Dao { + + /** + * Save or update usage record object + * + * @param usageRecord the usage record + * @return saved usage record entity + */ + UsageRecord save(TenantId tenantId, UsageRecord usageRecord); + + /** + * Find usage record by tenantId. + * + * @param tenantId the tenantId + * @return the corresponding usage record + */ + UsageRecord findTenantUsageRecord(UUID tenantId); + + /** + * Delete usage record by tenantId. + * + * @param tenantId the tenantId + */ + void deleteUsageRecordsByTenantId(TenantId tenantId); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordServiceImpl.java new file mode 100644 index 0000000000..fdaa38a1a2 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordServiceImpl.java @@ -0,0 +1,112 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.usagerecord; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.UsageRecord; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserCredentialsId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.security.UserCredentials; +import org.thingsboard.server.dao.entity.AbstractEntityService; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.exception.IncorrectParameterException; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TenantDao; + +import java.util.HashMap; +import java.util.Map; + +import static org.thingsboard.server.dao.service.Validator.validateId; +import static org.thingsboard.server.dao.service.Validator.validatePageLink; +import static org.thingsboard.server.dao.service.Validator.validateString; + +@Service +@Slf4j +public class UsageRecordServiceImpl extends AbstractEntityService implements UsageRecordService { + public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; + + private final UsageRecordDao usageRecordDao; + private final TenantDao tenantDao; + + public UsageRecordServiceImpl(TenantDao tenantDao, UsageRecordDao usageRecordDao) { + this.tenantDao = tenantDao; + this.usageRecordDao = usageRecordDao; + } + + @Override + public void deleteUsageRecordsByTenantId(TenantId tenantId) { + log.trace("Executing deleteUsageRecordsByTenantId [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + usageRecordDao.deleteUsageRecordsByTenantId(tenantId); + } + + @Override + public void createDefaultUsageRecord(TenantId tenantId) { + log.trace("Executing createDefaultUsageRecord [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + UsageRecord usageRecord = new UsageRecord(); + usageRecord.setTenantId(tenantId); + usageRecord.setEntityId(tenantId); + usageRecordValidator.validate(usageRecord, UsageRecord::getTenantId); + usageRecordDao.save(usageRecord.getTenantId(), usageRecord); + } + + @Override + public UsageRecord findTenantUsageRecord(TenantId tenantId) { + log.trace("Executing findTenantUsageRecord, tenantId [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + return usageRecordDao.findTenantUsageRecord(tenantId.getId()); + } + + private DataValidator usageRecordValidator = + new DataValidator() { + @Override + protected void validateDataImpl(TenantId requestTenantId, UsageRecord usageRecord) { + if (usageRecord.getTenantId() == null) { + throw new DataValidationException("UsageRecord should be assigned to tenant!"); + } else { + Tenant tenant = tenantDao.findById(requestTenantId, usageRecord.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Asset is referencing to non-existent tenant!"); + } + } + if (usageRecord.getEntityId() == null) { + throw new DataValidationException("UsageRecord should be assigned to entity!"); + } else if (!EntityType.TENANT.equals(usageRecord.getEntityId().getEntityType())) { + throw new DataValidationException("Only Tenant Usage Records are supported!"); + } else if (!usageRecord.getTenantId().getId().equals(usageRecord.getEntityId().getId())) { + throw new DataValidationException("Can't assign one Usage Record to multiple tenants!"); + } + } + }; + +} diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index 41d9e3ac81..e3cbbda351 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -404,3 +404,12 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( help_link varchar(255), CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) ); + +CREATE TABLE IF NOT EXISTS usage_record ( + id uuid NOT NULL CONSTRAINT usage_record_pkey PRIMARY KEY, + created_time bigint NOT NULL, + tenant_id uuid, + entity_type varchar(32), + entity_id uuid, + CONSTRAINT usage_record_unq_key UNIQUE (tenant_id, entity_id) +); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index e1ffba98eb..6cd3adb337 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -431,6 +431,15 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) ); +CREATE TABLE IF NOT EXISTS usage_record ( + id uuid NOT NULL CONSTRAINT usage_record_pkey PRIMARY KEY, + created_time bigint NOT NULL, + tenant_id uuid, + entity_type varchar(32), + entity_id uuid, + CONSTRAINT usage_record_unq_key UNIQUE (tenant_id, entity_id) +); + CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint) LANGUAGE plpgsql AS $$ diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java index cf6f574180..8da1727631 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java @@ -61,6 +61,7 @@ import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; +import org.thingsboard.server.dao.usagerecord.UsageRecordService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; @@ -85,6 +86,9 @@ public abstract class AbstractServiceTest { @Autowired protected UserService userService; + @Autowired + protected UsageRecordService usageRecordService; + @Autowired protected AdminSettingsService adminSettingsService; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseUsageRecordServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseUsageRecordServiceTest.java new file mode 100644 index 0000000000..28e0a6d54a --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseUsageRecordServiceTest.java @@ -0,0 +1,56 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.service; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.UsageRecord; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.service.AbstractServiceTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + + +public abstract class BaseUsageRecordServiceTest extends AbstractServiceTest { + + private TenantId tenantId; + + @Before + public void before() { + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = tenantService.saveTenant(tenant); + Assert.assertNotNull(savedTenant); + tenantId = savedTenant.getId(); + } + + @After + public void after() { + tenantService.deleteTenant(tenantId); + } + + @Test + public void testFindUsageRecordByTenantId() { + UsageRecord usageRecord = usageRecordService.findTenantUsageRecord(tenantId); + Assert.assertNotNull(usageRecord); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/sql/UsageRecordServiceSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/sql/UsageRecordServiceSqlTest.java new file mode 100644 index 0000000000..8275b92044 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/service/sql/UsageRecordServiceSqlTest.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.service.sql; + +import org.thingsboard.server.dao.service.BaseUsageRecordServiceTest; +import org.thingsboard.server.dao.service.BaseUserServiceTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class UsageRecordServiceSqlTest extends BaseUsageRecordServiceTest { +} diff --git a/dao/src/test/resources/sql/hsql/drop-all-tables.sql b/dao/src/test/resources/sql/hsql/drop-all-tables.sql index 160049a144..13c1eba943 100644 --- a/dao/src/test/resources/sql/hsql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/hsql/drop-all-tables.sql @@ -27,4 +27,5 @@ DROP TABLE IF EXISTS rule_chain; DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS oauth2_client_registration_info; DROP TABLE IF EXISTS oauth2_client_registration_template; +DROP TABLE IF EXISTS usage_record; DROP FUNCTION IF EXISTS to_uuid; diff --git a/dao/src/test/resources/sql/psql/drop-all-tables.sql b/dao/src/test/resources/sql/psql/drop-all-tables.sql index e277c39403..b8ab69c758 100644 --- a/dao/src/test/resources/sql/psql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/psql/drop-all-tables.sql @@ -28,3 +28,4 @@ DROP TABLE IF EXISTS tb_schema_settings; DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS oauth2_client_registration_info; DROP TABLE IF EXISTS oauth2_client_registration_template; +DROP TABLE IF EXISTS usage_record; \ No newline at end of file diff --git a/dao/src/test/resources/sql/timescale/drop-all-tables.sql b/dao/src/test/resources/sql/timescale/drop-all-tables.sql index da7eaff876..4333bc33ee 100644 --- a/dao/src/test/resources/sql/timescale/drop-all-tables.sql +++ b/dao/src/test/resources/sql/timescale/drop-all-tables.sql @@ -28,3 +28,4 @@ DROP TABLE IF EXISTS tb_schema_settings; DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS oauth2_client_registration_info; DROP TABLE IF EXISTS oauth2_client_registration_template; +DROP TABLE IF EXISTS usage_record; \ No newline at end of file diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java index aef2cad684..08e6fc4f71 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java @@ -340,6 +340,7 @@ final class MqttClientImpl implements MqttClient { MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(topic, getNewMessageId().messageId()); MqttPublishMessage message = new MqttPublishMessage(fixedHeader, variableHeader, payload); MqttPendingPublish pendingPublish = new MqttPendingPublish(variableHeader.packetId(), future, payload.retain(), message, qos); + ChannelFuture channelFuture = this.sendAndFlushPacket(message); if (channelFuture != null) { From 9cdf3dd80c2f6af64d313969e2d1b74635a78d03 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 19 Oct 2020 16:43:39 +0300 Subject: [PATCH 223/463] Improvements in MQTT publish processing --- .../src/main/java/org/thingsboard/mqtt/MqttClientImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java index aef2cad684..c493f2b860 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java @@ -340,6 +340,7 @@ final class MqttClientImpl implements MqttClient { MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(topic, getNewMessageId().messageId()); MqttPublishMessage message = new MqttPublishMessage(fixedHeader, variableHeader, payload); MqttPendingPublish pendingPublish = new MqttPendingPublish(variableHeader.packetId(), future, payload.retain(), message, qos); + this.pendingPublishes.put(pendingPublish.getMessageId(), pendingPublish); ChannelFuture channelFuture = this.sendAndFlushPacket(message); if (channelFuture != null) { @@ -350,9 +351,9 @@ final class MqttClientImpl implements MqttClient { } } 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()) { - this.pendingPublishes.put(pendingPublish.getMessageId(), pendingPublish); pendingPublish.startPublishRetransmissionTimer(this.eventLoop.next(), this::sendAndFlushPacket); } return future; From 24f5872adf3f50f36629ef0d5fb3b791c92be838 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 19 Oct 2020 18:00:20 +0300 Subject: [PATCH 224/463] Queue interfaces with no implementation --- .../queue/provider/TbCoreQueueFactory.java | 13 ++- .../provider/TbQueueProducerProvider.java | 11 ++ .../provider/TbRuleEngineQueueFactory.java | 2 +- .../provider/TbTransportQueueFactory.java | 10 +- .../TbTransportQueueProducerProvider.java | 7 ++ .../TbUsageStatsClientQueueFactory.java | 29 +++++ .../scheduler/DefaultSchedulerComponent.java | 61 ++++++++++ .../queue/scheduler/SchedulerComponent.java | 32 ++++++ .../usagestats/DefaultTbUsageStatsClient.java | 106 ++++++++++++++++++ .../queue/usagestats/TbUsageStatsClient.java | 26 +++++ common/queue/src/main/proto/queue.proto | 13 +++ .../transport/mqtt/MqttTransportHandler.java | 5 +- .../common/transport/TransportContext.java | 3 + .../common/transport/TransportService.java | 8 +- .../service/DefaultTransportService.java | 24 ++-- 15 files changed, 318 insertions(+), 32 deletions(-) create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/provider/TbUsageStatsClientQueueFactory.java create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/scheduler/DefaultSchedulerComponent.java create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/scheduler/SchedulerComponent.java create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java index 59a8f449b0..d009757997 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java @@ -16,6 +16,7 @@ package org.thingsboard.server.queue.provider; import org.thingsboard.server.gen.js.JsInvokeProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; @@ -33,7 +34,7 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; * Responsible for initialization of various Producers and Consumers used by TB Core Node. * Implementation Depends on the queue queue.type from yml or TB_QUEUE_TYPE environment variable */ -public interface TbCoreQueueFactory { +public interface TbCoreQueueFactory extends TbUsageStatsClientQueueFactory { /** * Used to push messages to instances of TB Transport Service @@ -77,6 +78,16 @@ public interface TbCoreQueueFactory { */ TbQueueConsumer> createToCoreMsgConsumer(); + /** + * Used to consume messages about usage statistics by TB Core Service + * + * @return + */ + default TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + //TODO: implement + return null; + } + /** * Used to consume high priority messages by TB Core Service * diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java index 34c6ee577f..fa20f11960 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.queue.provider; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; @@ -63,4 +64,14 @@ public interface TbQueueProducerProvider { */ TbQueueProducer> getTbCoreNotificationsMsgProducer(); + /** + * Used to push messages to other instances of TB Core Service + * + * @return + */ + default TbQueueProducer> getTbUsageStatsMsgProducer() { + //TODO: implement + return null; + } + } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java index 561b3e84ea..2f80bbce45 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java @@ -32,7 +32,7 @@ import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; * Responsible for initialization of various Producers and Consumers used by TB Core Node. * Implementation Depends on the queue queue.type from yml or TB_QUEUE_TYPE environment variable */ -public interface TbRuleEngineQueueFactory { +public interface TbRuleEngineQueueFactory extends TbUsageStatsClientQueueFactory { /** * Used to push messages to instances of TB Transport Service diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueFactory.java index dc1d2c449c..bd6c6e0584 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueFactory.java @@ -15,17 +15,17 @@ */ package org.thingsboard.server.queue.provider; -import org.thingsboard.server.queue.TbQueueConsumer; -import org.thingsboard.server.queue.TbQueueProducer; -import org.thingsboard.server.queue.TbQueueRequestTemplate; -import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; +import org.thingsboard.server.queue.TbQueueConsumer; +import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.TbQueueRequestTemplate; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; -public interface TbTransportQueueFactory { +public interface TbTransportQueueFactory extends TbUsageStatsClientQueueFactory { TbQueueRequestTemplate, TbProtoQueueMsg> createTransportApiRequestTemplate(); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java index bced3f83ac..9d11aa727b 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java @@ -30,6 +30,7 @@ public class TbTransportQueueProducerProvider implements TbQueueProducerProvider private final TbTransportQueueFactory tbQueueProvider; private TbQueueProducer> toRuleEngine; private TbQueueProducer> toTbCore; + private TbQueueProducer> toUsageStats; public TbTransportQueueProducerProvider(TbTransportQueueFactory tbQueueProvider) { this.tbQueueProvider = tbQueueProvider; @@ -39,6 +40,7 @@ public class TbTransportQueueProducerProvider implements TbQueueProducerProvider public void init() { this.toTbCore = tbQueueProvider.createTbCoreMsgProducer(); this.toRuleEngine = tbQueueProvider.createRuleEngineMsgProducer(); + this.toUsageStats = tbQueueProvider.createToUsageStatsServiceMsgProducer(); } @Override @@ -65,4 +67,9 @@ public class TbTransportQueueProducerProvider implements TbQueueProducerProvider public TbQueueProducer> getTbCoreNotificationsMsgProducer() { throw new RuntimeException("Not Implemented! Should not be used by Transport!"); } + + @Override + public TbQueueProducer> getTbUsageStatsMsgProducer() { + return toUsageStats; + } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbUsageStatsClientQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbUsageStatsClientQueueFactory.java new file mode 100644 index 0000000000..1811877338 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbUsageStatsClientQueueFactory.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.provider; + +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; +import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; + +public interface TbUsageStatsClientQueueFactory { + + default TbQueueProducer> createToUsageStatsServiceMsgProducer() { + //TODO: implement + return null; + } + +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/scheduler/DefaultSchedulerComponent.java b/common/queue/src/main/java/org/thingsboard/server/queue/scheduler/DefaultSchedulerComponent.java new file mode 100644 index 0000000000..02ce60496e --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/scheduler/DefaultSchedulerComponent.java @@ -0,0 +1,61 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.scheduler; + +import org.springframework.stereotype.Component; +import org.thingsboard.common.util.ThingsBoardThreadFactory; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +@Component +public class DefaultSchedulerComponent implements SchedulerComponent{ + + protected ScheduledExecutorService schedulerExecutor; + + @PostConstruct + public void init(){ + this.schedulerExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("queue-scheduler")); + } + + @PreDestroy + public void destroy() { + if (schedulerExecutor != null) { + schedulerExecutor.shutdownNow(); + } + } + + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + return schedulerExecutor.schedule(command, delay, unit); + } + + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + return schedulerExecutor.schedule(callable, delay, unit); + } + + public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + return schedulerExecutor.scheduleAtFixedRate(command, initialDelay, period, unit); + } + + public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { + return schedulerExecutor.scheduleWithFixedDelay(command, initialDelay, delay, unit); + } +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/scheduler/SchedulerComponent.java b/common/queue/src/main/java/org/thingsboard/server/queue/scheduler/SchedulerComponent.java new file mode 100644 index 0000000000..3c12be43a5 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/scheduler/SchedulerComponent.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.scheduler; + +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public interface SchedulerComponent { + + ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit); + + ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit); + + ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit); + + ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit); + +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java new file mode 100644 index 0000000000..87eb5e767a --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java @@ -0,0 +1,106 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.usagestats; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.UsageRecordKey; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; +import org.thingsboard.server.queue.TbQueueCallback; +import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.provider.TbQueueProducerProvider; +import org.thingsboard.server.queue.scheduler.SchedulerComponent; + +import javax.annotation.PostConstruct; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +@Component +public class DefaultTbUsageStatsClient implements TbUsageStatsClient { + + @Value("${usage.stats.report.enabled:true}") + private boolean enabled; + @Value("${usage.stats.report.interval:600}") + private int interval; + + private final ConcurrentMap[] values = new ConcurrentMap[UsageRecordKey.values().length]; + private final PartitionService partitionService; + private final SchedulerComponent scheduler; + private final TbQueueProducerProvider producerProvider; + private TbQueueProducer> msgProducer; + + public DefaultTbUsageStatsClient(PartitionService partitionService, SchedulerComponent scheduler, TbQueueProducerProvider producerProvider) { + this.partitionService = partitionService; + this.scheduler = scheduler; + this.producerProvider = producerProvider; + } + + @PostConstruct + private void init() { + if (enabled) { + msgProducer = this.producerProvider.getTbUsageStatsMsgProducer(); + for (UsageRecordKey key : UsageRecordKey.values()) { + values[key.ordinal()] = new ConcurrentHashMap<>(); + } + scheduler.scheduleWithFixedDelay(this::reportStats, new Random().nextInt(interval), interval, TimeUnit.SECONDS); + } + } + + private void reportStats() { + ConcurrentMap report = new ConcurrentHashMap<>(); + + for (UsageRecordKey key : UsageRecordKey.values()) { + values[key.ordinal()].forEach(((tenantId, atomicLong) -> { + long value = atomicLong.getAndSet(0); + if (value > 0) { + ToUsageStatsServiceMsg.Builder msgBuilder = report.computeIfAbsent(tenantId, id -> { + ToUsageStatsServiceMsg.Builder msg = ToUsageStatsServiceMsg.newBuilder(); + msg.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); + msg.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); + return msg; + }); + msgBuilder.addValues(UsageStatsKVProto.newBuilder().setKey(key.name()).setValue(value).build()); + } + })); + } + + report.forEach(((tenantId, builder) -> { + //TODO: figure out how to minimize messages into the queue. Maybe group by 100s of messages? + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId); + msgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), builder.build()), null); + })); + } + + @Override + public void report(TenantId tenantId, UsageRecordKey key, long value) { + if (enabled) { + ConcurrentMap map = values[key.ordinal()]; + AtomicLong atomicValue = map.computeIfAbsent(tenantId, id -> new AtomicLong()); + atomicValue.addAndGet(value); + } + } + +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java new file mode 100644 index 0000000000..e4e659b5c5 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.usagestats; + +import org.thingsboard.server.common.data.UsageRecordKey; +import org.thingsboard.server.common.data.id.TenantId; + +public interface TbUsageStatsClient { + + void report(TenantId tenantId, UsageRecordKey key, long value); + + +} diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 01d8ca4524..f716bd90ae 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -532,3 +532,16 @@ message ToTransportMsg { EntityDeleteMsg entityDeleteMsg = 9; ProvisionDeviceResponseMsg provisionResponse = 10; } + +message UsageStatsKVProto{ + string key = 1; + int64 value = 2; +} + +message ToUsageStatsServiceMsg { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + int64 entityIdMSB = 3; + int64 entityIdLSB = 4; + repeated UsageStatsKVProto values = 5; +} diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 3723b19f6d..2c4b2db7df 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -57,6 +57,7 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; +import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; import org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler; @@ -97,6 +98,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private final UUID sessionId; private final MqttTransportContext context; private final TransportService transportService; + private final SchedulerComponent scheduler; private final SslHandler sslHandler; private final ConcurrentMap mqttQoSMap; @@ -108,6 +110,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement this.sessionId = UUID.randomUUID(); this.context = context; this.transportService = context.getTransportService(); + this.scheduler = context.getScheduler(); this.sslHandler = sslHandler; this.mqttQoSMap = new ConcurrentHashMap<>(); this.deviceSessionCtx = new DeviceSessionCtx(sessionId, mqttQoSMap, context); @@ -333,7 +336,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } else { deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); } - transportService.getSchedulerExecutor().schedule(() -> processDisconnect(ctx), 60, TimeUnit.SECONDS); + scheduler.schedule(() -> processDisconnect(ctx), 60, TimeUnit.SECONDS); } catch (Exception e) { log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java index 66809f9e61..4b7677dd69 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java @@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; +import org.thingsboard.server.queue.scheduler.SchedulerComponent; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -44,6 +45,8 @@ public abstract class TransportContext { private TransportService transportService; @Autowired private TbServiceInfoProvider serviceInfoProvider; + @Autowired + private SchedulerComponent scheduler; @Getter private ExecutorService executor; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 8f57db3897..4872d8e477 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -17,16 +17,14 @@ package org.thingsboard.server.common.transport; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.limits.TransportRateLimitType; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; @@ -42,8 +40,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCre import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; -import java.util.concurrent.ScheduledExecutorService; - /** * Created by ashvayka on 04.10.18. */ @@ -92,8 +88,6 @@ public interface TransportService { void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback callback); - ScheduledExecutorService getSchedulerExecutor(); - void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener); void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 3f37685383..774a437e39 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -23,7 +23,6 @@ import com.google.gson.JsonObject; import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.DeviceProfile; @@ -53,7 +52,6 @@ import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.common.transport.limits.TransportRateLimit; import org.thingsboard.server.common.transport.limits.TransportRateLimitService; import org.thingsboard.server.common.transport.limits.TransportRateLimitType; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; @@ -79,6 +77,7 @@ import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbTransportQueueFactory; +import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.util.TbTransportComponent; import javax.annotation.PostConstruct; @@ -94,7 +93,6 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -126,6 +124,7 @@ public class DefaultTransportService implements TransportService { private final TransportTenantProfileCache tenantProfileCache; private final TransportRateLimitService rateLimitService; private final DataDecodingEncodingService dataDecodingEncodingService; + private final SchedulerComponent scheduler; protected TbQueueRequestTemplate, TbProtoQueueMsg> transportApiRequestTemplate; protected TbQueueProducer> ruleEngineMsgProducer; @@ -136,7 +135,6 @@ public class DefaultTransportService implements TransportService { protected MessagesStats tbCoreProducerStats; protected MessagesStats transportApiStats; - protected ScheduledExecutorService schedulerExecutor; protected ExecutorService transportCallbackExecutor; private ExecutorService mainConsumerExecutor; @@ -152,7 +150,7 @@ public class DefaultTransportService implements TransportService { StatsFactory statsFactory, TransportDeviceProfileCache deviceProfileCache, TransportTenantProfileCache tenantProfileCache, - TransportRateLimitService rateLimitService, DataDecodingEncodingService dataDecodingEncodingService) { + TransportRateLimitService rateLimitService, DataDecodingEncodingService dataDecodingEncodingService, SchedulerComponent scheduler) { this.serviceInfoProvider = serviceInfoProvider; this.queueProvider = queueProvider; this.producerProvider = producerProvider; @@ -162,6 +160,7 @@ public class DefaultTransportService implements TransportService { this.tenantProfileCache = tenantProfileCache; this.rateLimitService = rateLimitService; this.dataDecodingEncodingService = dataDecodingEncodingService; + this.scheduler = scheduler; } @PostConstruct @@ -169,9 +168,8 @@ public class DefaultTransportService implements TransportService { this.ruleEngineProducerStats = statsFactory.createMessagesStats(StatsType.RULE_ENGINE.getName() + ".producer"); this.tbCoreProducerStats = statsFactory.createMessagesStats(StatsType.CORE.getName() + ".producer"); this.transportApiStats = statsFactory.createMessagesStats(StatsType.TRANSPORT.getName() + ".producer"); - this.schedulerExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("transport-scheduler")); this.transportCallbackExecutor = Executors.newWorkStealingPool(20); - this.schedulerExecutor.scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) sessionReportTimeout), sessionReportTimeout, TimeUnit.MILLISECONDS); + this.scheduler.scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) sessionReportTimeout), sessionReportTimeout, TimeUnit.MILLISECONDS); transportApiRequestTemplate = queueProvider.createTransportApiRequestTemplate(); transportApiRequestTemplate.setMessagesStats(transportApiStats); ruleEngineMsgProducer = producerProvider.getRuleEngineMsgProducer(); @@ -217,9 +215,6 @@ public class DefaultTransportService implements TransportService { if (transportNotificationsConsumer != null) { transportNotificationsConsumer.unsubscribe(); } - if (schedulerExecutor != null) { - schedulerExecutor.shutdownNow(); - } if (transportCallbackExecutor != null) { transportCallbackExecutor.shutdownNow(); } @@ -231,11 +226,6 @@ public class DefaultTransportService implements TransportService { } } - @Override - public ScheduledExecutorService getSchedulerExecutor() { - return this.schedulerExecutor; - } - @Override public void registerAsyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener) { sessions.putIfAbsent(toSessionId(sessionInfo), new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listener)); @@ -488,7 +478,7 @@ public class DefaultTransportService implements TransportService { sendToRuleEngine(tenantId, tbMsg, new TransportTbQueueCallback(callback)); String requestId = sessionId + "-" + msg.getRequestId(); toServerRpcPendingMap.put(requestId, new RpcRequestMetadata(sessionId, msg.getRequestId())); - schedulerExecutor.schedule(() -> processTimeout(requestId), clientSideRpcTimeout, TimeUnit.MILLISECONDS); + scheduler.schedule(() -> processTimeout(requestId), clientSideRpcTimeout, TimeUnit.MILLISECONDS); } } @@ -561,7 +551,7 @@ public class DefaultTransportService implements TransportService { SessionMetaData currentSession = new SessionMetaData(sessionInfo, TransportProtos.SessionType.SYNC, listener); sessions.putIfAbsent(toSessionId(sessionInfo), currentSession); - ScheduledFuture executorFuture = schedulerExecutor.schedule(() -> { + ScheduledFuture executorFuture = scheduler.schedule(() -> { listener.onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto.getDefaultInstance()); deregisterSession(sessionInfo); }, timeout, TimeUnit.MILLISECONDS); From 5eb91c17f6790ed973d8c44a23d7d5a88284a8c8 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 19 Oct 2020 18:25:34 +0300 Subject: [PATCH 225/463] Usage records --- .../queue/DefaultTbCoreConsumerService.java | 79 +++++++++++++++++-- .../DefaultTbUsageStatsService.java | 15 ++++ .../usagestats/TbUsageStatsService.java | 10 +++ 3 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 5d240790f2..3f03b65416 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -17,20 +17,21 @@ package org.thingsboard.server.service.queue; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.rule.engine.api.RpcError; import org.thingsboard.server.actors.ActorSystemContext; -import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.TbActorMsg; -import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.dao.util.mapping.JacksonUtil; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto; import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto; @@ -61,6 +62,7 @@ import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; +import org.thingsboard.server.service.usagestats.TbUsageStatsService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -70,6 +72,8 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Collectors; @@ -88,32 +92,50 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> mainConsumer; private final DeviceStateService stateService; + private final TbUsageStatsService statsService; private final TbLocalSubscriptionService localSubscriptionService; private final SubscriptionManagerService subscriptionManagerService; private final TbCoreDeviceRpcService tbCoreDeviceRpcService; private final TbCoreConsumerStats stats; + protected final TbQueueConsumer> usageStatsConsumer; + + protected volatile ExecutorService usageStatsExecutor; + public DefaultTbCoreConsumerService(TbCoreQueueFactory tbCoreQueueFactory, ActorSystemContext actorContext, DeviceStateService stateService, TbLocalSubscriptionService localSubscriptionService, SubscriptionManagerService subscriptionManagerService, DataDecodingEncodingService encodingService, - TbCoreDeviceRpcService tbCoreDeviceRpcService, StatsFactory statsFactory, TbDeviceProfileCache deviceProfileCache) { + TbCoreDeviceRpcService tbCoreDeviceRpcService, StatsFactory statsFactory, TbDeviceProfileCache deviceProfileCache, + TbUsageStatsService statsService) { super(actorContext, encodingService, deviceProfileCache, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer()); this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer(); + this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer(); this.stateService = stateService; this.localSubscriptionService = localSubscriptionService; this.subscriptionManagerService = subscriptionManagerService; this.tbCoreDeviceRpcService = tbCoreDeviceRpcService; this.stats = new TbCoreConsumerStats(statsFactory); + this.statsService = statsService; } @PostConstruct public void init() { super.init("tb-core-consumer", "tb-core-notifications-consumer"); + this.usageStatsExecutor = Executors.newCachedThreadPool(ThingsBoardThreadFactory.forName("tb-core-usage-stats-consumer")); } @PreDestroy public void destroy() { super.destroy(); + if (usageStatsExecutor != null) { + usageStatsExecutor.shutdownNow(); + } + } + + @EventListener(ApplicationReadyEvent.class) + public void onApplicationEvent(ApplicationReadyEvent event) { + super.onApplicationEvent(event); + launchUsageStatsConsumer(); } @Override @@ -223,6 +245,53 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService { + while (!stopped) { + try { + List> msgs = usageStatsConsumer.poll(getNotificationPollDuration()); + if (msgs.isEmpty()) { + continue; + } + ConcurrentMap> pendingMap = msgs.stream().collect( + Collectors.toConcurrentMap(s -> UUID.randomUUID(), Function.identity())); + CountDownLatch processingTimeoutLatch = new CountDownLatch(1); + TbPackProcessingContext> ctx = new TbPackProcessingContext<>( + processingTimeoutLatch, pendingMap, new ConcurrentHashMap<>()); + pendingMap.forEach((id, msg) -> { + log.trace("[{}] Creating usage stats callback for message: {}", id, msg.getValue()); + TbCallback callback = new TbPackCallback<>(id, ctx); + try { + handleUsageStats(msg, callback); + } catch (Throwable e) { + log.warn("[{}] Failed to process usge stats: {}", id, msg, e); + callback.onFailure(e); + } + }); + if (!processingTimeoutLatch.await(getNotificationPackProcessingTimeout(), TimeUnit.MILLISECONDS)) { + ctx.getAckMap().forEach((id, msg) -> log.warn("[{}] Timeout to process usage stats: {}", id, msg.getValue())); + ctx.getFailedMap().forEach((id, msg) -> log.warn("[{}] Failed to process usage stats: {}", id, msg.getValue())); + } + usageStatsConsumer.commit(); + } catch (Exception e) { + if (!stopped) { + log.warn("Failed to obtain usage stats from queue.", e); + try { + Thread.sleep(getNotificationPollDuration()); + } catch (InterruptedException e2) { + log.trace("Failed to wait until the server has capacity to handle new usage stats", e2); + } + } + } + } + log.info("TB Usage Stats Consumer stopped."); + }); + } + + private void handleUsageStats(TbProtoQueueMsg msg, TbCallback callback) { + statsService.process(msg, callback); + } + private void forwardToCoreRpcService(FromDeviceRPCResponseProto proto, TbCallback callback) { RpcError error = proto.getError() > 0 ? RpcError.values()[proto.getError()] : null; FromDeviceRpcResponse response = new FromDeviceRpcResponse(new UUID(proto.getRequestIdMSB(), proto.getRequestIdLSB()) diff --git a/application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java b/application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java new file mode 100644 index 0000000000..6611507531 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java @@ -0,0 +1,15 @@ +package org.thingsboard.server.service.usagestats; + +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; + +@Service +public class DefaultTbUsageStatsService implements TbUsageStatsService { + @Override + public void process(TbProtoQueueMsg msg, TbCallback callback) { + + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java b/application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java new file mode 100644 index 0000000000..35a7051896 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java @@ -0,0 +1,10 @@ +package org.thingsboard.server.service.usagestats; + +import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; + +public interface TbUsageStatsService { + + void process(TbProtoQueueMsg msg, TbCallback callback); +} From 47d5aee3dc4a3d88b54e06f814443e34ff4afd05 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 19 Oct 2020 19:05:34 +0300 Subject: [PATCH 226/463] UI: Improvement alarm widget and fixed timeseries --- .../lib/alarms-table-widget.component.ts | 6 ++--- .../widget/lib/table-widget.models.ts | 2 +- .../lib/timeseries-table-widget.component.ts | 22 ++++++++++++------- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts index 26055c55b2..58176a48d1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts @@ -29,7 +29,7 @@ import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { WidgetAction, WidgetContext } from '@home/models/widget-component.models'; -import { DataKey, Datasource, WidgetActionDescriptor, WidgetConfig } from '@shared/models/widget.models'; +import { DataKey, WidgetActionDescriptor, WidgetConfig } from '@shared/models/widget.models'; import { IWidgetSubscription } from '@core/api/widget-api.models'; import { UtilsService } from '@core/services/utils.service'; import { TranslateService } from '@ngx-translate/core'; @@ -394,7 +394,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, this.displayedColumns.push(...this.columns.map(column => column.def)); } if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) { - this.defaultSortOrder = this.settings.defaultSortOrder; + this.defaultSortOrder = this.utils.customTranslation(this.settings.defaultSortOrder, this.settings.defaultSortOrder); } this.pageLink.sortOrder = entityDataSortOrderFromString(this.defaultSortOrder, this.columns); let sortColumn: EntityColumn; @@ -959,7 +959,7 @@ class AlarmsDatasource implements DataSource { } } } - alarm[dataKey.name] = value; + alarm[dataKey.label] = value; }); return alarm; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts index 9e079bc247..1ab9ea6fc4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts @@ -189,7 +189,7 @@ export function getAlarmValue(alarm: AlarmDataInfo, key: EntityColumn) { if (alarmField) { return getDescendantProp(alarm, alarmField.value); } else { - return getDescendantProp(alarm, key.name); + return getDescendantProp(alarm, key.label); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts index ebf5387862..1b820ab722 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts @@ -524,16 +524,22 @@ class TimeseriesDatasource implements DataSource { }); } - const rows: TimeseriesRow[] = []; - - for (const value of Object.values(rowsMap)) { - if (this.hideEmptyLines && isDefinedAndNotNull(value[1])) { - rows.push(value); - } else { - rows.push(value); + let rows: TimeseriesRow[] = []; + if (this.hideEmptyLines) { + for (const t of Object.keys(rowsMap)) { + let hideLine = true; + for (let c = 0; (c < data.length) && hideLine; c++) { + if (rowsMap[t][c + 1]) { + hideLine = false; + } + } + if (!hideLine) { + rows.push(rowsMap[t]); + } } + } else { + rows = Object.values(rowsMap); } - return rows; } From b598a7395492151598f8fe956d0022d5b4de33c3 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 19 Oct 2020 19:51:58 +0300 Subject: [PATCH 227/463] Renaming of UsageRecord to ApiUsageState --- .../apiusage/DefaultTbUsageStatsService.java | 30 +++++ .../service/apiusage/TbUsageStatsService.java | 25 ++++ .../queue/DefaultTbCoreConsumerService.java | 4 +- .../DefaultTbUsageStatsService.java | 15 --- .../usagestats/TbUsageStatsService.java | 10 -- ...Service.java => ApiUsageStateService.java} | 10 +- ...eRecordKey.java => ApiUsageRecordKey.java} | 2 +- .../{UsageRecord.java => ApiUsageState.java} | 11 +- .../server/common/data/EntityType.java | 2 +- ...sageRecordId.java => ApiUsageStateId.java} | 10 +- .../usagestats/DefaultTbUsageStatsClient.java | 11 +- .../queue/usagestats/TbUsageStatsClient.java | 4 +- .../server/dao/model/ModelConstants.java | 8 +- ...rdEntity.java => ApiUsageStateEntity.java} | 31 ++--- ...tory.java => ApiUsageStateRepository.java} | 15 +-- ...ecordDao.java => JpaApiUsageStateDao.java} | 30 ++--- .../server/dao/tenant/TenantServiceImpl.java | 8 +- .../ApiApiUsageStateServiceImpl.java | 91 ++++++++++++++ ...geRecordDao.java => ApiUsageStateDao.java} | 15 +-- .../usagerecord/UsageRecordServiceImpl.java | 112 ------------------ .../resources/sql/schema-entities-hsql.sql | 4 +- .../main/resources/sql/schema-entities.sql | 4 +- .../dao/service/AbstractServiceTest.java | 8 +- ...java => BaseApiUsageStateServiceTest.java} | 15 +-- ....java => ApiUsageStateServiceSqlTest.java} | 5 +- .../resources/sql/hsql/drop-all-tables.sql | 2 +- .../resources/sql/psql/drop-all-tables.sql | 2 +- .../sql/timescale/drop-all-tables.sql | 2 +- 28 files changed, 234 insertions(+), 252 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbUsageStatsService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/apiusage/TbUsageStatsService.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java rename common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/{UsageRecordService.java => ApiUsageStateService.java} (73%) rename common/data/src/main/java/org/thingsboard/server/common/data/{UsageRecordKey.java => ApiUsageRecordKey.java} (96%) rename common/data/src/main/java/org/thingsboard/server/common/data/{UsageRecord.java => ApiUsageState.java} (83%) rename common/data/src/main/java/org/thingsboard/server/common/data/id/{UsageRecordId.java => ApiUsageStateId.java} (77%) rename dao/src/main/java/org/thingsboard/server/dao/model/sql/{UsageRecordEntity.java => ApiUsageStateEntity.java} (67%) rename dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/{UsageRecordRepository.java => ApiUsageStateRepository.java} (64%) rename dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/{JpaUsageRecordDao.java => JpaApiUsageStateDao.java} (51%) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java rename dao/src/main/java/org/thingsboard/server/dao/usagerecord/{UsageRecordDao.java => ApiUsageStateDao.java} (69%) delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordServiceImpl.java rename dao/src/test/java/org/thingsboard/server/dao/service/{BaseUsageRecordServiceTest.java => BaseApiUsageStateServiceTest.java} (68%) rename dao/src/test/java/org/thingsboard/server/dao/service/sql/{UsageRecordServiceSqlTest.java => ApiUsageStateServiceSqlTest.java} (77%) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbUsageStatsService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbUsageStatsService.java new file mode 100644 index 0000000000..e2679d6433 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbUsageStatsService.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.apiusage; + +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; + +@Service +public class DefaultTbUsageStatsService implements TbUsageStatsService { + @Override + public void process(TbProtoQueueMsg msg, TbCallback callback) { + + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TbUsageStatsService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TbUsageStatsService.java new file mode 100644 index 0000000000..3c54479eaf --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TbUsageStatsService.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.apiusage; + +import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; + +public interface TbUsageStatsService { + + void process(TbProtoQueueMsg msg, TbCallback callback); +} diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 3f03b65416..a389eefd1a 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -62,7 +62,7 @@ import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; -import org.thingsboard.server.service.usagestats.TbUsageStatsService; +import org.thingsboard.server.service.apiusage.TbUsageStatsService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; diff --git a/application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java b/application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java deleted file mode 100644 index 6611507531..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.thingsboard.server.service.usagestats; - -import org.springframework.stereotype.Service; -import org.thingsboard.server.common.msg.queue.TbCallback; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.common.TbProtoQueueMsg; - -@Service -public class DefaultTbUsageStatsService implements TbUsageStatsService { - @Override - public void process(TbProtoQueueMsg msg, TbCallback callback) { - - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java b/application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java deleted file mode 100644 index 35a7051896..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.thingsboard.server.service.usagestats; - -import org.thingsboard.server.common.msg.queue.TbCallback; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.common.TbProtoQueueMsg; - -public interface TbUsageStatsService { - - void process(TbProtoQueueMsg msg, TbCallback callback); -} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java similarity index 73% rename from common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordService.java rename to common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java index 55adc257ad..ad6c73f63e 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java @@ -15,14 +15,14 @@ */ package org.thingsboard.server.dao.usagerecord; -import org.thingsboard.server.common.data.UsageRecord; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.id.TenantId; -public interface UsageRecordService { +public interface ApiUsageStateService { - UsageRecord findTenantUsageRecord(TenantId tenantId); + ApiUsageState findTenantApiUsageState(TenantId tenantId); - void deleteUsageRecordsByTenantId(TenantId tenantId); + void deleteApiUsageStateByTenantId(TenantId tenantId); - void createDefaultUsageRecord(TenantId id); + void createDefaultApiUsageState(TenantId id); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/UsageRecordKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java similarity index 96% rename from common/data/src/main/java/org/thingsboard/server/common/data/UsageRecordKey.java rename to common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java index 74a54cf713..9c8690a88f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/UsageRecordKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data; -public enum UsageRecordKey { +public enum ApiUsageRecordKey { MSG_COUNT, MSG_BYTES_COUNT, diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/UsageRecord.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java similarity index 83% rename from common/data/src/main/java/org/thingsboard/server/common/data/UsageRecord.java rename to common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java index e96cf2e452..796e434c8a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/UsageRecord.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java @@ -19,27 +19,26 @@ import lombok.EqualsAndHashCode; import lombok.ToString; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UsageRecordId; -import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.id.ApiUsageStateId; @ToString @EqualsAndHashCode(callSuper = true) -public class UsageRecord extends BaseData implements HasTenantId { +public class ApiUsageState extends BaseData implements HasTenantId { private static final long serialVersionUID = 8250339805336035966L; private TenantId tenantId; private EntityId entityId; - public UsageRecord() { + public ApiUsageState() { super(); } - public UsageRecord(UsageRecordId id) { + public ApiUsageState(ApiUsageStateId id) { super(id); } - public UsageRecord(UsageRecord ur) { + public ApiUsageState(ApiUsageState ur) { super(ur); this.tenantId = ur.getTenantId(); this.entityId = ur.getEntityId(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index 11bf45be76..9f7fb711e8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -19,5 +19,5 @@ package org.thingsboard.server.common.data; * @author Andrew Shvayka */ public enum EntityType { - TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, USAGE_RECORD; + TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, API_USAGE_STATE; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/UsageRecordId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/ApiUsageStateId.java similarity index 77% rename from common/data/src/main/java/org/thingsboard/server/common/data/id/UsageRecordId.java rename to common/data/src/main/java/org/thingsboard/server/common/data/id/ApiUsageStateId.java index 76cfaaedf6..87983f519a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/UsageRecordId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/ApiUsageStateId.java @@ -22,21 +22,21 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -public class UsageRecordId extends UUIDBased implements EntityId { +public class ApiUsageStateId extends UUIDBased implements EntityId { @JsonCreator - public UsageRecordId(@JsonProperty("id") UUID id) { + public ApiUsageStateId(@JsonProperty("id") UUID id) { super(id); } - public static UsageRecordId fromString(String userId) { - return new UsageRecordId(UUID.fromString(userId)); + public static ApiUsageStateId fromString(String userId) { + return new ApiUsageStateId(UUID.fromString(userId)); } @JsonIgnore @Override public EntityType getEntityType() { - return EntityType.USAGE_RECORD; + return EntityType.API_USAGE_STATE; } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java index 87eb5e767a..534937031c 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java @@ -17,13 +17,12 @@ package org.thingsboard.server.queue.usagestats; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.UsageRecordKey; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; -import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionService; @@ -46,7 +45,7 @@ public class DefaultTbUsageStatsClient implements TbUsageStatsClient { @Value("${usage.stats.report.interval:600}") private int interval; - private final ConcurrentMap[] values = new ConcurrentMap[UsageRecordKey.values().length]; + private final ConcurrentMap[] values = new ConcurrentMap[ApiUsageRecordKey.values().length]; private final PartitionService partitionService; private final SchedulerComponent scheduler; private final TbQueueProducerProvider producerProvider; @@ -62,7 +61,7 @@ public class DefaultTbUsageStatsClient implements TbUsageStatsClient { private void init() { if (enabled) { msgProducer = this.producerProvider.getTbUsageStatsMsgProducer(); - for (UsageRecordKey key : UsageRecordKey.values()) { + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { values[key.ordinal()] = new ConcurrentHashMap<>(); } scheduler.scheduleWithFixedDelay(this::reportStats, new Random().nextInt(interval), interval, TimeUnit.SECONDS); @@ -72,7 +71,7 @@ public class DefaultTbUsageStatsClient implements TbUsageStatsClient { private void reportStats() { ConcurrentMap report = new ConcurrentHashMap<>(); - for (UsageRecordKey key : UsageRecordKey.values()) { + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { values[key.ordinal()].forEach(((tenantId, atomicLong) -> { long value = atomicLong.getAndSet(0); if (value > 0) { @@ -95,7 +94,7 @@ public class DefaultTbUsageStatsClient implements TbUsageStatsClient { } @Override - public void report(TenantId tenantId, UsageRecordKey key, long value) { + public void report(TenantId tenantId, ApiUsageRecordKey key, long value) { if (enabled) { ConcurrentMap map = values[key.ordinal()]; AtomicLong atomicValue = map.computeIfAbsent(tenantId, id -> new AtomicLong()); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java index e4e659b5c5..4ed850a018 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java @@ -15,12 +15,12 @@ */ package org.thingsboard.server.queue.usagestats; -import org.thingsboard.server.common.data.UsageRecordKey; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.id.TenantId; public interface TbUsageStatsClient { - void report(TenantId tenantId, UsageRecordKey key, long value); + void report(TenantId tenantId, ApiUsageRecordKey key, long value); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 3ee942b0be..a0f5b45f02 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -441,10 +441,10 @@ public class ModelConstants { /** * Usage Record constants. */ - public static final String UR_TABLE_NAME = "usage_record"; - public static final String UR_TENANT_ID_COLUMN = TENANT_ID_PROPERTY; - public static final String UR_ENTITY_TYPE_COLUMN = ENTITY_TYPE_COLUMN; - public static final String UR_ENTITY_ID_COLUMN = ENTITY_ID_COLUMN; + public static final String API_USAGE_STATE_TABLE_NAME = "api_usage_state"; + public static final String API_USAGE_STATE_TENANT_ID_COLUMN = TENANT_ID_PROPERTY; + public static final String API_USAGE_STATE_ENTITY_TYPE_COLUMN = ENTITY_TYPE_COLUMN; + public static final String API_USAGE_STATE_ENTITY_ID_COLUMN = ENTITY_ID_COLUMN; /** * Cassandra attributes and timeseries constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UsageRecordEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java similarity index 67% rename from dao/src/main/java/org/thingsboard/server/dao/model/sql/UsageRecordEntity.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java index be5cc40621..a06ec142d3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UsageRecordEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java @@ -15,29 +15,20 @@ */ package org.thingsboard.server.dao.model.sql; -import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; -import org.thingsboard.server.common.data.UsageRecord; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UsageRecordId; -import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.model.SearchTextEntity; import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; import javax.persistence.Table; import java.util.UUID; @@ -48,22 +39,22 @@ import java.util.UUID; @EqualsAndHashCode(callSuper = true) @Entity @TypeDef(name = "json", typeClass = JsonStringType.class) -@Table(name = ModelConstants.UR_TABLE_NAME) -public class UsageRecordEntity extends BaseSqlEntity implements BaseEntity { +@Table(name = ModelConstants.API_USAGE_STATE_TABLE_NAME) +public class ApiUsageStateEntity extends BaseSqlEntity implements BaseEntity { - @Column(name = ModelConstants.UR_TENANT_ID_COLUMN) + @Column(name = ModelConstants.API_USAGE_STATE_TENANT_ID_COLUMN) private UUID tenantId; - @Column(name = ModelConstants.UR_ENTITY_TYPE_COLUMN) + @Column(name = ModelConstants.API_USAGE_STATE_ENTITY_TYPE_COLUMN) private String entityType; - @Column(name = ModelConstants.UR_ENTITY_ID_COLUMN) + @Column(name = ModelConstants.API_USAGE_STATE_ENTITY_ID_COLUMN) private UUID entityId; - public UsageRecordEntity() { + public ApiUsageStateEntity() { } - public UsageRecordEntity(UsageRecord ur) { + public ApiUsageStateEntity(ApiUsageState ur) { if (ur.getId() != null) { this.setUuid(ur.getId().getId()); } @@ -78,8 +69,8 @@ public class UsageRecordEntity extends BaseSqlEntity implements Bas } @Override - public UsageRecord toData() { - UsageRecord ur = new UsageRecord(new UsageRecordId(this.getUuid())); + public ApiUsageState toData() { + ApiUsageState ur = new ApiUsageState(new ApiUsageStateId(this.getUuid())); ur.setCreatedTime(createdTime); if (tenantId != null) { ur.setTenantId(new TenantId(tenantId)); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/UsageRecordRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java similarity index 64% rename from dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/UsageRecordRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java index dc2b0d9ad7..122bb9d966 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/UsageRecordRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java @@ -20,24 +20,21 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; -import org.thingsboard.server.common.data.UsageRecord; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.model.sql.UsageRecordEntity; -import org.thingsboard.server.dao.model.sql.UserEntity; +import org.thingsboard.server.dao.model.sql.ApiUsageStateEntity; import java.util.UUID; /** * @author Valerii Sosliuk */ -public interface UsageRecordRepository extends CrudRepository { +public interface ApiUsageStateRepository extends CrudRepository { - @Query("SELECT ur FROM UsageRecordEntity ur WHERE ur.tenantId = :tenantId " + + @Query("SELECT ur FROM ApiUsageStateEntity ur WHERE ur.tenantId = :tenantId " + "AND ur.entityId = :tenantId AND ur.entityType = 'TENANT' ") - UsageRecordEntity findByTenantId(@Param("tenantId") UUID tenantId); + ApiUsageStateEntity findByTenantId(@Param("tenantId") UUID tenantId); @Transactional @Modifying - @Query("DELETE FROM UsageRecordEntity ur WHERE ur.tenantId = :tenantId") - void deleteUsageRecordsByTenantId(@Param("tenantId") UUID tenantId); + @Query("DELETE FROM ApiUsageStateEntity ur WHERE ur.tenantId = :tenantId") + void deleteApiUsageStateByTenantId(@Param("tenantId") UUID tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaUsageRecordDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaApiUsageStateDao.java similarity index 51% rename from dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaUsageRecordDao.java rename to dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaApiUsageStateDao.java index 8d54e6b53b..2c6b2e924d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaUsageRecordDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaApiUsageStateDao.java @@ -17,12 +17,12 @@ package org.thingsboard.server.dao.sql.usagerecord; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.UsageRecord; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.sql.UsageRecordEntity; +import org.thingsboard.server.dao.model.sql.ApiUsageStateEntity; import org.thingsboard.server.dao.sql.JpaAbstractDao; -import org.thingsboard.server.dao.usagerecord.UsageRecordDao; +import org.thingsboard.server.dao.usagerecord.ApiUsageStateDao; import java.util.UUID; @@ -30,31 +30,31 @@ import java.util.UUID; * @author Andrii Shvaika */ @Component -public class JpaUsageRecordDao extends JpaAbstractDao implements UsageRecordDao { +public class JpaApiUsageStateDao extends JpaAbstractDao implements ApiUsageStateDao { - private final UsageRecordRepository usageRecordRepository; + private final ApiUsageStateRepository apiUsageStateRepository; - public JpaUsageRecordDao(UsageRecordRepository usageRecordRepository) { - this.usageRecordRepository = usageRecordRepository; + public JpaApiUsageStateDao(ApiUsageStateRepository apiUsageStateRepository) { + this.apiUsageStateRepository = apiUsageStateRepository; } @Override - protected Class getEntityClass() { - return UsageRecordEntity.class; + protected Class getEntityClass() { + return ApiUsageStateEntity.class; } @Override - protected CrudRepository getCrudRepository() { - return usageRecordRepository; + protected CrudRepository getCrudRepository() { + return apiUsageStateRepository; } @Override - public UsageRecord findTenantUsageRecord(UUID tenantId) { - return DaoUtil.getData(usageRecordRepository.findByTenantId(tenantId)); + public ApiUsageState findTenantApiUsageState(UUID tenantId) { + return DaoUtil.getData(apiUsageStateRepository.findByTenantId(tenantId)); } @Override - public void deleteUsageRecordsByTenantId(TenantId tenantId) { - usageRecordRepository.deleteUsageRecordsByTenantId(tenantId.getId()); + public void deleteApiUsageStateByTenantId(TenantId tenantId) { + apiUsageStateRepository.deleteApiUsageStateByTenantId(tenantId.getId()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index bd9ee321e0..3a0edf5eea 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -39,7 +39,7 @@ import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; -import org.thingsboard.server.dao.usagerecord.UsageRecordService; +import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetsBundleService; @@ -74,7 +74,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe private DeviceProfileService deviceProfileService; @Autowired - private UsageRecordService usageRecordService; + private ApiUsageStateService apiUsageStateService; @Autowired private EntityViewService entityViewService; @@ -121,7 +121,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe Tenant savedTenant = tenantDao.save(tenant.getId(), tenant); if (tenant.getId() == null) { deviceProfileService.createDefaultDeviceProfile(savedTenant.getId()); - usageRecordService.createDefaultUsageRecord(savedTenant.getId()); + apiUsageStateService.createDefaultApiUsageState(savedTenant.getId()); } return savedTenant; } @@ -139,7 +139,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe deviceProfileService.deleteDeviceProfilesByTenantId(tenantId); userService.deleteTenantAdmins(tenantId); ruleChainService.deleteRuleChainsByTenantId(tenantId); - usageRecordService.deleteUsageRecordsByTenantId(tenantId); + apiUsageStateService.deleteApiUsageStateByTenantId(tenantId); tenantDao.removeById(tenantId, tenantId.getId()); deleteEntityRelations(tenantId, tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java new file mode 100644 index 0000000000..9e543a8f7a --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java @@ -0,0 +1,91 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.usagerecord; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.entity.AbstractEntityService; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TenantDao; + +import static org.thingsboard.server.dao.service.Validator.validateId; + +@Service +@Slf4j +public class ApiApiUsageStateServiceImpl extends AbstractEntityService implements ApiUsageStateService { + public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; + + private final ApiUsageStateDao apiUsageStateDao; + private final TenantDao tenantDao; + + public ApiApiUsageStateServiceImpl(TenantDao tenantDao, ApiUsageStateDao apiUsageStateDao) { + this.tenantDao = tenantDao; + this.apiUsageStateDao = apiUsageStateDao; + } + + @Override + public void deleteApiUsageStateByTenantId(TenantId tenantId) { + log.trace("Executing deleteUsageRecordsByTenantId [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + apiUsageStateDao.deleteApiUsageStateByTenantId(tenantId); + } + + @Override + public void createDefaultApiUsageState(TenantId tenantId) { + log.trace("Executing createDefaultUsageRecord [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + ApiUsageState apiUsageState = new ApiUsageState(); + apiUsageState.setTenantId(tenantId); + apiUsageState.setEntityId(tenantId); + apiUsageStateValidator.validate(apiUsageState, ApiUsageState::getTenantId); + apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); + } + + @Override + public ApiUsageState findTenantApiUsageState(TenantId tenantId) { + log.trace("Executing findTenantUsageRecord, tenantId [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + return apiUsageStateDao.findTenantApiUsageState(tenantId.getId()); + } + + private DataValidator apiUsageStateValidator = + new DataValidator() { + @Override + protected void validateDataImpl(TenantId requestTenantId, ApiUsageState apiUsageState) { + if (apiUsageState.getTenantId() == null) { + throw new DataValidationException("ApiUsageState should be assigned to tenant!"); + } else { + Tenant tenant = tenantDao.findById(requestTenantId, apiUsageState.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Asset is referencing to non-existent tenant!"); + } + } + if (apiUsageState.getEntityId() == null) { + throw new DataValidationException("UsageRecord should be assigned to entity!"); + } else if (!EntityType.TENANT.equals(apiUsageState.getEntityId().getEntityType())) { + throw new DataValidationException("Only Tenant Usage Records are supported!"); + } else if (!apiUsageState.getTenantId().getId().equals(apiUsageState.getEntityId().getId())) { + throw new DataValidationException("Can't assign one Usage Record to multiple tenants!"); + } + } + }; + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordDao.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateDao.java similarity index 69% rename from dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordDao.java rename to dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateDao.java index 3b71e4ce20..1521da525e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateDao.java @@ -15,24 +15,21 @@ */ package org.thingsboard.server.dao.usagerecord; -import org.thingsboard.server.common.data.UsageRecord; -import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; import java.util.UUID; -public interface UsageRecordDao extends Dao { +public interface ApiUsageStateDao extends Dao { /** * Save or update usage record object * - * @param usageRecord the usage record + * @param apiUsageState the usage record * @return saved usage record entity */ - UsageRecord save(TenantId tenantId, UsageRecord usageRecord); + ApiUsageState save(TenantId tenantId, ApiUsageState apiUsageState); /** * Find usage record by tenantId. @@ -40,12 +37,12 @@ public interface UsageRecordDao extends Dao { * @param tenantId the tenantId * @return the corresponding usage record */ - UsageRecord findTenantUsageRecord(UUID tenantId); + ApiUsageState findTenantApiUsageState(UUID tenantId); /** * Delete usage record by tenantId. * * @param tenantId the tenantId */ - void deleteUsageRecordsByTenantId(TenantId tenantId); + void deleteApiUsageStateByTenantId(TenantId tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordServiceImpl.java deleted file mode 100644 index fdaa38a1a2..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordServiceImpl.java +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.usagerecord; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.common.util.concurrent.ListenableFuture; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.RandomStringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.UsageRecord; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UserCredentialsId; -import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.security.UserCredentials; -import org.thingsboard.server.dao.entity.AbstractEntityService; -import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.exception.IncorrectParameterException; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.service.DataValidator; -import org.thingsboard.server.dao.tenant.TenantDao; - -import java.util.HashMap; -import java.util.Map; - -import static org.thingsboard.server.dao.service.Validator.validateId; -import static org.thingsboard.server.dao.service.Validator.validatePageLink; -import static org.thingsboard.server.dao.service.Validator.validateString; - -@Service -@Slf4j -public class UsageRecordServiceImpl extends AbstractEntityService implements UsageRecordService { - public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; - - private final UsageRecordDao usageRecordDao; - private final TenantDao tenantDao; - - public UsageRecordServiceImpl(TenantDao tenantDao, UsageRecordDao usageRecordDao) { - this.tenantDao = tenantDao; - this.usageRecordDao = usageRecordDao; - } - - @Override - public void deleteUsageRecordsByTenantId(TenantId tenantId) { - log.trace("Executing deleteUsageRecordsByTenantId [{}]", tenantId); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - usageRecordDao.deleteUsageRecordsByTenantId(tenantId); - } - - @Override - public void createDefaultUsageRecord(TenantId tenantId) { - log.trace("Executing createDefaultUsageRecord [{}]", tenantId); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - UsageRecord usageRecord = new UsageRecord(); - usageRecord.setTenantId(tenantId); - usageRecord.setEntityId(tenantId); - usageRecordValidator.validate(usageRecord, UsageRecord::getTenantId); - usageRecordDao.save(usageRecord.getTenantId(), usageRecord); - } - - @Override - public UsageRecord findTenantUsageRecord(TenantId tenantId) { - log.trace("Executing findTenantUsageRecord, tenantId [{}]", tenantId); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - return usageRecordDao.findTenantUsageRecord(tenantId.getId()); - } - - private DataValidator usageRecordValidator = - new DataValidator() { - @Override - protected void validateDataImpl(TenantId requestTenantId, UsageRecord usageRecord) { - if (usageRecord.getTenantId() == null) { - throw new DataValidationException("UsageRecord should be assigned to tenant!"); - } else { - Tenant tenant = tenantDao.findById(requestTenantId, usageRecord.getTenantId().getId()); - if (tenant == null) { - throw new DataValidationException("Asset is referencing to non-existent tenant!"); - } - } - if (usageRecord.getEntityId() == null) { - throw new DataValidationException("UsageRecord should be assigned to entity!"); - } else if (!EntityType.TENANT.equals(usageRecord.getEntityId().getEntityType())) { - throw new DataValidationException("Only Tenant Usage Records are supported!"); - } else if (!usageRecord.getTenantId().getId().equals(usageRecord.getEntityId().getId())) { - throw new DataValidationException("Can't assign one Usage Record to multiple tenants!"); - } - } - }; - -} diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index e3cbbda351..c671cd2e19 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -405,11 +405,11 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) ); -CREATE TABLE IF NOT EXISTS usage_record ( +CREATE TABLE IF NOT EXISTS api_usage_state ( id uuid NOT NULL CONSTRAINT usage_record_pkey PRIMARY KEY, created_time bigint NOT NULL, tenant_id uuid, entity_type varchar(32), entity_id uuid, - CONSTRAINT usage_record_unq_key UNIQUE (tenant_id, entity_id) + CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) ); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 6cd3adb337..d8539d9af9 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -431,13 +431,13 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) ); -CREATE TABLE IF NOT EXISTS usage_record ( +CREATE TABLE IF NOT EXISTS api_usage_state ( id uuid NOT NULL CONSTRAINT usage_record_pkey PRIMARY KEY, created_time bigint NOT NULL, tenant_id uuid, entity_type varchar(32), entity_id uuid, - CONSTRAINT usage_record_unq_key UNIQUE (tenant_id, entity_id) + CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) ); CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint) diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java index 8da1727631..2a99e868a9 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java @@ -27,7 +27,6 @@ import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.support.AnnotationConfigContextLoader; -import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; @@ -38,10 +37,7 @@ import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTra import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.HasId; -import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UUIDBased; -import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.audit.AuditLogLevelFilter; @@ -61,7 +57,7 @@ import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; -import org.thingsboard.server.dao.usagerecord.UsageRecordService; +import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; @@ -87,7 +83,7 @@ public abstract class AbstractServiceTest { protected UserService userService; @Autowired - protected UsageRecordService usageRecordService; + protected ApiUsageStateService apiUsageStateService; @Autowired protected AdminSettingsService adminSettingsService; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseUsageRecordServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java similarity index 68% rename from dao/src/test/java/org/thingsboard/server/dao/service/BaseUsageRecordServiceTest.java rename to dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java index 28e0a6d54a..5f56506997 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseUsageRecordServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java @@ -19,17 +19,12 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.UsageRecord; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.service.AbstractServiceTest; -import org.thingsboard.server.dao.service.DaoSqlTest; -public abstract class BaseUsageRecordServiceTest extends AbstractServiceTest { +public abstract class BaseApiUsageStateServiceTest extends AbstractServiceTest { private TenantId tenantId; @@ -48,9 +43,9 @@ public abstract class BaseUsageRecordServiceTest extends AbstractServiceTest { } @Test - public void testFindUsageRecordByTenantId() { - UsageRecord usageRecord = usageRecordService.findTenantUsageRecord(tenantId); - Assert.assertNotNull(usageRecord); + public void testFindApiUsageStateByTenantId() { + ApiUsageState apiUsageState = apiUsageStateService.findTenantApiUsageState(tenantId); + Assert.assertNotNull(apiUsageState); } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/sql/UsageRecordServiceSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/sql/ApiUsageStateServiceSqlTest.java similarity index 77% rename from dao/src/test/java/org/thingsboard/server/dao/service/sql/UsageRecordServiceSqlTest.java rename to dao/src/test/java/org/thingsboard/server/dao/service/sql/ApiUsageStateServiceSqlTest.java index 8275b92044..bbaae0a5a9 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/sql/UsageRecordServiceSqlTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/sql/ApiUsageStateServiceSqlTest.java @@ -15,10 +15,9 @@ */ package org.thingsboard.server.dao.service.sql; -import org.thingsboard.server.dao.service.BaseUsageRecordServiceTest; -import org.thingsboard.server.dao.service.BaseUserServiceTest; +import org.thingsboard.server.dao.service.BaseApiUsageStateServiceTest; import org.thingsboard.server.dao.service.DaoSqlTest; @DaoSqlTest -public class UsageRecordServiceSqlTest extends BaseUsageRecordServiceTest { +public class ApiUsageStateServiceSqlTest extends BaseApiUsageStateServiceTest { } diff --git a/dao/src/test/resources/sql/hsql/drop-all-tables.sql b/dao/src/test/resources/sql/hsql/drop-all-tables.sql index 13c1eba943..b5629b0560 100644 --- a/dao/src/test/resources/sql/hsql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/hsql/drop-all-tables.sql @@ -27,5 +27,5 @@ DROP TABLE IF EXISTS rule_chain; DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS oauth2_client_registration_info; DROP TABLE IF EXISTS oauth2_client_registration_template; -DROP TABLE IF EXISTS usage_record; +DROP TABLE IF EXISTS api_usage_state; DROP FUNCTION IF EXISTS to_uuid; diff --git a/dao/src/test/resources/sql/psql/drop-all-tables.sql b/dao/src/test/resources/sql/psql/drop-all-tables.sql index b8ab69c758..34985883d7 100644 --- a/dao/src/test/resources/sql/psql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/psql/drop-all-tables.sql @@ -28,4 +28,4 @@ DROP TABLE IF EXISTS tb_schema_settings; DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS oauth2_client_registration_info; DROP TABLE IF EXISTS oauth2_client_registration_template; -DROP TABLE IF EXISTS usage_record; \ No newline at end of file +DROP TABLE IF EXISTS api_usage_state; \ No newline at end of file diff --git a/dao/src/test/resources/sql/timescale/drop-all-tables.sql b/dao/src/test/resources/sql/timescale/drop-all-tables.sql index 4333bc33ee..211cebb2ad 100644 --- a/dao/src/test/resources/sql/timescale/drop-all-tables.sql +++ b/dao/src/test/resources/sql/timescale/drop-all-tables.sql @@ -28,4 +28,4 @@ DROP TABLE IF EXISTS tb_schema_settings; DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS oauth2_client_registration_info; DROP TABLE IF EXISTS oauth2_client_registration_template; -DROP TABLE IF EXISTS usage_record; \ No newline at end of file +DROP TABLE IF EXISTS api_usage_state; \ No newline at end of file From ce591f9b38deebb6a0ef99d7ca53af98e934eb8a Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 20 Oct 2020 08:50:31 +0300 Subject: [PATCH 228/463] Refactoring --- .../org/thingsboard/mqtt/MqttClientImpl.java | 47 ++++++++++++++----- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java index c493f2b860..f72b8d0f4e 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java @@ -19,18 +19,39 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableSet; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; -import io.netty.channel.*; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; -import io.netty.handler.codec.mqtt.*; +import io.netty.handler.codec.mqtt.MqttDecoder; +import io.netty.handler.codec.mqtt.MqttEncoder; +import io.netty.handler.codec.mqtt.MqttFixedHeader; +import io.netty.handler.codec.mqtt.MqttMessage; +import io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader; +import io.netty.handler.codec.mqtt.MqttMessageType; +import io.netty.handler.codec.mqtt.MqttPublishMessage; +import io.netty.handler.codec.mqtt.MqttPublishVariableHeader; +import io.netty.handler.codec.mqtt.MqttQoS; +import io.netty.handler.codec.mqtt.MqttSubscribeMessage; +import io.netty.handler.codec.mqtt.MqttSubscribePayload; +import io.netty.handler.codec.mqtt.MqttTopicSubscription; +import io.netty.handler.codec.mqtt.MqttUnsubscribeMessage; +import io.netty.handler.codec.mqtt.MqttUnsubscribePayload; import io.netty.handler.ssl.SslContext; import io.netty.handler.timeout.IdleStateHandler; -import io.netty.util.collection.IntObjectHashMap; import io.netty.util.concurrent.DefaultPromise; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Promise; -import java.util.*; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -41,11 +62,11 @@ import java.util.concurrent.atomic.AtomicInteger; final class MqttClientImpl implements MqttClient { private final Set serverSubscriptions = new HashSet<>(); - private final IntObjectHashMap pendingServerUnsubscribes = new IntObjectHashMap<>(); - private final IntObjectHashMap qos2PendingIncomingPublishes = new IntObjectHashMap<>(); - private final IntObjectHashMap pendingPublishes = new IntObjectHashMap<>(); + private final ConcurrentHashMap pendingServerUnsubscribes = new ConcurrentHashMap<>(); + private final ConcurrentHashMap qos2PendingIncomingPublishes = new ConcurrentHashMap<>(); + private final ConcurrentHashMap pendingPublishes = new ConcurrentHashMap<>(); private final HashMultimap subscriptions = HashMultimap.create(); - private final IntObjectHashMap pendingSubscriptions = new IntObjectHashMap<>(); + private final ConcurrentHashMap pendingSubscriptions = new ConcurrentHashMap<>(); private final Set pendingSubscribeTopics = new HashSet<>(); private final HashMultimap handlerToSubscribtion = HashMultimap.create(); private final AtomicInteger nextMessageId = new AtomicInteger(1); @@ -355,6 +376,8 @@ final class MqttClientImpl implements MqttClient { pendingPublish.getFuture().setSuccess(null); //We don't get an ACK for QOS 0 } else if (pendingPublish.isSent()) { pendingPublish.startPublishRetransmissionTimer(this.eventLoop.next(), this::sendAndFlushPacket); + } else { + this.pendingPublishes.remove(pendingPublish.getMessageId()); } return future; } @@ -466,7 +489,7 @@ final class MqttClientImpl implements MqttClient { } } - IntObjectHashMap getPendingSubscriptions() { + ConcurrentHashMap getPendingSubscriptions() { return pendingSubscriptions; } @@ -486,15 +509,15 @@ final class MqttClientImpl implements MqttClient { return serverSubscriptions; } - IntObjectHashMap getPendingServerUnsubscribes() { + ConcurrentHashMap getPendingServerUnsubscribes() { return pendingServerUnsubscribes; } - IntObjectHashMap getPendingPublishes() { + ConcurrentHashMap getPendingPublishes() { return pendingPublishes; } - IntObjectHashMap getQos2PendingIncomingPublishes() { + ConcurrentHashMap getQos2PendingIncomingPublishes() { return qos2PendingIncomingPublishes; } From cbcb050e81d4004c4369350551def65dd66e92fa Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 20 Oct 2020 09:14:58 +0300 Subject: [PATCH 229/463] Refactoring --- .../components/widget/lib/alarms-table-widget.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts index 58176a48d1..3e4b37dc79 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts @@ -371,7 +371,8 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, this.subscription.alarmSource.dataKeys.forEach((alarmDataKey) => { const dataKey: EntityColumn = deepClone(alarmDataKey) as EntityColumn; dataKey.entityKey = dataKeyToEntityKey(alarmDataKey); - dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label); + dataKey.label = this.utils.customTranslation(dataKey.label, dataKey.label); + dataKey.title = dataKey.label; dataKey.def = 'def' + this.columns.length; const keySettings: TableWidgetDataKeySettings = dataKey.settings; if (dataKey.type === DataKeyType.alarm && !isDefined(keySettings.columnWidth)) { From ef29aba24c935dc963382a5103e6c04944f6a486 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 20 Oct 2020 10:11:26 +0300 Subject: [PATCH 230/463] created usage stats queue consumer and producer --- .../queue/DefaultTbCoreConsumerService.java | 2 +- .../DefaultTbUsageStatsService.java | 15 ++++++ .../usagestats/TbUsageStatsService.java | 15 ++++++ .../src/main/resources/thingsboard.yml | 1 + .../provider/AwsSqsMonolithQueueFactory.java | 45 ++++++++++------ .../provider/AwsSqsTbCoreQueueFactory.java | 12 +++++ .../AwsSqsTbRuleEngineQueueFactory.java | 6 +++ .../provider/AwsSqsTransportQueueFactory.java | 6 +++ .../InMemoryMonolithQueueFactory.java | 12 +++++ .../InMemoryTbTransportQueueFactory.java | 7 +++ .../provider/KafkaMonolithQueueFactory.java | 23 ++++++++ .../provider/KafkaTbCoreQueueFactory.java | 23 ++++++++ .../KafkaTbRuleEngineQueueFactory.java | 11 ++++ .../KafkaTbTransportQueueFactory.java | 11 ++++ .../provider/PubSubMonolithQueueFactory.java | 12 +++++ .../provider/PubSubTbCoreQueueFactory.java | 12 +++++ .../PubSubTbRuleEngineQueueFactory.java | 6 +++ .../provider/PubSubTransportQueueFactory.java | 6 +++ .../RabbitMqMonolithQueueFactory.java | 52 +++++++++++++------ .../provider/RabbitMqTbCoreQueueFactory.java | 12 +++++ .../RabbitMqTbRuleEngineQueueFactory.java | 6 +++ .../RabbitMqTransportQueueFactory.java | 6 +++ .../ServiceBusMonolithQueueFactory.java | 12 +++++ .../ServiceBusTbCoreQueueFactory.java | 23 ++++++-- .../ServiceBusTbRuleEngineQueueFactory.java | 17 ++++-- .../ServiceBusTransportQueueFactory.java | 36 ++++++++----- .../queue/provider/TbCoreQueueFactory.java | 7 +-- .../TbUsageStatsClientQueueFactory.java | 5 +- .../queue/settings/TbQueueCoreSettings.java | 3 ++ 29 files changed, 337 insertions(+), 67 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 3f03b65416..e806eccf66 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java b/application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java index 6611507531..bf20419670 100644 --- a/application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java +++ b/application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.service.usagestats; import org.springframework.stereotype.Service; diff --git a/application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java b/application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java index 35a7051896..e735c5e2e2 100644 --- a/application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java +++ b/application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.service.usagestats; import org.thingsboard.server.common.msg.queue.TbCallback; diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index deb28ffd99..f0f4b9dd54 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -673,6 +673,7 @@ queue: poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" partitions: "${TB_QUEUE_CORE_PARTITIONS:10}" pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:2000}" + usage-stats-topic: "${TB_QUEUE_US_TOPIC:tb_usage_stats}" stats: enabled: "${TB_QUEUE_CORE_STATS_ENABLED:true}" print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:60000}" diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java index c1d724c207..0299720005 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java @@ -22,7 +22,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsRequest; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsResponse; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.*; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueProducer; @@ -91,64 +91,64 @@ public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng } @Override - public TbQueueProducer> createTransportNotificationsMsgProducer() { + public TbQueueProducer> createTransportNotificationsMsgProducer() { return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, transportNotificationSettings.getNotificationsTopic()); } @Override - public TbQueueProducer> createRuleEngineMsgProducer() { + public TbQueueProducer> createRuleEngineMsgProducer() { return new TbAwsSqsProducerTemplate<>(ruleEngineAdmin, sqsSettings, ruleEngineSettings.getTopic()); } @Override - public TbQueueProducer> createRuleEngineNotificationsMsgProducer() { + public TbQueueProducer> createRuleEngineNotificationsMsgProducer() { return new TbAwsSqsProducerTemplate<>(ruleEngineAdmin, sqsSettings, ruleEngineSettings.getTopic()); } @Override - public TbQueueProducer> createTbCoreMsgProducer() { + public TbQueueProducer> createTbCoreMsgProducer() { return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getTopic()); } @Override - public TbQueueProducer> createTbCoreNotificationsMsgProducer() { + public TbQueueProducer> createTbCoreNotificationsMsgProducer() { return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getTopic()); } @Override - public TbQueueConsumer> createToRuleEngineMsgConsumer(TbRuleEngineQueueConfiguration configuration) { + public TbQueueConsumer> createToRuleEngineMsgConsumer(TbRuleEngineQueueConfiguration configuration) { return new TbAwsSqsConsumerTemplate<>(ruleEngineAdmin, sqsSettings, ruleEngineSettings.getTopic(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToRuleEngineMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueConsumer> createToRuleEngineNotificationsMsgConsumer() { + public TbQueueConsumer> createToRuleEngineNotificationsMsgConsumer() { return new TbAwsSqsConsumerTemplate<>(notificationAdmin, sqsSettings, partitionService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, serviceInfoProvider.getServiceId()).getFullTopicName(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToRuleEngineNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueConsumer> createToCoreMsgConsumer() { + public TbQueueConsumer> createToCoreMsgConsumer() { return new TbAwsSqsConsumerTemplate<>(coreAdmin, sqsSettings, coreSettings.getTopic(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToCoreMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueConsumer> createToCoreNotificationsMsgConsumer() { + public TbQueueConsumer> createToCoreNotificationsMsgConsumer() { return new TbAwsSqsConsumerTemplate<>(notificationAdmin, sqsSettings, partitionService.getNotificationsTopic(ServiceType.TB_CORE, serviceInfoProvider.getServiceId()).getFullTopicName(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToCoreNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueConsumer> createTransportApiRequestConsumer() { + public TbQueueConsumer> createTransportApiRequestConsumer() { return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, transportApiSettings.getRequestsTopic(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueProducer> createTransportApiResponseProducer() { + public TbQueueProducer> createTransportApiResponseProducer() { return new TbAwsSqsProducerTemplate<>(transportApiAdmin, sqsSettings, transportApiSettings.getResponsesTopic()); } @@ -175,6 +175,17 @@ public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng return builder.build(); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getUsageStatsTopic()); + } + + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, coreSettings.getUsageStatsTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java index 4889a7182d..4625b9d4fc 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java @@ -21,6 +21,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; @@ -163,6 +164,17 @@ public class AwsSqsTbCoreQueueFactory implements TbCoreQueueFactory { return builder.build(); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getUsageStatsTopic()); + } + + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, coreSettings.getUsageStatsTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java index cf43bc5fe4..851b8cc40e 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java @@ -143,6 +143,11 @@ public class AwsSqsTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory return builder.build(); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { @@ -158,4 +163,5 @@ public class AwsSqsTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory notificationAdmin.destroy(); } } + } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTransportQueueFactory.java index 8106ee084b..103711f36b 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTransportQueueFactory.java @@ -18,6 +18,7 @@ package org.thingsboard.server.queue.provider; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -109,6 +110,11 @@ public class AwsSqsTransportQueueFactory implements TbTransportQueueFactory { msg -> new TbProtoQueueMsg<>(msg.getKey(), ToTransportMsg.parseFrom(msg.getData()), msg.getHeaders())); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java index 8ebda6e7b9..f7a47ecf3a 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java @@ -125,8 +125,20 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE return null; } + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + return new InMemoryTbQueueConsumer<>(coreSettings.getUsageStatsTopic()); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new InMemoryTbQueueProducer<>(coreSettings.getUsageStatsTopic()); + } + @Scheduled(fixedRateString = "${queue.in_memory.stats.print-interval-ms:60000}") private void printInMemoryStats() { storage.printStats(); } + + } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryTbTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryTbTransportQueueFactory.java index 9e8e326f0d..9ddf72534c 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryTbTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryTbTransportQueueFactory.java @@ -18,6 +18,7 @@ package org.thingsboard.server.queue.provider; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -96,4 +97,10 @@ public class InMemoryTbTransportQueueFactory implements TbTransportQueueFactory public TbQueueConsumer> createTransportNotificationsConsumer() { return new InMemoryTbQueueConsumer<>(transportNotificationSettings.getNotificationsTopic() + "." + serviceInfoProvider.getServiceId()); } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new InMemoryTbQueueProducer<>(coreSettings.getUsageStatsTopic()); + } + } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java index 31ef4d3e45..538d91aa0f 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java @@ -26,6 +26,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMs import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -249,6 +250,28 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi return builder.build(); } + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> consumerBuilder = TbKafkaConsumerTemplate.builder(); + consumerBuilder.settings(kafkaSettings); + consumerBuilder.topic(coreSettings.getUsageStatsTopic()); + consumerBuilder.clientId("monolith-us-consumer-" + serviceInfoProvider.getServiceId()); + consumerBuilder.groupId("monolith-us-consumer-" + serviceInfoProvider.getServiceId()); + consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + consumerBuilder.admin(coreAdmin); + return consumerBuilder.build(); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); + requestBuilder.settings(kafkaSettings); + requestBuilder.clientId("monolith-us-producer-" + serviceInfoProvider.getServiceId()); + requestBuilder.defaultTopic(coreSettings.getUsageStatsTopic()); + requestBuilder.admin(coreAdmin); + return requestBuilder.build(); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java index bf2ec6751e..5e1b398e91 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java @@ -26,6 +26,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMs import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -219,6 +220,28 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { return builder.build(); } + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> consumerBuilder = TbKafkaConsumerTemplate.builder(); + consumerBuilder.settings(kafkaSettings); + consumerBuilder.topic(coreSettings.getUsageStatsTopic()); + consumerBuilder.clientId("tb-core-us-consumer-" + serviceInfoProvider.getServiceId()); + consumerBuilder.groupId("tb-core-us-consumer-" + serviceInfoProvider.getServiceId()); + consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + consumerBuilder.admin(coreAdmin); + return consumerBuilder.build(); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); + requestBuilder.settings(kafkaSettings); + requestBuilder.clientId("tb-core-us-producer-" + serviceInfoProvider.getServiceId()); + requestBuilder.defaultTopic(coreSettings.getUsageStatsTopic()); + requestBuilder.admin(coreAdmin); + return requestBuilder.build(); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java index dc44caf3f0..118f0f3316 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java @@ -26,6 +26,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMs import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueProducer; @@ -192,6 +193,16 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory { return builder.build(); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); + requestBuilder.settings(kafkaSettings); + requestBuilder.clientId("tb-rule-engine-us-producer-" + serviceInfoProvider.getServiceId()); + requestBuilder.defaultTopic(coreSettings.getUsageStatsTopic()); + requestBuilder.admin(coreAdmin); + return requestBuilder.build(); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbTransportQueueFactory.java index 1cf8bca4b2..7961bb97bb 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbTransportQueueFactory.java @@ -21,6 +21,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -138,6 +139,16 @@ public class KafkaTbTransportQueueFactory implements TbTransportQueueFactory { return responseBuilder.build(); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); + requestBuilder.settings(kafkaSettings); + requestBuilder.clientId("transport-node-us-producer-" + serviceInfoProvider.getServiceId()); + requestBuilder.defaultTopic(coreSettings.getUsageStatsTopic()); + requestBuilder.admin(coreAdmin); + return requestBuilder.build(); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java index 29a60af1e2..d7c7542626 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java @@ -27,6 +27,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMs import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -183,6 +184,17 @@ public class PubSubMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng return builder.build(); } + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getUsageStatsTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java index 7d85b5c3ba..71a1ceaf9b 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java @@ -26,6 +26,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMs import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -157,6 +158,17 @@ public class PubSubTbCoreQueueFactory implements TbCoreQueueFactory { return builder.build(); } + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getUsageStatsTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbRuleEngineQueueFactory.java index a6105fea0c..ad679039e4 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbRuleEngineQueueFactory.java @@ -26,6 +26,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMs import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueProducer; @@ -146,6 +147,11 @@ public class PubSubTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory return builder.build(); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTransportQueueFactory.java index 72665c5163..efde055271 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTransportQueueFactory.java @@ -21,6 +21,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -114,6 +115,11 @@ public class PubSubTransportQueueFactory implements TbTransportQueueFactory { msg -> new TbProtoQueueMsg<>(msg.getKey(), ToTransportMsg.parseFrom(msg.getData()), msg.getHeaders())); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java index 1f812ea13c..e6eff652be 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java @@ -22,7 +22,14 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsRequest; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsResponse; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueProducer; @@ -91,64 +98,64 @@ public class RabbitMqMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE } @Override - public TbQueueProducer> createTransportNotificationsMsgProducer() { + public TbQueueProducer> createTransportNotificationsMsgProducer() { return new TbRabbitMqProducerTemplate<>(notificationAdmin, rabbitMqSettings, transportNotificationSettings.getNotificationsTopic()); } @Override - public TbQueueProducer> createRuleEngineMsgProducer() { + public TbQueueProducer> createRuleEngineMsgProducer() { return new TbRabbitMqProducerTemplate<>(ruleEngineAdmin, rabbitMqSettings, ruleEngineSettings.getTopic()); } @Override - public TbQueueProducer> createRuleEngineNotificationsMsgProducer() { + public TbQueueProducer> createRuleEngineNotificationsMsgProducer() { return new TbRabbitMqProducerTemplate<>(ruleEngineAdmin, rabbitMqSettings, ruleEngineSettings.getTopic()); } @Override - public TbQueueProducer> createTbCoreMsgProducer() { + public TbQueueProducer> createTbCoreMsgProducer() { return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getTopic()); } @Override - public TbQueueProducer> createTbCoreNotificationsMsgProducer() { + public TbQueueProducer> createTbCoreNotificationsMsgProducer() { return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getTopic()); } @Override - public TbQueueConsumer> createToRuleEngineMsgConsumer(TbRuleEngineQueueConfiguration configuration) { + public TbQueueConsumer> createToRuleEngineMsgConsumer(TbRuleEngineQueueConfiguration configuration) { return new TbRabbitMqConsumerTemplate<>(ruleEngineAdmin, rabbitMqSettings, ruleEngineSettings.getTopic(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToRuleEngineMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueConsumer> createToRuleEngineNotificationsMsgConsumer() { + public TbQueueConsumer> createToRuleEngineNotificationsMsgConsumer() { return new TbRabbitMqConsumerTemplate<>(notificationAdmin, rabbitMqSettings, partitionService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, serviceInfoProvider.getServiceId()).getFullTopicName(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToRuleEngineNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueConsumer> createToCoreMsgConsumer() { + public TbQueueConsumer> createToCoreMsgConsumer() { return new TbRabbitMqConsumerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getTopic(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToCoreMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueConsumer> createToCoreNotificationsMsgConsumer() { + public TbQueueConsumer> createToCoreNotificationsMsgConsumer() { return new TbRabbitMqConsumerTemplate<>(notificationAdmin, rabbitMqSettings, partitionService.getNotificationsTopic(ServiceType.TB_CORE, serviceInfoProvider.getServiceId()).getFullTopicName(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToCoreNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueConsumer> createTransportApiRequestConsumer() { + public TbQueueConsumer> createTransportApiRequestConsumer() { return new TbRabbitMqConsumerTemplate<>(transportApiAdmin, rabbitMqSettings, transportApiSettings.getRequestsTopic(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueProducer> createTransportApiResponseProducer() { + public TbQueueProducer> createTransportApiResponseProducer() { return new TbRabbitMqProducerTemplate<>(transportApiAdmin, rabbitMqSettings, transportApiSettings.getResponsesTopic()); } @@ -175,6 +182,17 @@ public class RabbitMqMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE return builder.build(); } + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + return new TbRabbitMqConsumerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getUsageStatsTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java index 65d582c4bc..e3064dd62d 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java @@ -26,6 +26,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMs import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -163,6 +164,17 @@ public class RabbitMqTbCoreQueueFactory implements TbCoreQueueFactory { return builder.build(); } + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + return new TbRabbitMqConsumerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getUsageStatsTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbRuleEngineQueueFactory.java index 26abf78164..a1097049db 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbRuleEngineQueueFactory.java @@ -26,6 +26,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMs import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueProducer; @@ -144,6 +145,11 @@ public class RabbitMqTbRuleEngineQueueFactory implements TbRuleEngineQueueFactor return builder.build(); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTransportQueueFactory.java index 03cd6eb557..b28a2cb7e2 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTransportQueueFactory.java @@ -21,6 +21,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -111,6 +112,11 @@ public class RabbitMqTransportQueueFactory implements TbTransportQueueFactory { msg -> new TbProtoQueueMsg<>(msg.getKey(), ToTransportMsg.parseFrom(msg.getData()), msg.getHeaders())); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java index 3db6496b7d..a205f9b061 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java @@ -26,6 +26,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMs import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -180,6 +181,17 @@ public class ServiceBusMonolithQueueFactory implements TbCoreQueueFactory, TbRul return builder.build(); } + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + return new TbServiceBusConsumerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getUsageStatsTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java index a3ba577f06..f00aee331b 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java @@ -21,10 +21,12 @@ import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -101,7 +103,7 @@ public class ServiceBusTbCoreQueueFactory implements TbCoreQueueFactory { } @Override - public TbQueueProducer> createRuleEngineNotificationsMsgProducer() { + public TbQueueProducer> createRuleEngineNotificationsMsgProducer() { return new TbServiceBusProducerTemplate<>(ruleEngineAdmin, serviceBusSettings, ruleEngineSettings.getTopic()); } @@ -111,7 +113,7 @@ public class ServiceBusTbCoreQueueFactory implements TbCoreQueueFactory { } @Override - public TbQueueProducer> createTbCoreNotificationsMsgProducer() { + public TbQueueProducer> createTbCoreNotificationsMsgProducer() { return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getTopic()); } @@ -122,10 +124,10 @@ public class ServiceBusTbCoreQueueFactory implements TbCoreQueueFactory { } @Override - public TbQueueConsumer> createToCoreNotificationsMsgConsumer() { + public TbQueueConsumer> createToCoreNotificationsMsgConsumer() { return new TbServiceBusConsumerTemplate<>(notificationAdmin, serviceBusSettings, partitionService.getNotificationsTopic(ServiceType.TB_CORE, serviceInfoProvider.getServiceId()).getFullTopicName(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToCoreNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override @@ -162,6 +164,17 @@ public class ServiceBusTbCoreQueueFactory implements TbCoreQueueFactory { return builder.build(); } + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + return new TbServiceBusConsumerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getUsageStatsTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbRuleEngineQueueFactory.java index e4f6dbfbb4..40a183a254 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbRuleEngineQueueFactory.java @@ -21,10 +21,12 @@ import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueProducer; @@ -93,7 +95,7 @@ public class ServiceBusTbRuleEngineQueueFactory implements TbRuleEngineQueueFact } @Override - public TbQueueProducer> createRuleEngineNotificationsMsgProducer() { + public TbQueueProducer> createRuleEngineNotificationsMsgProducer() { return new TbServiceBusProducerTemplate<>(ruleEngineAdmin, serviceBusSettings, ruleEngineSettings.getTopic()); } @@ -103,7 +105,7 @@ public class ServiceBusTbRuleEngineQueueFactory implements TbRuleEngineQueueFact } @Override - public TbQueueProducer> createTbCoreNotificationsMsgProducer() { + public TbQueueProducer> createTbCoreNotificationsMsgProducer() { return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getTopic()); } @@ -114,10 +116,10 @@ public class ServiceBusTbRuleEngineQueueFactory implements TbRuleEngineQueueFact } @Override - public TbQueueConsumer> createToRuleEngineNotificationsMsgConsumer() { + public TbQueueConsumer> createToRuleEngineNotificationsMsgConsumer() { return new TbServiceBusConsumerTemplate<>(notificationAdmin, serviceBusSettings, partitionService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, serviceInfoProvider.getServiceId()).getFullTopicName(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToRuleEngineNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override @@ -143,6 +145,11 @@ public class ServiceBusTbRuleEngineQueueFactory implements TbRuleEngineQueueFact return builder.build(); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTransportQueueFactory.java index 1e900f37e5..4781d46583 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTransportQueueFactory.java @@ -18,19 +18,24 @@ package org.thingsboard.server.queue.provider; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.TbQueueRequestTemplate; import org.thingsboard.server.queue.azure.servicebus.TbServiceBusAdmin; +import org.thingsboard.server.queue.azure.servicebus.TbServiceBusConsumerTemplate; +import org.thingsboard.server.queue.azure.servicebus.TbServiceBusProducerTemplate; import org.thingsboard.server.queue.azure.servicebus.TbServiceBusQueueConfigs; +import org.thingsboard.server.queue.azure.servicebus.TbServiceBusSettings; import org.thingsboard.server.queue.common.DefaultTbQueueRequestTemplate; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; -import org.thingsboard.server.queue.azure.servicebus.TbServiceBusConsumerTemplate; -import org.thingsboard.server.queue.azure.servicebus.TbServiceBusProducerTemplate; -import org.thingsboard.server.queue.azure.servicebus.TbServiceBusSettings; import org.thingsboard.server.queue.settings.TbQueueCoreSettings; import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; @@ -69,17 +74,17 @@ public class ServiceBusTransportQueueFactory implements TbTransportQueueFactory } @Override - public TbQueueRequestTemplate, TbProtoQueueMsg> createTransportApiRequestTemplate() { - TbQueueProducer> producerTemplate = + public TbQueueRequestTemplate, TbProtoQueueMsg> createTransportApiRequestTemplate() { + TbQueueProducer> producerTemplate = new TbServiceBusProducerTemplate<>(transportApiAdmin, serviceBusSettings, transportApiSettings.getRequestsTopic()); - TbQueueConsumer> consumerTemplate = + TbQueueConsumer> consumerTemplate = new TbServiceBusConsumerTemplate<>(transportApiAdmin, serviceBusSettings, transportApiSettings.getResponsesTopic() + "." + serviceInfoProvider.getServiceId(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.TransportApiResponseMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiResponseMsg.parseFrom(msg.getData()), msg.getHeaders())); DefaultTbQueueRequestTemplate.DefaultTbQueueRequestTemplateBuilder - , TbProtoQueueMsg> templateBuilder = DefaultTbQueueRequestTemplate.builder(); + , TbProtoQueueMsg> templateBuilder = DefaultTbQueueRequestTemplate.builder(); templateBuilder.queueAdmin(transportApiAdmin); templateBuilder.requestTemplate(producerTemplate); templateBuilder.responseTemplate(consumerTemplate); @@ -90,20 +95,25 @@ public class ServiceBusTransportQueueFactory implements TbTransportQueueFactory } @Override - public TbQueueProducer> createRuleEngineMsgProducer() { + public TbQueueProducer> createRuleEngineMsgProducer() { return new TbServiceBusProducerTemplate<>(transportApiAdmin, serviceBusSettings, transportApiSettings.getRequestsTopic()); } @Override - public TbQueueProducer> createTbCoreMsgProducer() { + public TbQueueProducer> createTbCoreMsgProducer() { return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getTopic()); } @Override - public TbQueueConsumer> createTransportNotificationsConsumer() { + public TbQueueConsumer> createTransportNotificationsConsumer() { return new TbServiceBusConsumerTemplate<>(notificationAdmin, serviceBusSettings, transportNotificationSettings.getNotificationsTopic() + "." + serviceInfoProvider.getServiceId(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToTransportMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToTransportMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getUsageStatsTopic()); } @PreDestroy diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java index d009757997..7c19a1ba2f 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java @@ -16,12 +16,12 @@ package org.thingsboard.server.queue.provider; import org.thingsboard.server.gen.js.JsInvokeProtos; -import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueConsumer; @@ -83,10 +83,7 @@ public interface TbCoreQueueFactory extends TbUsageStatsClientQueueFactory { * * @return */ - default TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { - //TODO: implement - return null; - } + TbQueueConsumer> createToUsageStatsServiceMsgConsumer(); /** * Used to consume high priority messages by TB Core Service diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbUsageStatsClientQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbUsageStatsClientQueueFactory.java index 1811877338..f2ce1a9fe5 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbUsageStatsClientQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbUsageStatsClientQueueFactory.java @@ -21,9 +21,6 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; public interface TbUsageStatsClientQueueFactory { - default TbQueueProducer> createToUsageStatsServiceMsgProducer() { - //TODO: implement - return null; - } + TbQueueProducer> createToUsageStatsServiceMsgProducer(); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java index 1c260c754d..d406139dbb 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java @@ -26,6 +26,9 @@ public class TbQueueCoreSettings { @Value("${queue.core.topic}") private String topic; + @Value("${queue.core.usage-stats-topic}") + private String usageStatsTopic; + @Value("${queue.core.partitions}") private int partitions; } From cc2446442d7423a7038cb3a95e239e0a4e7c276d Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 20 Oct 2020 12:00:35 +0300 Subject: [PATCH 231/463] Api Usage Stats flow --- .../DefaultTbApiUsageStateService.java | 105 ++++++++++++++++++ .../apiusage/DefaultTbUsageStatsService.java | 30 ----- ...rvice.java => TbApiUsageStateService.java} | 2 +- .../service/apiusage/TenantApiUsageState.java | 47 ++++++++ .../queue/DefaultTbCoreConsumerService.java | 6 +- .../usagestats/DefaultTbUsageStatsClient.java | 47 ++++---- .../service/DefaultTransportService.java | 36 +++++- 7 files changed, 217 insertions(+), 56 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbUsageStatsService.java rename application/src/main/java/org/thingsboard/server/service/apiusage/{TbUsageStatsService.java => TbApiUsageStateService.java} (95%) create mode 100644 application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java new file mode 100644 index 0000000000..8a50c9d62b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -0,0 +1,105 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.apiusage; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.common.msg.tools.SchedulerUtils; +import org.thingsboard.server.dao.timeseries.TimeseriesService; +import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.util.TbCoreComponent; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; + +@Slf4j +@TbCoreComponent +@Service +public class DefaultTbApiUsageStateService implements TbApiUsageStateService { + + private final ApiUsageStateService apiUsageStateService; + private final TimeseriesService tsService; + private final ZoneId zoneId; + private final Map tenantStates = new ConcurrentHashMap<>(); + + public DefaultTbApiUsageStateService(ApiUsageStateService apiUsageStateService, TimeseriesService tsService) { + this.apiUsageStateService = apiUsageStateService; + this.tsService = tsService; + this.zoneId = SchedulerUtils.getZoneId("UTC"); + } + + @Override + public void process(TbProtoQueueMsg msg, TbCallback callback) { + ToUsageStatsServiceMsg statsMsg = msg.getValue(); + TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB())); + TenantApiUsageState tenantState = getOrFetchState(tenantId); + long ts = tenantState.getCurrentMonthTs(); + List updatedEntries = new ArrayList<>(ApiUsageRecordKey.values().length); + for (UsageStatsKVProto kvProto : statsMsg.getValuesList()) { + ApiUsageRecordKey recordKey = ApiUsageRecordKey.valueOf(kvProto.getKey()); + long newValue = tenantState.add(recordKey, kvProto.getValue()); + updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.name(), newValue))); + } + tsService.save(tenantId, tenantState.getEntityId(), updatedEntries, 0L); + } + + private TenantApiUsageState getOrFetchState(TenantId tenantId) { + TenantApiUsageState tenantState = tenantStates.get(tenantId); + if (tenantState == null) { + long currentMonthTs = LocalDate.now().withDayOfMonth(1).atStartOfDay(zoneId).toInstant().toEpochMilli(); + ApiUsageState dbStateEntity = apiUsageStateService.findTenantApiUsageState(tenantId); + tenantState = new TenantApiUsageState(currentMonthTs, dbStateEntity.getEntityId()); + try { + List dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getEntityId()).get(); + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { + TsKvEntry keyEntry = null; + for (TsKvEntry tsKvEntry : dbValues) { + if (tsKvEntry.getKey().equals(key.name())) { + keyEntry = tsKvEntry; + break; + } + } + if (keyEntry != null) { + tenantState.put(key, keyEntry.getLongValue().get()); + } else { + tenantState.put(key, 0L); + } + } + tenantStates.put(tenantId, tenantState); + } catch (InterruptedException | ExecutionException e) { + log.warn("[{}] Failed to fetch api usage state from db.", tenantId, e); + } + } + return tenantState; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbUsageStatsService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbUsageStatsService.java deleted file mode 100644 index e2679d6433..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbUsageStatsService.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.apiusage; - -import org.springframework.stereotype.Service; -import org.thingsboard.server.common.msg.queue.TbCallback; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.common.TbProtoQueueMsg; - -@Service -public class DefaultTbUsageStatsService implements TbUsageStatsService { - @Override - public void process(TbProtoQueueMsg msg, TbCallback callback) { - - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TbUsageStatsService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java similarity index 95% rename from application/src/main/java/org/thingsboard/server/service/apiusage/TbUsageStatsService.java rename to application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java index 3c54479eaf..b5aeedeb06 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TbUsageStatsService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java @@ -19,7 +19,7 @@ import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -public interface TbUsageStatsService { +public interface TbApiUsageStateService { void process(TbProtoQueueMsg msg, TbCallback callback); } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java new file mode 100644 index 0000000000..a8be19914b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.apiusage; + +import lombok.Getter; +import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.id.EntityId; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class TenantApiUsageState { + + private final Map values = new ConcurrentHashMap<>(); + @Getter + private final EntityId entityId; + @Getter + private volatile long currentMonthTs; + + public TenantApiUsageState(long currentMonthTs, EntityId entityId) { + this.entityId = entityId; + this.currentMonthTs = currentMonthTs; + } + + public void put(ApiUsageRecordKey key, Long value) { + values.put(key, value); + } + + public long add(ApiUsageRecordKey key, long value) { + long result = values.getOrDefault(key, 0L) + value; + values.put(key, result); + return result; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index a389eefd1a..51fce853db 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -62,7 +62,7 @@ import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; -import org.thingsboard.server.service.apiusage.TbUsageStatsService; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -92,7 +92,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> mainConsumer; private final DeviceStateService stateService; - private final TbUsageStatsService statsService; + private final TbApiUsageStateService statsService; private final TbLocalSubscriptionService localSubscriptionService; private final SubscriptionManagerService subscriptionManagerService; private final TbCoreDeviceRpcService tbCoreDeviceRpcService; @@ -106,7 +106,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService[] values = new ConcurrentMap[ApiUsageRecordKey.values().length]; @@ -69,28 +71,33 @@ public class DefaultTbUsageStatsClient implements TbUsageStatsClient { } private void reportStats() { - ConcurrentMap report = new ConcurrentHashMap<>(); + try { + ConcurrentMap report = new ConcurrentHashMap<>(); - for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { - values[key.ordinal()].forEach(((tenantId, atomicLong) -> { - long value = atomicLong.getAndSet(0); - if (value > 0) { - ToUsageStatsServiceMsg.Builder msgBuilder = report.computeIfAbsent(tenantId, id -> { - ToUsageStatsServiceMsg.Builder msg = ToUsageStatsServiceMsg.newBuilder(); - msg.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); - msg.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); - return msg; - }); - msgBuilder.addValues(UsageStatsKVProto.newBuilder().setKey(key.name()).setValue(value).build()); - } + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { + values[key.ordinal()].forEach(((tenantId, atomicLong) -> { + long value = atomicLong.getAndSet(0); + if (value > 0) { + ToUsageStatsServiceMsg.Builder msgBuilder = report.computeIfAbsent(tenantId, id -> { + ToUsageStatsServiceMsg.Builder msg = ToUsageStatsServiceMsg.newBuilder(); + msg.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); + msg.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); + return msg; + }); + msgBuilder.addValues(UsageStatsKVProto.newBuilder().setKey(key.name()).setValue(value).build()); + } + })); + } + + report.forEach(((tenantId, builder) -> { + //TODO: figure out how to minimize messages into the queue. Maybe group by 100s of messages? + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId); + msgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), builder.build()), null); })); + log.info("Report statistics for: {} tenants", report.size()); + } catch (Exception e) { + log.warn("Failed to report statistics: ", e); } - - report.forEach(((tenantId, builder) -> { - //TODO: figure out how to minimize messages into the queue. Maybe group by 100s of messages? - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId); - msgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), builder.build()), null); - })); } @Override diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 774a437e39..010a3cd899 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -25,6 +25,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; @@ -78,6 +79,7 @@ import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbTransportQueueFactory; import org.thingsboard.server.queue.scheduler.SchedulerComponent; +import org.thingsboard.server.queue.usagestats.TbUsageStatsClient; import org.thingsboard.server.queue.util.TbTransportComponent; import javax.annotation.PostConstruct; @@ -122,6 +124,7 @@ public class DefaultTransportService implements TransportService { private final StatsFactory statsFactory; private final TransportDeviceProfileCache deviceProfileCache; private final TransportTenantProfileCache tenantProfileCache; + private final TbUsageStatsClient apiUsageStatsClient; private final TransportRateLimitService rateLimitService; private final DataDecodingEncodingService dataDecodingEncodingService; private final SchedulerComponent scheduler; @@ -150,7 +153,8 @@ public class DefaultTransportService implements TransportService { StatsFactory statsFactory, TransportDeviceProfileCache deviceProfileCache, TransportTenantProfileCache tenantProfileCache, - TransportRateLimitService rateLimitService, DataDecodingEncodingService dataDecodingEncodingService, SchedulerComponent scheduler) { + TbUsageStatsClient apiUsageStatsClient, TransportRateLimitService rateLimitService, + DataDecodingEncodingService dataDecodingEncodingService, SchedulerComponent scheduler) { this.serviceInfoProvider = serviceInfoProvider; this.queueProvider = queueProvider; this.producerProvider = producerProvider; @@ -158,6 +162,7 @@ public class DefaultTransportService implements TransportService { this.statsFactory = statsFactory; this.deviceProfileCache = deviceProfileCache; this.tenantProfileCache = tenantProfileCache; + this.apiUsageStatsClient = apiUsageStatsClient; this.rateLimitService = rateLimitService; this.dataDecodingEncodingService = dataDecodingEncodingService; this.scheduler = scheduler; @@ -362,7 +367,7 @@ public class DefaultTransportService implements TransportService { reportActivityInternal(sessionInfo); TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); - MsgPackCallback packCallback = new MsgPackCallback(msg.getTsKvListCount(), callback); + MsgPackCallback packCallback = new MsgPackCallback(msg.getTsKvListCount(), new ApiStatsProxyCallback<>(tenantId, dataPoints, callback)); for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("deviceName", sessionInfo.getDeviceName()); @@ -806,4 +811,31 @@ public class DefaultTransportService implements TransportService { callback.onError(t); } } + + private class ApiStatsProxyCallback implements TransportServiceCallback { + private final TenantId tenantId; + private final int dataPoints; + private final TransportServiceCallback callback; + + public ApiStatsProxyCallback(TenantId tenantId, int dataPoints, TransportServiceCallback callback) { + this.tenantId = tenantId; + this.dataPoints = dataPoints; + this.callback = callback; + } + + @Override + public void onSuccess(T msg) { + try { + apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.MSG_COUNT, 1); + apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.DP_TRANSPORT_COUNT, dataPoints); + } finally { + callback.onSuccess(msg); + } + } + + @Override + public void onError(Throwable e) { + callback.onError(e); + } + } } From 70fe28a5ac172bbc7aa093fdee04e8151118485e Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 20 Oct 2020 13:59:14 +0300 Subject: [PATCH 232/463] added usage stats consumer and producer to queue providers --- .../queue/DefaultTbCoreConsumerService.java | 20 +++++++---- .../common/msg/queue/TopicPartitionInfo.java | 4 +++ .../provider/TbCoreQueueProducerProvider.java | 34 ++++++++++++------ .../provider/TbQueueProducerProvider.java | 8 ++--- .../TbRuleEngineProducerProvider.java | 35 ++++++++++++------- .../TbTransportQueueProducerProvider.java | 25 +++++++------ .../usagestats/DefaultTbUsageStatsClient.java | 2 +- 7 files changed, 81 insertions(+), 47 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 51fce853db..929d5000f0 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -30,28 +30,29 @@ import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.common.stats.StatsFactory; +import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.dao.util.mapping.JacksonUtil; -import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto; import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto; -import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeUpdateProto; -import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeDeleteProto; -import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmDeleteProto; +import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmUpdateProto; +import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeDeleteProto; +import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseProto; import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.provider.TbCoreQueueFactory; -import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.queue.processing.AbstractConsumerService; import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; @@ -62,7 +63,6 @@ import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; -import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -143,6 +143,12 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService tpi.newByTopic(usageStatsConsumer.getTopic())) + .collect(Collectors.toSet())); } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/queue/TopicPartitionInfo.java b/common/message/src/main/java/org/thingsboard/server/common/msg/queue/TopicPartitionInfo.java index bf0fffe404..c2dec02612 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/queue/TopicPartitionInfo.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/queue/TopicPartitionInfo.java @@ -50,6 +50,10 @@ public class TopicPartitionInfo { this.fullTopicName = tmp; } + public TopicPartitionInfo newByTopic(String topic) { + return new TopicPartitionInfo(topic, this.tenantId, this.partition, this.myPartition); + } + public String getTopic() { return topic; } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java index 9fe5de0bcc..e620259960 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java @@ -17,7 +17,12 @@ package org.thingsboard.server.queue.provider; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; @@ -28,11 +33,12 @@ import javax.annotation.PostConstruct; public class TbCoreQueueProducerProvider implements TbQueueProducerProvider { private final TbCoreQueueFactory tbQueueProvider; - private TbQueueProducer> toTransport; - private TbQueueProducer> toRuleEngine; - private TbQueueProducer> toTbCore; - private TbQueueProducer> toRuleEngineNotifications; - private TbQueueProducer> toTbCoreNotifications; + private TbQueueProducer> toTransport; + private TbQueueProducer> toRuleEngine; + private TbQueueProducer> toTbCore; + private TbQueueProducer> toRuleEngineNotifications; + private TbQueueProducer> toTbCoreNotifications; + private TbQueueProducer> toUsageStats; public TbCoreQueueProducerProvider(TbCoreQueueFactory tbQueueProvider) { this.tbQueueProvider = tbQueueProvider; @@ -45,30 +51,36 @@ public class TbCoreQueueProducerProvider implements TbQueueProducerProvider { this.toRuleEngine = tbQueueProvider.createRuleEngineMsgProducer(); this.toRuleEngineNotifications = tbQueueProvider.createRuleEngineNotificationsMsgProducer(); this.toTbCoreNotifications = tbQueueProvider.createTbCoreNotificationsMsgProducer(); + this.toUsageStats = tbQueueProvider.createToUsageStatsServiceMsgProducer(); } @Override - public TbQueueProducer> getTransportNotificationsMsgProducer() { + public TbQueueProducer> getTransportNotificationsMsgProducer() { return toTransport; } @Override - public TbQueueProducer> getRuleEngineMsgProducer() { + public TbQueueProducer> getRuleEngineMsgProducer() { return toRuleEngine; } @Override - public TbQueueProducer> getRuleEngineNotificationsMsgProducer() { + public TbQueueProducer> getRuleEngineNotificationsMsgProducer() { return toRuleEngineNotifications; } @Override - public TbQueueProducer> getTbCoreMsgProducer() { + public TbQueueProducer> getTbCoreMsgProducer() { return toTbCore; } @Override - public TbQueueProducer> getTbCoreNotificationsMsgProducer() { + public TbQueueProducer> getTbCoreNotificationsMsgProducer() { return toTbCoreNotifications; } + + @Override + public TbQueueProducer> getTbUsageStatsMsgProducer() { + return toUsageStats; + } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java index fa20f11960..8c3a0b6565 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java @@ -15,12 +15,12 @@ */ package org.thingsboard.server.queue.provider; -import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; @@ -69,9 +69,5 @@ public interface TbQueueProducerProvider { * * @return */ - default TbQueueProducer> getTbUsageStatsMsgProducer() { - //TODO: implement - return null; - } - + TbQueueProducer> getTbUsageStatsMsgProducer(); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java index 69de7ca3e2..309def1269 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java @@ -17,7 +17,12 @@ package org.thingsboard.server.queue.provider; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; @@ -28,12 +33,12 @@ import javax.annotation.PostConstruct; public class TbRuleEngineProducerProvider implements TbQueueProducerProvider { private final TbRuleEngineQueueFactory tbQueueProvider; - private TbQueueProducer> toTransport; - private TbQueueProducer> toRuleEngine; - private TbQueueProducer> toTbCore; - private TbQueueProducer> toRuleEngineNotifications; - private TbQueueProducer> toTbCoreNotifications; - + private TbQueueProducer> toTransport; + private TbQueueProducer> toRuleEngine; + private TbQueueProducer> toTbCore; + private TbQueueProducer> toRuleEngineNotifications; + private TbQueueProducer> toTbCoreNotifications; + private TbQueueProducer> toUsageStats; public TbRuleEngineProducerProvider(TbRuleEngineQueueFactory tbQueueProvider) { this.tbQueueProvider = tbQueueProvider; @@ -46,30 +51,36 @@ public class TbRuleEngineProducerProvider implements TbQueueProducerProvider { this.toRuleEngine = tbQueueProvider.createRuleEngineMsgProducer(); this.toRuleEngineNotifications = tbQueueProvider.createRuleEngineNotificationsMsgProducer(); this.toTbCoreNotifications = tbQueueProvider.createTbCoreNotificationsMsgProducer(); + this.toUsageStats = tbQueueProvider.createToUsageStatsServiceMsgProducer(); } @Override - public TbQueueProducer> getTransportNotificationsMsgProducer() { + public TbQueueProducer> getTransportNotificationsMsgProducer() { return toTransport; } @Override - public TbQueueProducer> getRuleEngineMsgProducer() { + public TbQueueProducer> getRuleEngineMsgProducer() { return toRuleEngine; } @Override - public TbQueueProducer> getRuleEngineNotificationsMsgProducer() { + public TbQueueProducer> getRuleEngineNotificationsMsgProducer() { return toRuleEngineNotifications; } @Override - public TbQueueProducer> getTbCoreMsgProducer() { + public TbQueueProducer> getTbCoreMsgProducer() { return toTbCore; } @Override - public TbQueueProducer> getTbCoreNotificationsMsgProducer() { + public TbQueueProducer> getTbCoreNotificationsMsgProducer() { return toTbCoreNotifications; } + + @Override + public TbQueueProducer> getTbUsageStatsMsgProducer() { + return toUsageStats; + } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java index 9d11aa727b..dee6508ef4 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java @@ -17,7 +17,12 @@ package org.thingsboard.server.queue.provider; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; @@ -28,9 +33,9 @@ import javax.annotation.PostConstruct; public class TbTransportQueueProducerProvider implements TbQueueProducerProvider { private final TbTransportQueueFactory tbQueueProvider; - private TbQueueProducer> toRuleEngine; - private TbQueueProducer> toTbCore; - private TbQueueProducer> toUsageStats; + private TbQueueProducer> toRuleEngine; + private TbQueueProducer> toTbCore; + private TbQueueProducer> toUsageStats; public TbTransportQueueProducerProvider(TbTransportQueueFactory tbQueueProvider) { this.tbQueueProvider = tbQueueProvider; @@ -44,32 +49,32 @@ public class TbTransportQueueProducerProvider implements TbQueueProducerProvider } @Override - public TbQueueProducer> getTransportNotificationsMsgProducer() { + public TbQueueProducer> getTransportNotificationsMsgProducer() { throw new RuntimeException("Not Implemented! Should not be used by Transport!"); } @Override - public TbQueueProducer> getRuleEngineMsgProducer() { + public TbQueueProducer> getRuleEngineMsgProducer() { return toRuleEngine; } @Override - public TbQueueProducer> getTbCoreMsgProducer() { + public TbQueueProducer> getTbCoreMsgProducer() { return toTbCore; } @Override - public TbQueueProducer> getRuleEngineNotificationsMsgProducer() { + public TbQueueProducer> getRuleEngineNotificationsMsgProducer() { throw new RuntimeException("Not Implemented! Should not be used by Transport!"); } @Override - public TbQueueProducer> getTbCoreNotificationsMsgProducer() { + public TbQueueProducer> getTbCoreNotificationsMsgProducer() { throw new RuntimeException("Not Implemented! Should not be used by Transport!"); } @Override - public TbQueueProducer> getTbUsageStatsMsgProducer() { + public TbQueueProducer> getTbUsageStatsMsgProducer() { return toUsageStats; } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java index 35bb0c2a6a..5a94cd505e 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java @@ -91,7 +91,7 @@ public class DefaultTbUsageStatsClient implements TbUsageStatsClient { report.forEach(((tenantId, builder) -> { //TODO: figure out how to minimize messages into the queue. Maybe group by 100s of messages? - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId); + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).newByTopic(msgProducer.getDefaultTopic()); msgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), builder.build()), null); })); log.info("Report statistics for: {} tenants", report.size()); From 9ec4b776728a8da85bc552a5eefb448133ae83bc Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 20 Oct 2020 15:50:04 +0300 Subject: [PATCH 233/463] Improvements to ApiUsageState persistence --- .../DefaultTbApiUsageStateService.java | 119 ++++++++++++++---- .../apiusage/TbApiUsageStateService.java | 12 +- .../service/apiusage/TenantApiUsageState.java | 49 ++++++-- .../dao/usagerecord/ApiUsageStateService.java | 2 +- .../data/plugin/ComponentLifecycleEvent.java | 2 +- .../common/msg/tools/SchedulerUtils.java | 48 +++++++ .../ApiApiUsageStateServiceImpl.java | 4 +- 7 files changed, 202 insertions(+), 34 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 8a50c9d62b..1b6cdf3cff 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -16,9 +16,11 @@ package org.thingsboard.server.service.apiusage; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; @@ -30,69 +32,144 @@ import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.profile.TbTenantProfileCache; -import java.time.LocalDate; -import java.time.ZoneId; +import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; @Slf4j @TbCoreComponent @Service public class DefaultTbApiUsageStateService implements TbApiUsageStateService { + public static final String HOURLY = "HOURLY_"; private final ApiUsageStateService apiUsageStateService; private final TimeseriesService tsService; - private final ZoneId zoneId; + private final SchedulerComponent scheduler; + private final TbTenantProfileCache tenantProfileCache; private final Map tenantStates = new ConcurrentHashMap<>(); - public DefaultTbApiUsageStateService(ApiUsageStateService apiUsageStateService, TimeseriesService tsService) { + @Value("${usage.stats.report.enabled:true}") + private boolean enabled; + + @Value("${usage.stats.check.cycle:60000}") + private long nextCycleCheckInterval; + + private final Lock updateLock = new ReentrantLock(); + + public DefaultTbApiUsageStateService(ApiUsageStateService apiUsageStateService, TimeseriesService tsService, SchedulerComponent scheduler, TbTenantProfileCache tenantProfileCache) { this.apiUsageStateService = apiUsageStateService; this.tsService = tsService; - this.zoneId = SchedulerUtils.getZoneId("UTC"); + this.scheduler = scheduler; + this.tenantProfileCache = tenantProfileCache; + } + + @PostConstruct + public void init() { + if (enabled) { + scheduler.scheduleAtFixedRate(this::checkStartOfNextCycle, nextCycleCheckInterval, nextCycleCheckInterval, TimeUnit.MILLISECONDS); + } } @Override public void process(TbProtoQueueMsg msg, TbCallback callback) { ToUsageStatsServiceMsg statsMsg = msg.getValue(); TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB())); - TenantApiUsageState tenantState = getOrFetchState(tenantId); - long ts = tenantState.getCurrentMonthTs(); - List updatedEntries = new ArrayList<>(ApiUsageRecordKey.values().length); - for (UsageStatsKVProto kvProto : statsMsg.getValuesList()) { - ApiUsageRecordKey recordKey = ApiUsageRecordKey.valueOf(kvProto.getKey()); - long newValue = tenantState.add(recordKey, kvProto.getValue()); - updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.name(), newValue))); + TenantApiUsageState tenantState; + List updatedEntries; + updateLock.lock(); + try { + tenantState = getOrFetchState(tenantId); + long ts = tenantState.getCurrentCycleTs(); + long hourTs = tenantState.getCurrentHourTs(); + long newHourTs = SchedulerUtils.getStartOfCurrentHour(); + if (newHourTs != hourTs) { + tenantState.setHour(newHourTs); + } + updatedEntries = new ArrayList<>(ApiUsageRecordKey.values().length); + for (UsageStatsKVProto kvProto : statsMsg.getValuesList()) { + ApiUsageRecordKey recordKey = ApiUsageRecordKey.valueOf(kvProto.getKey()); + long newValue = tenantState.add(recordKey, kvProto.getValue()); + updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.name(), newValue))); + newValue = tenantState.addToHourly(recordKey, kvProto.getValue()); + updatedEntries.add(new BasicTsKvEntry(hourTs, new LongDataEntry(HOURLY + recordKey.name(), newValue))); + } + } finally { + updateLock.unlock(); } tsService.save(tenantId, tenantState.getEntityId(), updatedEntries, 0L); } + @Override + public TenantApiUsageState getApiUsageState(TenantId tenantId) { + return null; + } + + @Override + public void onAddedToAllowList(TenantId tenantId) { + + } + + @Override + public void onAddedToDenyList(TenantId tenantId) { + + } + + private void checkStartOfNextCycle() { + updateLock.lock(); + try { + long now = System.currentTimeMillis(); + tenantStates.values().forEach(state -> { + if ((state.getNextCycleTs() > now) && (state.getNextCycleTs() - now < TimeUnit.HOURS.toMillis(1))) { + state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth()); + } + }); + } finally { + updateLock.unlock(); + } + } + private TenantApiUsageState getOrFetchState(TenantId tenantId) { TenantApiUsageState tenantState = tenantStates.get(tenantId); if (tenantState == null) { - long currentMonthTs = LocalDate.now().withDayOfMonth(1).atStartOfDay(zoneId).toInstant().toEpochMilli(); ApiUsageState dbStateEntity = apiUsageStateService.findTenantApiUsageState(tenantId); - tenantState = new TenantApiUsageState(currentMonthTs, dbStateEntity.getEntityId()); + if (dbStateEntity == null) { + try { + dbStateEntity = apiUsageStateService.createDefaultApiUsageState(tenantId); + } catch (Exception e) { + dbStateEntity = apiUsageStateService.findTenantApiUsageState(tenantId); + } + } + tenantState = new TenantApiUsageState(dbStateEntity.getEntityId()); try { List dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getEntityId()).get(); for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { - TsKvEntry keyEntry = null; + boolean cycleEntryFound = false; + boolean hourlyEntryFound = false; for (TsKvEntry tsKvEntry : dbValues) { if (tsKvEntry.getKey().equals(key.name())) { - keyEntry = tsKvEntry; + cycleEntryFound = true; + tenantState.put(key, tsKvEntry.getLongValue().get()); + } else if (tsKvEntry.getKey().equals(HOURLY + key.name())) { + hourlyEntryFound = true; + if (tsKvEntry.getTs() == tenantState.getCurrentHourTs()) { + tenantState.putHourly(key, tsKvEntry.getLongValue().get()); + } + } + if (cycleEntryFound && hourlyEntryFound) { break; } } - if (keyEntry != null) { - tenantState.put(key, keyEntry.getLongValue().get()); - } else { - tenantState.put(key, 0L); - } } tenantStates.put(tenantId, tenantState); } catch (InterruptedException | ExecutionException e) { diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java index b5aeedeb06..e79b79d069 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java @@ -15,11 +15,19 @@ */ package org.thingsboard.server.service.apiusage; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.TbCallback; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; public interface TbApiUsageStateService { - void process(TbProtoQueueMsg msg, TbCallback callback); + void process(TbProtoQueueMsg msg, TbCallback callback); + + TenantApiUsageState getApiUsageState(TenantId tenantId); + + void onAddedToAllowList(TenantId tenantId); + + void onAddedToDenyList(TenantId tenantId); + } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java index a8be19914b..29335c3326 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -18,30 +18,65 @@ package org.thingsboard.server.service.apiusage; import lombok.Getter; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.msg.tools.SchedulerUtils; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class TenantApiUsageState { - private final Map values = new ConcurrentHashMap<>(); + private final Map currentCycleValues = new ConcurrentHashMap<>(); + private final Map currentHourValues = new ConcurrentHashMap<>(); + @Getter private final EntityId entityId; @Getter - private volatile long currentMonthTs; + private volatile long currentCycleTs; + @Getter + private volatile long nextCycleTs; + @Getter + private volatile long currentHourTs; - public TenantApiUsageState(long currentMonthTs, EntityId entityId) { + public TenantApiUsageState(EntityId entityId) { this.entityId = entityId; - this.currentMonthTs = currentMonthTs; + this.currentCycleTs = SchedulerUtils.getStartOfCurrentMonth(); + this.nextCycleTs = SchedulerUtils.getStartOfNextMonth(); + this.currentHourTs = SchedulerUtils.getStartOfCurrentHour(); } public void put(ApiUsageRecordKey key, Long value) { - values.put(key, value); + currentCycleValues.put(key, value); + } + + public void putHourly(ApiUsageRecordKey key, Long value) { + currentHourValues.put(key, value); } public long add(ApiUsageRecordKey key, long value) { - long result = values.getOrDefault(key, 0L) + value; - values.put(key, result); + long result = currentCycleValues.getOrDefault(key, 0L) + value; + currentCycleValues.put(key, result); return result; } + + public long addToHourly(ApiUsageRecordKey key, long value) { + long result = currentHourValues.getOrDefault(key, 0L) + value; + currentHourValues.put(key, result); + return result; + } + + public void setHour(long currentHourTs) { + this.currentHourTs = currentHourTs; + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { + currentHourValues.put(key, 0L); + } + } + + public void setCycles(long currentCycleTs, long nextCycleTs) { + this.currentCycleTs = currentCycleTs; + this.nextCycleTs = nextCycleTs; + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { + currentCycleValues.put(key, 0L); + } + } + } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java index ad6c73f63e..c39e2a69dc 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java @@ -24,5 +24,5 @@ public interface ApiUsageStateService { void deleteApiUsageStateByTenantId(TenantId tenantId); - void createDefaultApiUsageState(TenantId id); + ApiUsageState createDefaultApiUsageState(TenantId id); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleEvent.java b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleEvent.java index 87adc31713..1f242a0f88 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleEvent.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleEvent.java @@ -21,5 +21,5 @@ import java.io.Serializable; * @author Andrew Shvayka */ public enum ComponentLifecycleEvent implements Serializable { - CREATED, STARTED, ACTIVATED, SUSPENDED, UPDATED, STOPPED, DELETED + CREATED, STARTED, ACTIVATED, SUSPENDED, UPDATED, STOPPED, DELETED, ADDED_TO_ALLOW_LIST, ADDED_TO_DENY_LIST } \ No newline at end of file diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/tools/SchedulerUtils.java b/common/message/src/main/java/org/thingsboard/server/common/msg/tools/SchedulerUtils.java index fea6fcb337..421066b30b 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/tools/SchedulerUtils.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/tools/SchedulerUtils.java @@ -15,16 +15,64 @@ */ package org.thingsboard.server.common.msg.tools; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalAdjusters; +import java.time.temporal.TemporalUnit; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoUnit.MONTHS; + public class SchedulerUtils { + private final static ZoneId UTC = ZoneId.of("UTC"); private static final ConcurrentMap tzMap = new ConcurrentHashMap<>(); public static ZoneId getZoneId(String tz) { return tzMap.computeIfAbsent(tz == null || tz.isEmpty() ? "UTC" : tz, ZoneId::of); } + public static long getStartOfCurrentHour() { + return getStartOfCurrentHour(UTC); + } + + public static long getStartOfCurrentHour(ZoneId zoneId) { + return LocalDateTime.now(ZoneOffset.UTC).atZone(zoneId).truncatedTo(ChronoUnit.HOURS).toInstant().toEpochMilli(); + } + + public static long getStartOfCurrentMonth() { + return getStartOfCurrentMonth(UTC); + } + + public static long getStartOfCurrentMonth(ZoneId zoneId) { + return LocalDate.now().withDayOfMonth(1).atStartOfDay(zoneId).toInstant().toEpochMilli(); + } + + public static long getStartOfNextMonth() { + return getStartOfNextMonth(UTC); + } + + public static long getStartOfNextMonth(ZoneId zoneId) { + return LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli(); + } + + public static long getStartOfNextNextMonth() { + return getStartOfNextNextMonth(UTC); + } + + public static long getStartOfNextNextMonth(ZoneId zoneId) { + return LocalDate.now().with(firstDayOfNextNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli(); + } + + public static TemporalAdjuster firstDayOfNextNextMonth() { + return (temporal) -> temporal.with(DAY_OF_MONTH, 1).plus(2, MONTHS); + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java index 9e543a8f7a..6be56a1815 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java @@ -49,14 +49,14 @@ public class ApiApiUsageStateServiceImpl extends AbstractEntityService implement } @Override - public void createDefaultApiUsageState(TenantId tenantId) { + public ApiUsageState createDefaultApiUsageState(TenantId tenantId) { log.trace("Executing createDefaultUsageRecord [{}]", tenantId); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); ApiUsageState apiUsageState = new ApiUsageState(); apiUsageState.setTenantId(tenantId); apiUsageState.setEntityId(tenantId); apiUsageStateValidator.validate(apiUsageState, ApiUsageState::getTenantId); - apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); + return apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); } @Override From 7e8837603016db5c3ab79bd0408f6b7df14710db Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 20 Oct 2020 18:55:52 +0300 Subject: [PATCH 234/463] created alias ApiUsageState --- .../DefaultTbApiUsageStateService.java | 6 ++-- .../service/apiusage/TenantApiUsageState.java | 7 +++-- .../queue/DefaultTbCoreConsumerService.java | 3 ++ .../service/security/AccessValidator.java | 27 ++++++++++++++++++ .../service/security/permission/Resource.java | 3 +- .../permission/TenantAdminPermissions.java | 1 + .../dao/usagerecord/ApiUsageStateService.java | 3 ++ .../common/data/id/EntityIdFactory.java | 2 ++ .../data/query/ApiUsageStateFilter.java | 28 +++++++++++++++++++ .../common/data/query/EntityFilter.java | 1 + .../common/data/query/EntityFilterType.java | 3 +- .../query/DefaultEntityQueryRepository.java | 4 +++ .../ApiApiUsageStateServiceImpl.java | 8 ++++++ ui-ngx/src/app/core/http/entity.service.ts | 3 ++ ui-ngx/src/app/shared/models/alias.models.ts | 9 +++++- .../assets/locale/locale.constant-en_US.json | 1 + 16 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/query/ApiUsageStateFilter.java diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 1b6cdf3cff..185637e543 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -62,7 +62,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @Value("${usage.stats.report.enabled:true}") private boolean enabled; - @Value("${usage.stats.check.cycle:60000}") + @Value("${usage.stats.check.cycle:6000}") private long nextCycleCheckInterval; private final Lock updateLock = new ReentrantLock(); @@ -107,7 +107,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } finally { updateLock.unlock(); } - tsService.save(tenantId, tenantState.getEntityId(), updatedEntries, 0L); + tsService.save(tenantId, tenantState.getId(), updatedEntries, 0L); } @Override @@ -150,7 +150,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { dbStateEntity = apiUsageStateService.findTenantApiUsageState(tenantId); } } - tenantState = new TenantApiUsageState(dbStateEntity.getEntityId()); + tenantState = new TenantApiUsageState(dbStateEntity.getId()); try { List dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getEntityId()).get(); for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java index 29335c3326..2c483cea29 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.apiusage; import lombok.Getter; import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.msg.tools.SchedulerUtils; @@ -29,7 +30,7 @@ public class TenantApiUsageState { private final Map currentHourValues = new ConcurrentHashMap<>(); @Getter - private final EntityId entityId; + private final ApiUsageStateId id; @Getter private volatile long currentCycleTs; @Getter @@ -37,8 +38,8 @@ public class TenantApiUsageState { @Getter private volatile long currentHourTs; - public TenantApiUsageState(EntityId entityId) { - this.entityId = entityId; + public TenantApiUsageState(ApiUsageStateId id) { + this.id = id; this.currentCycleTs = SchedulerUtils.getStartOfCurrentMonth(); this.nextCycleTs = SchedulerUtils.getStartOfNextMonth(); this.currentHourTs = SchedulerUtils.getStartOfCurrentHour(); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 929d5000f0..ee2aa71e73 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -396,6 +396,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService callback) { + if (currentUser.isSystemAdmin()) { + callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); + } else { + ApiUsageState apiUsageState = apiUsageStateService.findApiUsageStateById(currentUser.getTenantId(), new ApiUsageStateId(entityId.getId())); + if (apiUsageState == null) { + callback.onSuccess(ValidationResult.entityNotFound("Api Usage State with requested id wasn't found!")); + } else { + try { + accessControlService.checkPermission(currentUser, Resource.API_USAGE_STATE, operation, entityId, apiUsageState); + } catch (ThingsboardException e) { + callback.onSuccess(ValidationResult.accessDenied(e.getMessage())); + } + callback.onSuccess(ValidationResult.ok(apiUsageState)); + } + } + } + private void validateAsset(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback callback) { if (currentUser.isSystemAdmin()) { callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java index 08534dc2d4..9d16760464 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java @@ -35,7 +35,8 @@ public enum Resource { OAUTH2_CONFIGURATION_INFO(), OAUTH2_CONFIGURATION_TEMPLATE(), TENANT_PROFILE(EntityType.TENANT_PROFILE), - DEVICE_PROFILE(EntityType.DEVICE_PROFILE); + DEVICE_PROFILE(EntityType.DEVICE_PROFILE), + API_USAGE_STATE(EntityType.API_USAGE_STATE); private final EntityType entityType; diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java index 4124292c95..e0c34200f4 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java @@ -40,6 +40,7 @@ public class TenantAdminPermissions extends AbstractPermissions { put(Resource.WIDGETS_BUNDLE, widgetsPermissionChecker); put(Resource.WIDGET_TYPE, widgetsPermissionChecker); put(Resource.DEVICE_PROFILE, tenantEntityPermissionChecker); + put(Resource.API_USAGE_STATE, tenantEntityPermissionChecker); } public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java index c39e2a69dc..8131291fbb 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao.usagerecord; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.TenantId; public interface ApiUsageStateService { @@ -25,4 +26,6 @@ public interface ApiUsageStateService { void deleteApiUsageStateByTenantId(TenantId tenantId); ApiUsageState createDefaultApiUsageState(TenantId id); + + ApiUsageState findApiUsageStateById(TenantId tenantId, ApiUsageStateId id); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java index dc1589814f..8a1a769c5e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java @@ -66,6 +66,8 @@ public class EntityIdFactory { return new DeviceProfileId(uuid); case TENANT_PROFILE: return new TenantProfileId(uuid); + case API_USAGE_STATE: + return new ApiUsageStateId(uuid); } throw new IllegalArgumentException("EntityType " + type + " is not supported!"); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/ApiUsageStateFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/ApiUsageStateFilter.java new file mode 100644 index 0000000000..436aa44230 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/ApiUsageStateFilter.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.query; + +import lombok.Data; +import org.thingsboard.server.common.data.id.EntityId; + +@Data +public class ApiUsageStateFilter implements EntityFilter { + @Override + public EntityFilterType getType() { + return EntityFilterType.API_USAGE_STATE; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilter.java index d3b9c39530..f2a6dde626 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilter.java @@ -32,6 +32,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; @JsonSubTypes.Type(value = AssetTypeFilter.class, name = "assetType"), @JsonSubTypes.Type(value = DeviceTypeFilter.class, name = "deviceType"), @JsonSubTypes.Type(value = EntityViewTypeFilter.class, name = "entityViewType"), + @JsonSubTypes.Type(value = ApiUsageStateFilter.class, name = "apiUsageState"), @JsonSubTypes.Type(value = RelationsQueryFilter.class, name = "relationsQuery"), @JsonSubTypes.Type(value = AssetSearchQueryFilter.class, name = "assetSearchQuery"), @JsonSubTypes.Type(value = DeviceSearchQueryFilter.class, name = "deviceSearchQuery"), diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java index 0c05fdc8e4..12860b8453 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java @@ -25,7 +25,8 @@ public enum EntityFilterType { RELATIONS_QUERY("relationsQuery"), ASSET_SEARCH_QUERY("assetSearchQuery"), DEVICE_SEARCH_QUERY("deviceSearchQuery"), - ENTITY_VIEW_SEARCH_QUERY("entityViewSearchQuery"); + ENTITY_VIEW_SEARCH_QUERY("entityViewSearchQuery"), + API_USAGE_STATE("apiUsageState"); private final String label; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java index 75f7658a5a..380e166aa5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java @@ -210,6 +210,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { entityTableMap.put(EntityType.CUSTOMER, "customer"); entityTableMap.put(EntityType.USER, "tb_user"); entityTableMap.put(EntityType.TENANT, "tenant"); + entityTableMap.put(EntityType.API_USAGE_STATE, "(select aus.id, aus.created_time, aus.tenant_id, '' as name, '' as additional_info from api_usage_state as aus)"); } public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{ @@ -431,6 +432,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { case DEVICE_SEARCH_QUERY: case ASSET_SEARCH_QUERY: case ENTITY_VIEW_SEARCH_QUERY: + case API_USAGE_STATE: return ""; default: throw new RuntimeException("Not implemented!"); @@ -682,6 +684,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { return EntityType.ENTITY_VIEW; case RELATIONS_QUERY: return ((RelationsQueryFilter) entityFilter).getRootEntity().getEntityType(); + case API_USAGE_STATE: + return EntityType.API_USAGE_STATE; default: throw new RuntimeException("Not implemented!"); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java index 6be56a1815..00ecde9d95 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java @@ -20,6 +20,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; @@ -66,6 +67,13 @@ public class ApiApiUsageStateServiceImpl extends AbstractEntityService implement return apiUsageStateDao.findTenantApiUsageState(tenantId.getId()); } + @Override + public ApiUsageState findApiUsageStateById(TenantId tenantId, ApiUsageStateId id) { + log.trace("Executing findApiUsageStateById, tenantId [{}], apiUsageStateId [{}]", tenantId, id); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateId(id, "Incorrect apiUsageStateId " + id); + return apiUsageStateDao.findById(tenantId, id.getId()); } + private DataValidator apiUsageStateValidator = new DataValidator() { @Override diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index a510735855..acd89e3c1b 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -753,6 +753,9 @@ export class EntityService { case AliasFilterType.entityViewType: result.entityFilter = deepClone(filter); return of(result); + case AliasFilterType.apiUsageState: + result.entityFilter = deepClone(filter); + return of(result); case AliasFilterType.relationsQuery: result.stateEntity = filter.rootStateEntity; let rootEntityType; diff --git a/ui-ngx/src/app/shared/models/alias.models.ts b/ui-ngx/src/app/shared/models/alias.models.ts index 35b52435a2..f437931f27 100644 --- a/ui-ngx/src/app/shared/models/alias.models.ts +++ b/ui-ngx/src/app/shared/models/alias.models.ts @@ -28,6 +28,7 @@ export enum AliasFilterType { assetType = 'assetType', deviceType = 'deviceType', entityViewType = 'entityViewType', + apiUsageState = 'apiUsageState', relationsQuery = 'relationsQuery', assetSearchQuery = 'assetSearchQuery', deviceSearchQuery = 'deviceSearchQuery', @@ -43,6 +44,7 @@ export const aliasFilterTypeTranslationMap = new Map( [ AliasFilterType.assetType, 'alias.filter-type-asset-type' ], [ AliasFilterType.deviceType, 'alias.filter-type-device-type' ], [ AliasFilterType.entityViewType, 'alias.filter-type-entity-view-type' ], + [ AliasFilterType.apiUsageState, 'alias.filter-type-apiUsageState' ], [ AliasFilterType.relationsQuery, 'alias.filter-type-relations-query' ], [ AliasFilterType.assetSearchQuery, 'alias.filter-type-asset-search-query' ], [ AliasFilterType.deviceSearchQuery, 'alias.filter-type-device-search-query' ], @@ -106,6 +108,10 @@ export interface EntitySearchQueryFilter { fetchLastLevelOnly?: boolean; } +export interface ApiUsageStateFilter { + +} + export interface AssetSearchQueryFilter extends EntitySearchQueryFilter { assetTypes?: string[]; } @@ -129,7 +135,8 @@ export type EntityFilters = RelationsQueryFilter & AssetSearchQueryFilter & DeviceSearchQueryFilter & - EntityViewSearchQueryFilter; + EntityViewSearchQueryFilter & + EntitySearchQueryFilter; export interface EntityAliasFilter extends EntityFilters { type?: AliasFilterType; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 453d5d2abd..082c2241b4 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -288,6 +288,7 @@ "filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", "filter-type-entity-view-search-query": "Entity view search query", "filter-type-entity-view-search-query-description": "Entity views with types {{entityViewTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", + "filter-type-apiUsageState": "Api Usage State", "entity-filter": "Entity filter", "resolve-multiple": "Resolve as multiple entities", "filter-type": "Filter type", From f7aa8df607f494444ec0f8261a37721d5335f8ba Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 20 Oct 2020 18:57:08 +0300 Subject: [PATCH 235/463] refactored --- .../server/service/apiusage/DefaultTbApiUsageStateService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 185637e543..1a7d1cfae1 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -62,7 +62,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @Value("${usage.stats.report.enabled:true}") private boolean enabled; - @Value("${usage.stats.check.cycle:6000}") + @Value("${usage.stats.check.cycle:60000}") private long nextCycleCheckInterval; private final Lock updateLock = new ReentrantLock(); From 9d14a38966c5351edd16c01b339a9ff3c19e88e7 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 20 Oct 2020 18:57:15 +0300 Subject: [PATCH 236/463] Tenant Profile updates for ApiUsageStateService --- .../server/actors/app/AppActor.java | 9 +- .../DefaultTbApiUsageStateService.java | 37 +++++- .../apiusage/TbApiUsageStateService.java | 6 +- .../service/apiusage/TenantApiUsageState.java | 122 +++++++++++++++++- .../queue/DefaultTbCoreConsumerService.java | 2 +- .../DefaultTbRuleEngineConsumerService.java | 4 +- .../processing/AbstractConsumerService.java | 18 ++- .../dao/usagerecord/ApiUsageStateService.java | 5 +- .../server/common/data/ApiUsageState.java | 30 ++--- .../ApiApiUsageStateServiceImpl.java | 8 ++ 10 files changed, 197 insertions(+), 44 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java index 826596db56..fea5558540 100644 --- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java @@ -40,8 +40,6 @@ import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg; import org.thingsboard.server.common.msg.queue.RuleEngineException; import org.thingsboard.server.common.msg.queue.ServiceType; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.service.profile.TbTenantProfileCache; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; @@ -150,15 +148,12 @@ public class AppActor extends ContextAwareActor { private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { TbActorRef target = null; if (TenantId.SYS_TENANT_ID.equals(msg.getTenantId())) { - if (msg.getEntityId().getEntityType() == EntityType.TENANT_PROFILE) { - tenantProfileCache.evict(new TenantProfileId(msg.getEntityId().getId())); - } else { + if (!EntityType.TENANT_PROFILE.equals(msg.getEntityId().getEntityType())) { log.warn("Message has system tenant id: {}", msg); } } else { - if (msg.getEntityId().getEntityType() == EntityType.TENANT) { + if (EntityType.TENANT.equals(msg.getEntityId().getEntityType())) { TenantId tenantId = new TenantId(msg.getEntityId().getId()); - tenantProfileCache.evict(tenantId); if (msg.getEvent() == ComponentLifecycleEvent.DELETED) { log.info("[{}] Handling tenant deleted notification: {}", msg.getTenantId(), msg); deletedTenants.add(tenantId); diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 1b6cdf3cff..84801224e1 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; @@ -87,6 +88,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB())); TenantApiUsageState tenantState; List updatedEntries; + boolean stateUpdated = false; updateLock.lock(); try { tenantState = getOrFetchState(tenantId); @@ -101,13 +103,21 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { ApiUsageRecordKey recordKey = ApiUsageRecordKey.valueOf(kvProto.getKey()); long newValue = tenantState.add(recordKey, kvProto.getValue()); updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.name(), newValue))); - newValue = tenantState.addToHourly(recordKey, kvProto.getValue()); - updatedEntries.add(new BasicTsKvEntry(hourTs, new LongDataEntry(HOURLY + recordKey.name(), newValue))); + long newHourlyValue = tenantState.addToHourly(recordKey, kvProto.getValue()); + updatedEntries.add(new BasicTsKvEntry(hourTs, new LongDataEntry(HOURLY + recordKey.name(), newHourlyValue))); + stateUpdated |= tenantState.checkStateUpdatedDueToThreshold(recordKey); } } finally { updateLock.unlock(); } tsService.save(tenantId, tenantState.getEntityId(), updatedEntries, 0L); + if (stateUpdated) { + // Save new state into the database; + apiUsageStateService.update(tenantState.getApiUsageState()); + //TODO: clear cache on cluster repartition. + //TODO: update profiles on tenant and profile updates. + //TODO: broadcast to everyone notifications about enabled/disabled features. + } } @Override @@ -116,12 +126,26 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } @Override - public void onAddedToAllowList(TenantId tenantId) { - + public void onTenantProfileUpdate(TenantProfileId tenantProfileId) { + TenantProfile tenantProfile = tenantProfileCache.get(tenantProfileId); + updateLock.lock(); + try { + tenantStates.values().forEach(state -> { + if (tenantProfile.getId().equals(state.getTenantProfileId())) { + state.setTenantProfileData(tenantProfile.getProfileData()); + if (state.checkStateUpdatedDueToThresholds()) { + apiUsageStateService.update(state.getApiUsageState()); + //TODO: send notification to cluster; + } + } + }); + } finally { + updateLock.unlock(); + } } @Override - public void onAddedToDenyList(TenantId tenantId) { + public void onTenantUpdate(TenantId tenantId) { } @@ -150,7 +174,8 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { dbStateEntity = apiUsageStateService.findTenantApiUsageState(tenantId); } } - tenantState = new TenantApiUsageState(dbStateEntity.getEntityId()); + TenantProfile tenantProfile = tenantProfileCache.get(tenantId); + tenantState = new TenantApiUsageState(tenantProfile, dbStateEntity); try { List dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getEntityId()).get(); for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java index e79b79d069..d17bd9a3dd 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.apiusage; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; @@ -26,8 +27,7 @@ public interface TbApiUsageStateService { TenantApiUsageState getApiUsageState(TenantId tenantId); - void onAddedToAllowList(TenantId tenantId); - - void onAddedToDenyList(TenantId tenantId); + void onTenantProfileUpdate(TenantProfileId tenantProfileId); + void onTenantUpdate(TenantId tenantId); } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java index 29335c3326..0492ba9793 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -16,8 +16,13 @@ package org.thingsboard.server.service.apiusage; import lombok.Getter; +import lombok.Setter; import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.TenantProfileData; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.msg.tools.SchedulerUtils; import java.util.Map; @@ -29,7 +34,13 @@ public class TenantApiUsageState { private final Map currentHourValues = new ConcurrentHashMap<>(); @Getter - private final EntityId entityId; + @Setter + private TenantProfileId tenantProfileId; + @Getter + @Setter + private TenantProfileData tenantProfileData; + @Getter + private final ApiUsageState apiUsageState; @Getter private volatile long currentCycleTs; @Getter @@ -37,8 +48,10 @@ public class TenantApiUsageState { @Getter private volatile long currentHourTs; - public TenantApiUsageState(EntityId entityId) { - this.entityId = entityId; + public TenantApiUsageState(TenantProfile tenantProfile, ApiUsageState apiUsageState) { + this.tenantProfileId = tenantProfile.getId(); + this.tenantProfileData = tenantProfile.getProfileData(); + this.apiUsageState = apiUsageState; this.currentCycleTs = SchedulerUtils.getStartOfCurrentMonth(); this.nextCycleTs = SchedulerUtils.getStartOfNextMonth(); this.currentHourTs = SchedulerUtils.getStartOfCurrentHour(); @@ -58,6 +71,10 @@ public class TenantApiUsageState { return result; } + public long get(ApiUsageRecordKey key) { + return currentCycleValues.getOrDefault(key, 0L); + } + public long addToHourly(ApiUsageRecordKey key, long value) { long result = currentHourValues.getOrDefault(key, 0L) + value; currentHourValues.put(key, result); @@ -79,4 +96,103 @@ public class TenantApiUsageState { } } + public long getProfileThreshold(ApiUsageRecordKey key) { + Object threshold = tenantProfileData.getProperties().get(key.name()); + if (threshold != null) { + if (threshold instanceof String) { + return Long.parseLong((String) threshold); + } else if (threshold instanceof Long) { + return (Long) threshold; + } + } + return 0L; + } + + public EntityId getEntityId() { + return apiUsageState.getEntityId(); + } + + public boolean isTransportEnabled() { + return apiUsageState.isTransportEnabled(); + } + + public boolean isDbStorageEnabled() { + return apiUsageState.isDbStorageEnabled(); + } + + public boolean isRuleEngineEnabled() { + return apiUsageState.isRuleEngineEnabled(); + } + + public boolean isJsExecEnabled() { + return apiUsageState.isJsExecEnabled(); + } + + public void setTransportEnabled(boolean transportEnabled) { + apiUsageState.setTransportEnabled(transportEnabled); + } + + public void setDbStorageEnabled(boolean dbStorageEnabled) { + apiUsageState.setDbStorageEnabled(dbStorageEnabled); + } + + public void setRuleEngineEnabled(boolean ruleEngineEnabled) { + apiUsageState.setRuleEngineEnabled(ruleEngineEnabled); + } + + public void setJsExecEnabled(boolean jsExecEnabled) { + apiUsageState.setJsExecEnabled(jsExecEnabled); + } + + public boolean isFeatureEnabled(ApiUsageRecordKey recordKey) { + switch (recordKey) { + case MSG_COUNT: + case MSG_BYTES_COUNT: + case DP_TRANSPORT_COUNT: + return isTransportEnabled(); + case RE_EXEC_COUNT: + return isRuleEngineEnabled(); + case DP_STORAGE_COUNT: + return isDbStorageEnabled(); + case JS_EXEC_COUNT: + return isJsExecEnabled(); + default: + return true; + } + } + + public boolean setFeatureValue(ApiUsageRecordKey recordKey, boolean value) { + boolean currentValue = isFeatureEnabled(recordKey); + switch (recordKey) { + case MSG_COUNT: + case MSG_BYTES_COUNT: + case DP_TRANSPORT_COUNT: + setTransportEnabled(value); + break; + case RE_EXEC_COUNT: + setRuleEngineEnabled(value); + break; + case DP_STORAGE_COUNT: + setDbStorageEnabled(value); + break; + case JS_EXEC_COUNT: + setJsExecEnabled(value); + break; + } + return currentValue == value; + } + + public boolean checkStateUpdatedDueToThresholds() { + boolean update = false; + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { + update |= checkStateUpdatedDueToThreshold(key); + } + return update; + } + + public boolean checkStateUpdatedDueToThreshold(ApiUsageRecordKey recordKey) { + long value = get(recordKey); + long threshold = getProfileThreshold(recordKey); + return setFeatureValue(recordKey, threshold == 0 || value < threshold); + } } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 929d5000f0..4ad073ead8 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -107,7 +107,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> nfConsumer; public AbstractConsumerService(ActorSystemContext actorContext, DataDecodingEncodingService encodingService, - TbDeviceProfileCache deviceProfileCache, TbQueueConsumer> nfConsumer) { + TbTenantProfileCache tenantProfileCache, TbDeviceProfileCache deviceProfileCache, TbApiUsageStateService apiUsageStateService, TbQueueConsumer> nfConsumer) { this.actorContext = actorContext; this.encodingService = encodingService; + this.tenantProfileCache = tenantProfileCache; this.deviceProfileCache = deviceProfileCache; + this.apiUsageStateService = apiUsageStateService; this.nfConsumer = nfConsumer; } @@ -143,7 +150,14 @@ public abstract class AbstractConsumerService implements HasTenan private static final long serialVersionUID = 8250339805336035966L; + @Getter @Setter private TenantId tenantId; + @Getter @Setter private EntityId entityId; + @Getter @Setter + private boolean transportEnabled; + @Getter @Setter + private boolean dbStorageEnabled; + @Getter @Setter + private boolean ruleEngineEnabled; + @Getter @Setter + private boolean jsExecEnabled; public ApiUsageState() { super(); @@ -43,22 +55,4 @@ public class ApiUsageState extends BaseData implements HasTenan this.tenantId = ur.getTenantId(); this.entityId = ur.getEntityId(); } - - @Override - public TenantId getTenantId() { - return tenantId; - } - - public void setTenantId(TenantId tenantId) { - this.tenantId = tenantId; - } - - public EntityId getEntityId() { - return entityId; - } - - public void setEntityId(EntityId entityId) { - this.entityId = entityId; - } - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java index 6be56a1815..7dc390046e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java @@ -59,6 +59,14 @@ public class ApiApiUsageStateServiceImpl extends AbstractEntityService implement return apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); } + @Override + public ApiUsageState update(ApiUsageState apiUsageState) { + log.trace("Executing save [{}]", apiUsageState.getTenantId()); + validateId(apiUsageState.getTenantId(), INCORRECT_TENANT_ID + apiUsageState.getTenantId()); + validateId(apiUsageState.getId(), "Can't save new usage state. Only update is allowed!"); + return apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); + } + @Override public ApiUsageState findTenantApiUsageState(TenantId tenantId) { log.trace("Executing findTenantUsageRecord, tenantId [{}]", tenantId); From 9ab92ba73cddfb584bebfd3166e41908ae989236 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 21 Oct 2020 10:21:01 +0300 Subject: [PATCH 237/463] validateApiUsageState improvements --- .../thingsboard/server/service/security/AccessValidator.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java index 46de4f150e..57c192235e 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java +++ b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java @@ -250,6 +250,9 @@ public class AccessValidator { if (currentUser.isSystemAdmin()) { callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); } else { + if (!operation.equals(Operation.READ_TELEMETRY)) { + callback.onSuccess(ValidationResult.accessDenied("Allowed only READ_TELEMETRY operation!")); + } ApiUsageState apiUsageState = apiUsageStateService.findApiUsageStateById(currentUser.getTenantId(), new ApiUsageStateId(entityId.getId())); if (apiUsageState == null) { callback.onSuccess(ValidationResult.entityNotFound("Api Usage State with requested id wasn't found!")); From a922b8227f299de8b7c0bb48a6cf322f2bc6a464 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 21 Oct 2020 10:35:15 +0300 Subject: [PATCH 238/463] Usage records compile fix --- .../server/service/queue/DefaultTbCoreConsumerService.java | 3 +++ .../service/queue/DefaultTbRuleEngineConsumerService.java | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 4ad073ead8..33c21a5b5f 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -54,6 +54,7 @@ import org.thingsboard.server.queue.provider.TbCoreQueueFactory; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; +import org.thingsboard.server.service.profile.TbTenantProfileCache; import org.thingsboard.server.service.queue.processing.AbstractConsumerService; import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; @@ -104,6 +105,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService Date: Wed, 21 Oct 2020 11:13:48 +0300 Subject: [PATCH 239/463] Cleanup of the map on repartition --- .../DefaultTbApiUsageStateService.java | 41 +++++++++++++++---- .../apiusage/TbApiUsageStateService.java | 6 ++- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 84801224e1..003ee72807 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.tools.SchedulerUtils; import org.thingsboard.server.dao.timeseries.TimeseriesService; @@ -33,6 +34,8 @@ import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.discovery.PartitionChangeEvent; +import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.profile.TbTenantProfileCache; @@ -54,6 +57,7 @@ import java.util.concurrent.locks.ReentrantLock; public class DefaultTbApiUsageStateService implements TbApiUsageStateService { public static final String HOURLY = "HOURLY_"; + private final PartitionService partitionService; private final ApiUsageStateService apiUsageStateService; private final TimeseriesService tsService; private final SchedulerComponent scheduler; @@ -68,7 +72,8 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { private final Lock updateLock = new ReentrantLock(); - public DefaultTbApiUsageStateService(ApiUsageStateService apiUsageStateService, TimeseriesService tsService, SchedulerComponent scheduler, TbTenantProfileCache tenantProfileCache) { + public DefaultTbApiUsageStateService(PartitionService partitionService, ApiUsageStateService apiUsageStateService, TimeseriesService tsService, SchedulerComponent scheduler, TbTenantProfileCache tenantProfileCache) { + this.partitionService = partitionService; this.apiUsageStateService = apiUsageStateService; this.tsService = tsService; this.scheduler = scheduler; @@ -120,8 +125,16 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } + @Override + public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { + if (partitionChangeEvent.getServiceType().equals(ServiceType.TB_CORE)) { + tenantStates.entrySet().removeIf(entry -> !partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition()); + } + } + @Override public TenantApiUsageState getApiUsageState(TenantId tenantId) { + //We should always get it from the map of from the database; return null; } @@ -132,11 +145,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { try { tenantStates.values().forEach(state -> { if (tenantProfile.getId().equals(state.getTenantProfileId())) { - state.setTenantProfileData(tenantProfile.getProfileData()); - if (state.checkStateUpdatedDueToThresholds()) { - apiUsageStateService.update(state.getApiUsageState()); - //TODO: send notification to cluster; - } + updateTenantState(state, tenantProfile); } }); } finally { @@ -146,7 +155,25 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @Override public void onTenantUpdate(TenantId tenantId) { + TenantProfile tenantProfile = tenantProfileCache.get(tenantId); + updateLock.lock(); + try { + TenantApiUsageState state = tenantStates.get(tenantId); + if (state != null && !state.getTenantProfileId().equals(tenantProfile.getId())) { + updateTenantState(state, tenantProfile); + } + } finally { + updateLock.unlock(); + } + } + + private void updateTenantState(TenantApiUsageState state, TenantProfile tenantProfile) { + state.setTenantProfileData(tenantProfile.getProfileData()); + if (state.checkStateUpdatedDueToThresholds()) { + apiUsageStateService.update(state.getApiUsageState()); + //TODO: send notification to cluster; + } } private void checkStartOfNextCycle() { diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java index d17bd9a3dd..4816dc1037 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -15,13 +15,15 @@ */ package org.thingsboard.server.service.apiusage; +import org.springframework.context.ApplicationListener; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.discovery.PartitionChangeEvent; -public interface TbApiUsageStateService { +public interface TbApiUsageStateService extends ApplicationListener { void process(TbProtoQueueMsg msg, TbCallback callback); From 444af2e57b4cfc63a1229e1a2621de1a9a5d8414 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Wed, 21 Oct 2020 11:25:14 +0300 Subject: [PATCH 240/463] Refactoring --- .../org/thingsboard/mqtt/MqttClientImpl.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java index f72b8d0f4e..289f73dc52 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java @@ -52,6 +52,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -62,11 +63,11 @@ import java.util.concurrent.atomic.AtomicInteger; final class MqttClientImpl implements MqttClient { private final Set serverSubscriptions = new HashSet<>(); - private final ConcurrentHashMap pendingServerUnsubscribes = new ConcurrentHashMap<>(); - private final ConcurrentHashMap qos2PendingIncomingPublishes = new ConcurrentHashMap<>(); - private final ConcurrentHashMap pendingPublishes = new ConcurrentHashMap<>(); + private final ConcurrentMap pendingServerUnsubscribes = new ConcurrentHashMap<>(); + private final ConcurrentMap qos2PendingIncomingPublishes = new ConcurrentHashMap<>(); + private final ConcurrentMap pendingPublishes = new ConcurrentHashMap<>(); private final HashMultimap subscriptions = HashMultimap.create(); - private final ConcurrentHashMap pendingSubscriptions = new ConcurrentHashMap<>(); + private final ConcurrentMap pendingSubscriptions = new ConcurrentHashMap<>(); private final Set pendingSubscribeTopics = new HashSet<>(); private final HashMultimap handlerToSubscribtion = HashMultimap.create(); private final AtomicInteger nextMessageId = new AtomicInteger(1); @@ -489,7 +490,7 @@ final class MqttClientImpl implements MqttClient { } } - ConcurrentHashMap getPendingSubscriptions() { + ConcurrentMap getPendingSubscriptions() { return pendingSubscriptions; } @@ -509,15 +510,15 @@ final class MqttClientImpl implements MqttClient { return serverSubscriptions; } - ConcurrentHashMap getPendingServerUnsubscribes() { + ConcurrentMap getPendingServerUnsubscribes() { return pendingServerUnsubscribes; } - ConcurrentHashMap getPendingPublishes() { + ConcurrentMap getPendingPublishes() { return pendingPublishes; } - ConcurrentHashMap getQos2PendingIncomingPublishes() { + ConcurrentMap getQos2PendingIncomingPublishes() { return qos2PendingIncomingPublishes; } From 41212e11c01130eee0ca99127c5f12f49b60f9d4 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 22 Oct 2020 15:33:34 +0300 Subject: [PATCH 241/463] created tenant profile configuration --- .../DefaultTbApiUsageStateService.java | 2 +- .../apiusage/TbApiUsageStateService.java | 2 +- .../service/apiusage/TenantApiUsageState.java | 2 +- .../DefaultSystemDataLoaderService.java | 12 +- .../BaseTenantProfileControllerTest.java | 2 +- .../server/common/data/TenantProfile.java | 1 + .../server/common/data/TenantProfileType.java | 20 +++ .../DefaultTenantProfileConfiguration.java | 45 ++++++ .../profile/TenantProfileConfiguration.java | 36 +++++ .../profile}/TenantProfileData.java | 4 +- .../DefaultTransportRateLimitService.java | 2 +- .../dao/model/sql/TenantProfileEntity.java | 3 +- .../dao/tenant/TenantProfileServiceImpl.java | 4 +- .../service/BaseTenantProfileServiceTest.java | 11 +- .../home/components/home-components.module.ts | 4 + .../tenant-profile-autocomplete.component.ts | 1 - .../tenant-profile-data.component.html | 16 +- .../profile/tenant-profile-data.component.ts | 6 +- .../profile/tenant-profile.component.ts | 15 +- ...enant-profile-configuration.component.html | 141 ++++++++++++++++++ ...-tenant-profile-configuration.component.ts | 115 ++++++++++++++ ...enant-profile-configuration.component.html | 27 ++++ .../tenant-profile-configuration.component.ts | 103 +++++++++++++ ui-ngx/src/app/shared/models/tenant.model.ts | 53 ++++++- .../assets/locale/locale.constant-en_US.json | 33 +++- 25 files changed, 628 insertions(+), 32 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/TenantProfileType.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java rename common/data/src/main/java/org/thingsboard/server/common/data/{ => tenant/profile}/TenantProfileData.java (91%) create mode 100644 ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/profile/tenant/tenant-profile-configuration.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/tenant/tenant-profile-configuration.component.ts diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index df7e5013d2..2dd06c9957 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java index 4816dc1037..dcd9d4823e 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java index 0492ba9793..9c39047742 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -20,7 +20,7 @@ import lombok.Setter; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.TenantProfileData; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.msg.tools.SchedulerUtils; diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 1a0f9eca65..eeef99fff6 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -30,7 +30,8 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.TenantProfileData; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.CustomerId; @@ -127,6 +128,10 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { public void createDefaultTenantProfiles() throws Exception { tenantProfileService.findOrCreateDefaultTenantProfile(TenantId.SYS_TENANT_ID); + TenantProfileData tenantProfileData = new TenantProfileData(); + DefaultTenantProfileConfiguration configuration = new DefaultTenantProfileConfiguration(); + tenantProfileData.setConfiguration(configuration); + TenantProfile isolatedTbCoreProfile = new TenantProfile(); isolatedTbCoreProfile.setDefault(false); isolatedTbCoreProfile.setName("Isolated TB Core"); @@ -134,6 +139,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { isolatedTbCoreProfile.setDescription("Isolated TB Core tenant profile"); isolatedTbCoreProfile.setIsolatedTbCore(true); isolatedTbCoreProfile.setIsolatedTbRuleEngine(false); + isolatedTbCoreProfile.setProfileData(tenantProfileData); try { tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, isolatedTbCoreProfile); } catch (DataValidationException e) { @@ -147,6 +153,8 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { isolatedTbRuleEngineProfile.setDescription("Isolated TB Rule Engine tenant profile"); isolatedTbRuleEngineProfile.setIsolatedTbCore(false); isolatedTbRuleEngineProfile.setIsolatedTbRuleEngine(true); + isolatedTbRuleEngineProfile.setProfileData(tenantProfileData); + try { tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, isolatedTbRuleEngineProfile); } catch (DataValidationException e) { @@ -160,6 +168,8 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { isolatedTbCoreAndTbRuleEngineProfile.setDescription("Isolated TB Core and TB Rule Engine tenant profile"); isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbCore(true); isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbRuleEngine(true); + isolatedTbCoreAndTbRuleEngineProfile.setProfileData(tenantProfileData); + try { tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, isolatedTbCoreAndTbRuleEngineProfile); } catch (DataValidationException e) { diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java index a4d615ae2b..311a93bf82 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java @@ -23,7 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.TenantProfileData; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java index 32f620f869..5e5f4a09de 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java @@ -21,6 +21,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.id.TenantProfileId; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import java.io.ByteArrayInputStream; import java.io.IOException; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfileType.java b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfileType.java new file mode 100644 index 0000000000..2b0ed502ac --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfileType.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +public enum TenantProfileType { + DEFAULT +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java new file mode 100644 index 0000000000..f361ec901c --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.tenant.profile; + +import lombok.Data; +import org.thingsboard.server.common.data.TenantProfileType; + +@Data +public class DefaultTenantProfileConfiguration implements TenantProfileConfiguration { + + private long maxDevices; + private long maxAssets; + + private String transportTenantMsgRateLimit; + private String transportTenantTelemetryMsgRateLimit; + private String transportTenantTelemetryDataPointsRateLimit; + private String transportDeviceMsgRateLimit; + private String transportDeviceTelemetryMsgRateLimit; + private String transportDeviceTelemetryDataPointsRateLimit; + + private long maxTransportMessages; + private long maxTransportDataPoints; + private long maxREExecutions; + private long maxJSExecutions; + private long maxDPStorageDays; + private int maxRuleNodeExecutionsPerMessage; + + @Override + public TenantProfileType getType() { + return TenantProfileType.DEFAULT; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java new file mode 100644 index 0000000000..07b184b389 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.tenant.profile; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.thingsboard.server.common.data.TenantProfileType; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = DefaultTenantProfileConfiguration.class, name = "DEFAULT")}) +public interface TenantProfileConfiguration { + + @JsonIgnore + TenantProfileType getType(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfileData.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileData.java similarity index 91% rename from common/data/src/main/java/org/thingsboard/server/common/data/TenantProfileData.java rename to common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileData.java index 54319276f1..6336cb4ace 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfileData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileData.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data; +package org.thingsboard.server.common.data.tenant.profile; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; @@ -26,6 +26,8 @@ import java.util.Map; @Data public class TenantProfileData { + private TenantProfileConfiguration configuration; + @JsonIgnore private Map properties = new HashMap<>(); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java index 569f5446db..8e811a282f 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java @@ -18,9 +18,9 @@ package org.thingsboard.server.common.transport.limits; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.TenantProfileData; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; import org.thingsboard.server.queue.util.TbTransportComponent; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantProfileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantProfileEntity.java index 7c69f58935..7663682496 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantProfileEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantProfileEntity.java @@ -22,14 +22,13 @@ import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.TenantProfileData; import org.thingsboard.server.common.data.id.TenantProfileId; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.dao.util.mapping.JsonBinaryType; -import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java index 939e2b7426..864679ce2a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java @@ -23,20 +23,18 @@ import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.TenantProfileData; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; -import org.thingsboard.server.dao.util.mapping.JacksonUtil; import java.util.Arrays; import java.util.Collections; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java index a9917126df..22830bed8c 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java @@ -21,13 +21,12 @@ import org.junit.Test; import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.TenantProfileData; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.util.mapping.JacksonUtil; import java.util.ArrayList; import java.util.Collections; @@ -188,8 +187,8 @@ public class BaseTenantProfileServiceTest extends AbstractServiceTest { Assert.assertTrue(pageData.getData().isEmpty()); tenantProfiles.addAll(pageData.getData()); - for (int i=0;i<28;i++) { - TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile"+i); + for (int i = 0; i < 28; i++) { + TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile" + i); tenantProfiles.add(tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, tenantProfile)); } @@ -224,8 +223,8 @@ public class BaseTenantProfileServiceTest extends AbstractServiceTest { List tenantProfiles = new ArrayList<>(); - for (int i=0;i<28;i++) { - TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile"+i); + for (int i = 0; i < 28; i++) { + TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile" + i); tenantProfiles.add(tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, tenantProfile)); } diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index d2e7cb0a28..ec7ccea660 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -113,6 +113,8 @@ import { AlarmScheduleInfoComponent } from './profile/alarm/alarm-schedule-info. import { AlarmScheduleDialogComponent } from '@home/components/profile/alarm/alarm-schedule-dialog.component'; import { EditAlarmDetailsDialogComponent } from './profile/alarm/edit-alarm-details-dialog.component'; import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alarm/alarm-rule-condition-dialog.component'; +import { DefaultTenantProfileConfigurationComponent } from './profile/tenant/default-tenant-profile-configuration.component'; +import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-profile-configuration.component'; @NgModule({ declarations: @@ -182,6 +184,8 @@ import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alar FilterUserInfoDialogComponent, FilterPredicateValueComponent, TenantProfileAutocompleteComponent, + DefaultTenantProfileConfigurationComponent, + TenantProfileConfigurationComponent, TenantProfileDataComponent, TenantProfileComponent, TenantProfileDialogComponent, diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.ts index 3cdb0b3a4e..9553e23528 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.ts @@ -204,7 +204,6 @@ export class TenantProfileAutocompleteComponent implements ControlValueAccessor, createTenantProfile($event: Event, profileName: string) { $event.preventDefault(); const tenantProfile: TenantProfile = { - id: null, name: profileName }; this.openTenantProfileDialog(tenantProfile, true); diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.html index a3d504d0e0..3d853940f4 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.html @@ -16,9 +16,15 @@ -->
- - + + + +
tenant-profile.profile-configuration
+
+
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.ts index 377b6f0978..cc6ce7a6ce 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.ts @@ -62,7 +62,7 @@ export class TenantProfileDataComponent implements ControlValueAccessor, OnInit ngOnInit() { this.tenantProfileDataFormGroup = this.fb.group({ - tenantProfileData: [null, Validators.required] + configuration: [null, Validators.required] }); this.tenantProfileDataFormGroup.valueChanges.subscribe(() => { this.updateModel(); @@ -79,13 +79,13 @@ export class TenantProfileDataComponent implements ControlValueAccessor, OnInit } writeValue(value: TenantProfileData | null): void { - this.tenantProfileDataFormGroup.get('tenantProfileData').patchValue(value, {emitEvent: false}); + this.tenantProfileDataFormGroup.patchValue({configuration: value?.configuration}, {emitEvent: false}); } private updateModel() { let tenantProfileData: TenantProfileData = null; if (this.tenantProfileDataFormGroup.valid) { - tenantProfileData = this.tenantProfileDataFormGroup.getRawValue().tenantProfileData; + tenantProfileData = this.tenantProfileDataFormGroup.getRawValue(); } this.propagateChange(tenantProfileData); } diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts index 2aadb551de..7cca3ae414 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts @@ -18,7 +18,12 @@ import { Component, Inject, Input, Optional } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { TenantProfile } from '@app/shared/models/tenant.model'; +import { + createTenantProfileConfiguration, + TenantProfile, + TenantProfileData, + TenantProfileType +} from '@app/shared/models/tenant.model'; import { ActionNotificationShow } from '@app/core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; @@ -56,7 +61,9 @@ export class TenantProfileComponent extends EntityComponent { name: [entity ? entity.name : '', [Validators.required]], isolatedTbCore: [entity ? entity.isolatedTbCore : false, []], isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []], - profileData: [entity && !this.isAdd ? entity.profileData : {}, []], + profileData: [entity && !this.isAdd ? entity.profileData : { + configuration: createTenantProfileConfiguration(TenantProfileType.DEFAULT) + } as TenantProfileData, []], description: [entity ? entity.description : '', []], } ); @@ -66,7 +73,9 @@ export class TenantProfileComponent extends EntityComponent { this.entityForm.patchValue({name: entity.name}); this.entityForm.patchValue({isolatedTbCore: entity.isolatedTbCore}); this.entityForm.patchValue({isolatedTbRuleEngine: entity.isolatedTbRuleEngine}); - this.entityForm.patchValue({profileData: entity.profileData}); + this.entityForm.patchValue({profileData: !this.isAdd ? entity.profileData: { + configuration: createTenantProfileConfiguration(TenantProfileType.DEFAULT) + } as TenantProfileData}); this.entityForm.patchValue({description: entity.description}); } diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html new file mode 100644 index 0000000000..cdb4febcc0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html @@ -0,0 +1,141 @@ + +
+
+ + tenant-profile.maximum-devices + + + {{ 'tenant-profile.maximum-devices-required' | translate}} + + + {{ 'tenant-profile.maximum-devices-range' | translate}} + + + + tenant-profile.maximum-assets + + + {{ 'tenant-profile.maximum-assets-required' | translate}} + + + {{ 'tenant-profile.maximum-assets-range' | translate}} + + + + tenant-profile.max-transport-messages + + + {{ 'tenant-profile.max-transport-messages-range' | translate}} + + + {{ 'tenant-profile.max-transport-messages-required' | translate}} + + + + tenant-profile.max-transport-data-points + + + {{ 'tenant-profile.max-transport-data-points-required' | translate}} + + + {{ 'tenant-profile.max-transport-data-points-range' | translate}} + + + + tenant-profile.max-r-e-executions + + + {{ 'tenant-profile.max-r-e-executions-required' | translate}} + + + {{ 'tenant-profile.max-r-e-executions-range' | translate}} + + + + tenant-profile.max-j-s-executions + + + {{ 'tenant-profile.max-j-s-executions-required' | translate}} + + + {{ 'tenant-profile.max-j-s-executions-range' | translate}} + + + + tenant-profile.max-d-p-storage-days + + + {{ 'tenant-profile.max-d-p-storage-days-required' | translate}} + + + {{ 'tenant-profile.max-d-p-storage-days-range' | translate}} + + + + tenant-profile.max-rule-node-executions-per-message + + + {{ 'tenant-profile.max-rule-node-executions-per-message-required' | translate}} + + + {{ 'tenant-profile.max-rule-node-executions-per-message-range' | translate}} + + + + tenant-profile.transport-tenant-msg-rate-limit + + + + tenant-profile.transport-tenant-telemetry-msg-rate-limit + + + + tenant-profile.transport-tenant-telemetry-data-points-rate-limit + + + + tenant-profile.transport-device-msg-rate-limit + + + + tenant-profile.transport-device-telemetry-msg-rate-limit + + + + tenant-profile.transport-device-telemetry-data-points-rate-limit + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts new file mode 100644 index 0000000000..f83b59c35a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts @@ -0,0 +1,115 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@app/core/core.state'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { + DefaultTenantProfileConfiguration, + TenantProfileConfiguration, + TenantProfileType +} from '@shared/models/tenant.model'; +import { isDefinedAndNotNull } from '@core/utils'; + +@Component({ + selector: 'tb-default-tenant-profile-configuration', + templateUrl: './default-tenant-profile-configuration.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DefaultTenantProfileConfigurationComponent), + multi: true + }] +}) +export class DefaultTenantProfileConfigurationComponent implements ControlValueAccessor, OnInit { + + defaultTenantProfileConfigurationFormGroup: FormGroup; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private fb: FormBuilder) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.defaultTenantProfileConfigurationFormGroup = this.fb.group({ + configuration: this.fb.group({ + maxDevices: [null, [Validators.required, Validators.min(0)]], + maxAssets: [null, [Validators.required, Validators.min(0)]], + transportTenantMsgRateLimit: [null, []], + transportTenantTelemetryMsgRateLimit: [null, []], + transportTenantTelemetryDataPointsRateLimit: [null, []], + transportDeviceMsgRateLimit: [null, []], + transportDeviceTelemetryMsgRateLimit: [null, []], + transportDeviceTelemetryDataPointsRateLimit: [null, []], + maxTransportMessages: [null, [Validators.required, Validators.min(0)]], + maxTransportDataPoints: [null, [Validators.required, Validators.min(0)]], + maxREExecutions: [null, [Validators.required, Validators.min(0)]], + maxJSExecutions: [null, [Validators.required, Validators.min(0)]], + maxDPStorageDays: [null, [Validators.required, Validators.min(0)]], + maxRuleNodeExecutionsPerMessage: [null, [Validators.required, Validators.min(0)]] + }) + }); + this.defaultTenantProfileConfigurationFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.defaultTenantProfileConfigurationFormGroup.disable({emitEvent: false}); + } else { + this.defaultTenantProfileConfigurationFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: DefaultTenantProfileConfiguration | null): void { + if (isDefinedAndNotNull(value)) { + this.defaultTenantProfileConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); + } + } + + private updateModel() { + let configuration: TenantProfileConfiguration = null; + if (this.defaultTenantProfileConfigurationFormGroup.valid) { + configuration = this.defaultTenantProfileConfigurationFormGroup.getRawValue().configuration; + configuration.type = TenantProfileType.DEFAULT; + } + this.propagateChange(configuration); + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/tenant-profile-configuration.component.html new file mode 100644 index 0000000000..3204b7d2fb --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/tenant-profile-configuration.component.html @@ -0,0 +1,27 @@ + +
+
+ + + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/tenant-profile-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/tenant-profile-configuration.component.ts new file mode 100644 index 0000000000..4f5fa989d4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/tenant-profile-configuration.component.ts @@ -0,0 +1,103 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@app/core/core.state'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { deepClone } from '@core/utils'; +import { TenantProfileConfiguration, TenantProfileType } from '@shared/models/tenant.model'; + +@Component({ + selector: 'tb-tenant-profile-configuration', + templateUrl: './tenant-profile-configuration.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TenantProfileConfigurationComponent), + multi: true + }] +}) +export class TenantProfileConfigurationComponent implements ControlValueAccessor, OnInit { + + tenantProfileType = TenantProfileType; + + tenantProfileConfigurationFormGroup: FormGroup; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + type: TenantProfileType; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private fb: FormBuilder) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.tenantProfileConfigurationFormGroup = this.fb.group({ + configuration: [null, Validators.required] + }); + this.tenantProfileConfigurationFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.tenantProfileConfigurationFormGroup.disable({emitEvent: false}); + } else { + this.tenantProfileConfigurationFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: TenantProfileConfiguration | null): void { + this.type = value?.type; + const configuration = deepClone(value); + if (configuration) { + delete configuration.type; + } + this.tenantProfileConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); + } + + private updateModel() { + let configuration: TenantProfileConfiguration = null; + if (this.tenantProfileConfigurationFormGroup.valid) { + configuration = this.tenantProfileConfigurationFormGroup.getRawValue().configuration; + configuration.type = this.type; + } + this.propagateChange(configuration); + } +} diff --git a/ui-ngx/src/app/shared/models/tenant.model.ts b/ui-ngx/src/app/shared/models/tenant.model.ts index 378dca7c25..ad946efdb7 100644 --- a/ui-ngx/src/app/shared/models/tenant.model.ts +++ b/ui-ngx/src/app/shared/models/tenant.model.ts @@ -19,8 +19,59 @@ import { TenantId } from './id/tenant-id'; import { TenantProfileId } from '@shared/models/id/tenant-profile-id'; import { BaseData } from '@shared/models/base-data'; +export enum TenantProfileType { + DEFAULT = 'DEFAULT' +} + +export interface DefaultTenantProfileConfiguration { + maxDevices: number; + maxAssets: number; + + transportTenantMsgRateLimit?: string; + transportTenantTelemetryMsgRateLimit?: string; + transportTenantTelemetryDataPointsRateLimit?: string; + transportDeviceMsgRateLimit?: string; + transportDeviceTelemetryMsgRateLimit?: string; + transportDeviceTelemetryDataPointsRateLimit?: string; + + maxTransportMessages: number; + maxTransportDataPoints: number; + maxREExecutions: number; + maxJSExecutions: number; + maxDPStorageDays: number; + maxRuleNodeExecutionsPerMessage: number; +} + +export type TenantProfileConfigurations = DefaultTenantProfileConfiguration; + +export interface TenantProfileConfiguration extends TenantProfileConfigurations { + type: TenantProfileType; +} + +export function createTenantProfileConfiguration(type: TenantProfileType): TenantProfileConfiguration { + let configuration: TenantProfileConfiguration = null; + if (type) { + switch (type) { + case TenantProfileType.DEFAULT: + const defaultConfiguration: DefaultTenantProfileConfiguration = { + maxDevices: 0, + maxAssets: 0, + maxTransportMessages: 0, + maxTransportDataPoints: 0, + maxREExecutions: 0, + maxJSExecutions: 0, + maxDPStorageDays: 0, + maxRuleNodeExecutionsPerMessage: 0 + }; + configuration = {...defaultConfiguration, type: TenantProfileType.DEFAULT}; + break; + } + } + return configuration; +} + export interface TenantProfileData { - [key: string]: string; + configuration: TenantProfileConfiguration; } export interface TenantProfile extends BaseData { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 082c2241b4..ca88616110 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1923,6 +1923,7 @@ "name": "Name", "name-required": "Name is required.", "data": "Profile data", + "profile-configuration": "Profile configuration", "description": "Description", "default": "Default", "delete-tenant-profile-title": "Are you sure you want to delete the tenant profile '{{tenantProfileName}}'?", @@ -1932,7 +1933,37 @@ "set-default-tenant-profile-title": "Are you sure you want to make the tenant profile '{{tenantProfileName}}' default?", "set-default-tenant-profile-text": "After the confirmation the tenant profile will be marked as default and will be used for new tenants with no profile specified.", "no-tenant-profiles-found": "No tenant profiles found.", - "create-new-tenant-profile": "Create a new one!" + "create-new-tenant-profile": "Create a new one!", + "maximum-devices": "Maximum number of devices (0 - unlimited)", + "maximum-devices-required": "Maximum number of devices is required.", + "maximum-devices-range": "Minimum number of devices can't be negative", + "maximum-assets": "Maximum number of assets (0 - unlimited)", + "maximum-assets-required": "Maximum number of assets is required.", + "maximum-assets-range": "Maximum number of assets can't be negative", + "transport-tenant-msg-rate-limit": "Transport tenant messages rate limit.", + "transport-tenant-telemetry-msg-rate-limit": "Transport tenant telemetry messages rate limit.", + "transport-tenant-telemetry-data-points-rate-limit": "Transport tenant telemetry data points rate limit.", + "transport-device-msg-rate-limit": "Transport device messages rate limit.", + "transport-device-telemetry-msg-rate-limit": "Transport device telemetry messages rate limit.", + "transport-device-telemetry-data-points-rate-limit": "Transport device telemetry data points rate limit.", + "max-transport-messages": "Maximum number of transport messages (0 - unlimited)", + "max-transport-messages-required": "Maximum number of transport messages is required.", + "max-transport-messages-range": "Maximum number of transport messages can't be negative", + "max-transport-data-points": "Maximum number of transport data points (0 - unlimited)", + "max-transport-data-points-required": "Maximum number of transport data points is required.", + "max-transport-data-points-range": "Minimum number of transport data points can't be negative", + "max-r-e-executions": "Maximum number of Rule Engine executions (0 - unlimited)", + "max-r-e-executions-required": "Maximum number of Rule Engine executions is required.", + "max-r-e-executions-range": "Minimum number of Rule Engine executions can't be negative", + "max-j-s-executions": "Maximum number of JavaScript executions (0 - unlimited)", + "max-j-s-executions-required": "Maximum number of JavaScript executions is required.", + "max-j-s-executions-range": "Minimum number of JavaScript executions can't be negative", + "max-d-p-storage-days": "Maximum number of data points storage days (0 - unlimited)", + "max-d-p-storage-days-required": "Maximum number of data points storage days is required.", + "max-d-p-storage-days-range": "Minimum number of data points storage days can't be negative", + "max-rule-node-executions-per-message": "Maximum number of rule node executions per message (0 - unlimited)", + "max-rule-node-executions-per-message-required": "Maximum number of rule node executions per message is required.", + "max-rule-node-executions-per-message-range": "Minimum number of rule node executions per message can't be negative" }, "timeinterval": { "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", From 0e0ab6ca3982d7e20c47267e1967010ad9e607f7 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 22 Oct 2020 15:56:31 +0300 Subject: [PATCH 242/463] Improvements to API State --- .../server/service/apiusage/ApiFeature.java | 5 + .../DefaultTbApiUsageStateService.java | 92 ++++++++++++++----- .../apiusage/TbApiUsageStateService.java | 7 +- .../service/apiusage/TenantApiUsageState.java | 43 ++++++--- .../queue/DefaultTbClusterService.java | 27 ++++-- .../service/queue/TbClusterService.java | 3 + .../processing/AbstractConsumerService.java | 4 +- .../transport/DefaultTransportApiService.java | 22 +++-- .../server/common/data/ApiUsageRecordKey.java | 1 - .../server/common/data/ApiUsageState.java | 30 ++++-- .../MultipleTbQueueCallbackWrapper.java | 45 +++++++++ common/queue/src/main/proto/queue.proto | 1 + .../transport/mqtt/MqttTransportHandler.java | 9 +- .../common/transport/TransportService.java | 4 +- .../DefaultTransportRateLimitService.java | 11 ++- .../limits/TransportRateLimitService.java | 1 + .../limits/TransportRateLimitType.java | 3 +- .../service/DefaultTransportService.java | 17 ++-- .../DefaultTransportTenantProfileCache.java | 26 ++++-- .../server/dao/model/ModelConstants.java | 4 + .../dao/model/sql/ApiUsageStateEntity.java | 19 ++++ ...mpl.java => ApiUsageStateServiceImpl.java} | 4 +- .../resources/sql/schema-entities-hsql.sql | 4 + .../main/resources/sql/schema-entities.sql | 4 + .../service/BaseApiUsageStateServiceTest.java | 13 +++ 25 files changed, 310 insertions(+), 89 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/common/MultipleTbQueueCallbackWrapper.java rename dao/src/main/java/org/thingsboard/server/dao/usagerecord/{ApiApiUsageStateServiceImpl.java => ApiUsageStateServiceImpl.java} (96%) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java b/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java new file mode 100644 index 0000000000..a893a55873 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java @@ -0,0 +1,5 @@ +package org.thingsboard.server.service.apiusage; + +public enum ApiFeature { + TRANSPORT, DB, RE, JS +} diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index df7e5013d2..c5e6cab16d 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.apiusage; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; @@ -33,15 +34,18 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; +import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.profile.TbTenantProfileCache; +import org.thingsboard.server.service.queue.TbClusterService; import javax.annotation.PostConstruct; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -57,12 +61,17 @@ import java.util.concurrent.locks.ReentrantLock; public class DefaultTbApiUsageStateService implements TbApiUsageStateService { public static final String HOURLY = "HOURLY_"; + private final TbClusterService clusterService; private final PartitionService partitionService; private final ApiUsageStateService apiUsageStateService; private final TimeseriesService tsService; private final SchedulerComponent scheduler; private final TbTenantProfileCache tenantProfileCache; - private final Map tenantStates = new ConcurrentHashMap<>(); + + // Tenants that should be processed on this server + private final Map myTenantStates = new ConcurrentHashMap<>(); + // Tenants that should be processed on other servers + private final Map otherTenantStates = new ConcurrentHashMap<>(); @Value("${usage.stats.report.enabled:true}") private boolean enabled; @@ -72,7 +81,13 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { private final Lock updateLock = new ReentrantLock(); - public DefaultTbApiUsageStateService(PartitionService partitionService, ApiUsageStateService apiUsageStateService, TimeseriesService tsService, SchedulerComponent scheduler, TbTenantProfileCache tenantProfileCache) { + public DefaultTbApiUsageStateService(TbClusterService clusterService, + PartitionService partitionService, + ApiUsageStateService apiUsageStateService, + TimeseriesService tsService, + SchedulerComponent scheduler, + TbTenantProfileCache tenantProfileCache) { + this.clusterService = clusterService; this.partitionService = partitionService; this.apiUsageStateService = apiUsageStateService; this.tsService = tsService; @@ -93,7 +108,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB())); TenantApiUsageState tenantState; List updatedEntries; - boolean stateUpdated = false; + Map result = new HashMap<>(); updateLock.lock(); try { tenantState = getOrFetchState(tenantId); @@ -110,32 +125,49 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.name(), newValue))); long newHourlyValue = tenantState.addToHourly(recordKey, kvProto.getValue()); updatedEntries.add(new BasicTsKvEntry(hourTs, new LongDataEntry(HOURLY + recordKey.name(), newHourlyValue))); - stateUpdated |= tenantState.checkStateUpdatedDueToThreshold(recordKey); + Pair update = tenantState.checkStateUpdatedDueToThreshold(recordKey); + if (update != null) { + result.put(update.getFirst(), update.getSecond()); + } } } finally { updateLock.unlock(); } tsService.save(tenantId, tenantState.getApiUsageState().getId(), updatedEntries, 0L); - if (stateUpdated) { - // Save new state into the database; - apiUsageStateService.update(tenantState.getApiUsageState()); - //TODO: clear cache on cluster repartition. - //TODO: update profiles on tenant and profile updates. - //TODO: broadcast to everyone notifications about enabled/disabled features. + if (!result.isEmpty()) { + persistAndNotify(tenantState, result); } } @Override public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { if (partitionChangeEvent.getServiceType().equals(ServiceType.TB_CORE)) { - tenantStates.entrySet().removeIf(entry -> !partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition()); + myTenantStates.entrySet().removeIf(entry -> !partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition()); + otherTenantStates.entrySet().removeIf(entry -> partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition()); } } @Override - public TenantApiUsageState getApiUsageState(TenantId tenantId) { - //We should always get it from the map of from the database; - return null; + public ApiUsageState getApiUsageState(TenantId tenantId) { + if (partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) { + TenantApiUsageState state = getOrFetchState(tenantId); + return state.getApiUsageState(); + } else { + ApiUsageState state = otherTenantStates.get(tenantId); + if (state == null) { + updateLock.lock(); + try { + state = otherTenantStates.get(tenantId); + if (state == null) { + state = apiUsageStateService.findTenantApiUsageState(tenantId); + otherTenantStates.put(tenantId, state); + } + } finally { + updateLock.unlock(); + } + } + return state; + } } @Override @@ -143,7 +175,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { TenantProfile tenantProfile = tenantProfileCache.get(tenantProfileId); updateLock.lock(); try { - tenantStates.values().forEach(state -> { + myTenantStates.values().forEach(state -> { if (tenantProfile.getId().equals(state.getTenantProfileId())) { updateTenantState(state, tenantProfile); } @@ -158,7 +190,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { TenantProfile tenantProfile = tenantProfileCache.get(tenantId); updateLock.lock(); try { - TenantApiUsageState state = tenantStates.get(tenantId); + TenantApiUsageState state = myTenantStates.get(tenantId); if (state != null && !state.getTenantProfileId().equals(tenantProfile.getId())) { updateTenantState(state, tenantProfile); } @@ -167,12 +199,28 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } + @Override + public void onApiUsageStateUpdate(TenantId tenantId) { + + } private void updateTenantState(TenantApiUsageState state, TenantProfile tenantProfile) { state.setTenantProfileData(tenantProfile.getProfileData()); - if (state.checkStateUpdatedDueToThresholds()) { - apiUsageStateService.update(state.getApiUsageState()); - //TODO: send notification to cluster; + Map result = state.checkStateUpdatedDueToThresholds(); + if (!result.isEmpty()) { + persistAndNotify(state, result); + } + } + + private void persistAndNotify(TenantApiUsageState state, Map result) { + // TODO: + // 1. Broadcast to everyone notifications about enabled/disabled features. + // 2. Report rule engine and js executor metrics + // 4. UI for configuration of the thresholds + // 5. Max rule node executions per message. + apiUsageStateService.update(state.getApiUsageState()); + if (result.containsKey(ApiFeature.TRANSPORT)) { + clusterService.onApiStateChange(state.getApiUsageState(), null); } } @@ -180,7 +228,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { updateLock.lock(); try { long now = System.currentTimeMillis(); - tenantStates.values().forEach(state -> { + myTenantStates.values().forEach(state -> { if ((state.getNextCycleTs() > now) && (state.getNextCycleTs() - now < TimeUnit.HOURS.toMillis(1))) { state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth()); } @@ -191,7 +239,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } private TenantApiUsageState getOrFetchState(TenantId tenantId) { - TenantApiUsageState tenantState = tenantStates.get(tenantId); + TenantApiUsageState tenantState = myTenantStates.get(tenantId); if (tenantState == null) { ApiUsageState dbStateEntity = apiUsageStateService.findTenantApiUsageState(tenantId); if (dbStateEntity == null) { @@ -223,7 +271,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } } - tenantStates.put(tenantId, tenantState); + myTenantStates.put(tenantId, tenantState); } catch (InterruptedException | ExecutionException e) { log.warn("[{}] Failed to fetch api usage state from db.", tenantId, e); } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java index 4816dc1037..e19c0a4154 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -16,6 +16,7 @@ package org.thingsboard.server.service.apiusage; import org.springframework.context.ApplicationListener; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.msg.queue.TbCallback; @@ -27,9 +28,11 @@ public interface TbApiUsageStateService extends ApplicationListener msg, TbCallback callback); - TenantApiUsageState getApiUsageState(TenantId tenantId); + ApiUsageState getApiUsageState(TenantId tenantId); void onTenantProfileUpdate(TenantProfileId tenantProfileId); void onTenantUpdate(TenantId tenantId); + + void onApiUsageStateUpdate(TenantId tenantId); } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java index 0492ba9793..71bc3f1f6d 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -17,14 +17,17 @@ package org.thingsboard.server.service.apiusage; import lombok.Getter; import lombok.Setter; +import org.springframework.data.util.Pair; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfileData; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.msg.tools.SchedulerUtils; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -103,11 +106,17 @@ public class TenantApiUsageState { return Long.parseLong((String) threshold); } else if (threshold instanceof Long) { return (Long) threshold; + } else if (threshold instanceof Integer) { + return (Integer) threshold; } } return 0L; } + public TenantId getTenantId() { + return apiUsageState.getTenantId(); + } + public EntityId getEntityId() { return apiUsageState.getEntityId(); } @@ -121,7 +130,7 @@ public class TenantApiUsageState { } public boolean isRuleEngineEnabled() { - return apiUsageState.isRuleEngineEnabled(); + return apiUsageState.isReExecEnabled(); } public boolean isJsExecEnabled() { @@ -137,7 +146,7 @@ public class TenantApiUsageState { } public void setRuleEngineEnabled(boolean ruleEngineEnabled) { - apiUsageState.setRuleEngineEnabled(ruleEngineEnabled); + apiUsageState.setReExecEnabled(ruleEngineEnabled); } public void setJsExecEnabled(boolean jsExecEnabled) { @@ -147,7 +156,6 @@ public class TenantApiUsageState { public boolean isFeatureEnabled(ApiUsageRecordKey recordKey) { switch (recordKey) { case MSG_COUNT: - case MSG_BYTES_COUNT: case DP_TRANSPORT_COUNT: return isTransportEnabled(); case RE_EXEC_COUNT: @@ -161,38 +169,47 @@ public class TenantApiUsageState { } } - public boolean setFeatureValue(ApiUsageRecordKey recordKey, boolean value) { + public ApiFeature setFeatureValue(ApiUsageRecordKey recordKey, boolean value) { + ApiFeature feature = null; boolean currentValue = isFeatureEnabled(recordKey); switch (recordKey) { case MSG_COUNT: - case MSG_BYTES_COUNT: case DP_TRANSPORT_COUNT: + feature = ApiFeature.TRANSPORT; setTransportEnabled(value); break; case RE_EXEC_COUNT: + feature = ApiFeature.RE; setRuleEngineEnabled(value); break; case DP_STORAGE_COUNT: + feature = ApiFeature.DB; setDbStorageEnabled(value); break; case JS_EXEC_COUNT: + feature = ApiFeature.JS; setJsExecEnabled(value); break; } - return currentValue == value; + return currentValue == value ? null : feature; } - public boolean checkStateUpdatedDueToThresholds() { - boolean update = false; + public Map checkStateUpdatedDueToThresholds() { + Map result = new HashMap<>(); for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { - update |= checkStateUpdatedDueToThreshold(key); + Pair featureUpdate = checkStateUpdatedDueToThreshold(key); + if (featureUpdate != null) { + result.put(featureUpdate.getFirst(), featureUpdate.getSecond()); + } } - return update; + return result; } - public boolean checkStateUpdatedDueToThreshold(ApiUsageRecordKey recordKey) { + public Pair checkStateUpdatedDueToThreshold(ApiUsageRecordKey recordKey) { long value = get(recordKey); long threshold = getProfileThreshold(recordKey); - return setFeatureValue(recordKey, threshold == 0 || value < threshold); + boolean featureValue = threshold == 0 || value < threshold; + ApiFeature feature = setFeatureValue(recordKey, featureValue); + return feature != null ? Pair.of(feature, featureValue) : null; } } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index 9451b58e9f..b26baf8d2e 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasName; @@ -47,6 +48,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotifica import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.common.MultipleTbQueueCallbackWrapper; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; @@ -206,6 +208,12 @@ public class DefaultTbClusterService implements TbClusterService { onEntityChange(TenantId.SYS_TENANT_ID, tenant.getId(), tenant, callback); } + @Override + public void onApiStateChange(ApiUsageState apiUsageState, TbQueueCallback callback) { + onEntityChange(apiUsageState.getTenantId(), apiUsageState.getId(), apiUsageState, callback); + broadcast(new ComponentLifecycleMsg(apiUsageState.getTenantId(), apiUsageState.getId(), ComponentLifecycleEvent.UPDATED)); + } + @Override public void onDeviceProfileDelete(DeviceProfile entity, TbQueueCallback callback) { onEntityDelete(entity.getTenantId(), entity.getId(), entity.getName(), callback); @@ -221,13 +229,14 @@ public class DefaultTbClusterService implements TbClusterService { onEntityDelete(TenantId.SYS_TENANT_ID, entity.getId(), entity.getName(), callback); } - public void onEntityChange(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) { - log.trace("[{}][{}][{}] Processing [{}] change event", tenantId, entityid.getEntityType(), entityid.getId(), entity.getName()); + public void onEntityChange(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) { + String entityName = (entity instanceof HasName) ? ((HasName) entity).getName() : entity.getClass().getName(); + log.trace("[{}][{}][{}] Processing [{}] change event", tenantId, entityid.getEntityType(), entityid.getId(), entityName); TransportProtos.EntityUpdateMsg entityUpdateMsg = TransportProtos.EntityUpdateMsg.newBuilder() .setEntityType(entityid.getEntityType().name()) .setData(ByteString.copyFrom(encodingService.encode(entity))).build(); ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setEntityUpdateMsg(entityUpdateMsg).build(); - broadcast(transportMsg); + broadcast(transportMsg, callback); } private void onEntityDelete(TenantId tenantId, EntityId entityId, String name, TbQueueCallback callback) { @@ -238,15 +247,16 @@ public class DefaultTbClusterService implements TbClusterService { .setEntityIdLSB(entityId.getId().getLeastSignificantBits()) .build(); ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setEntityDeleteMsg(entityDeleteMsg).build(); - broadcast(transportMsg); + broadcast(transportMsg, callback); } - private void broadcast(ToTransportMsg transportMsg) { + private void broadcast(ToTransportMsg transportMsg, TbQueueCallback callback) { TbQueueProducer> toTransportNfProducer = producerProvider.getTransportNotificationsMsgProducer(); Set tbTransportServices = partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT); + TbQueueCallback proxyCallback = callback != null ? new MultipleTbQueueCallbackWrapper(tbTransportServices.size(), callback) : null; for (String transportServiceId : tbTransportServices) { TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_TRANSPORT, transportServiceId); - toTransportNfProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), transportMsg), null); + toTransportNfProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), transportMsg), proxyCallback); toTransportNfs.incrementAndGet(); } } @@ -256,7 +266,8 @@ public class DefaultTbClusterService implements TbClusterService { TbQueueProducer> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer(); Set tbRuleEngineServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)); if (msg.getEntityId().getEntityType().equals(EntityType.TENANT) - || msg.getEntityId().getEntityType().equals(EntityType.DEVICE_PROFILE)) { + || msg.getEntityId().getEntityType().equals(EntityType.DEVICE_PROFILE) + || msg.getEntityId().getEntityType().equals(EntityType.API_USAGE_STATE)) { TbQueueProducer> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer(); Set tbCoreServices = partitionService.getAllServiceIds(ServiceType.TB_CORE); for (String serviceId : tbCoreServices) { diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java index 838dbe0eef..b36af16716 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.queue; import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; @@ -63,4 +64,6 @@ public interface TbClusterService { void onTenantChange(Tenant tenant, TbQueueCallback callback); void onTenantDelete(Tenant tenant, TbQueueCallback callback); + + void onApiStateChange(ApiUsageState apiUsageState, TbQueueCallback callback); } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java index 0532d2f9c8..02fe877cd0 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -161,6 +161,8 @@ public abstract class AbstractConsumerService deviceCreationLocks = new ConcurrentHashMap<>(); public DefaultTransportApiService(TbDeviceProfileCache deviceProfileCache, - TbTenantProfileCache tenantProfileCache, DeviceService deviceService, + TbTenantProfileCache tenantProfileCache, TbApiUsageStateService apiUsageStateService, DeviceService deviceService, RelationService relationService, DeviceCredentialsService deviceCredentialsService, DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService, TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService, DeviceProvisionService deviceProvisionService) { this.deviceProfileCache = deviceProfileCache; this.tenantProfileCache = tenantProfileCache; + this.apiUsageStateService = apiUsageStateService; this.deviceService = deviceService; this.relationService = relationService; this.deviceCredentialsService = deviceCredentialsService; @@ -316,18 +319,21 @@ public class DefaultTransportApiService implements TransportApiService { private ListenableFuture handle(GetEntityProfileRequestMsg requestMsg) { EntityType entityType = EntityType.valueOf(requestMsg.getEntityType()); UUID entityUuid = new UUID(requestMsg.getEntityIdMSB(), requestMsg.getEntityIdLSB()); - ByteString data; + GetEntityProfileResponseMsg.Builder builder = GetEntityProfileResponseMsg.newBuilder(); if (entityType.equals(EntityType.DEVICE_PROFILE)) { DeviceProfileId deviceProfileId = new DeviceProfileId(entityUuid); DeviceProfile deviceProfile = deviceProfileCache.find(deviceProfileId); - data = ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile)); + builder.setData(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile))); } else if (entityType.equals(EntityType.TENANT)) { - TenantProfile tenantProfile = tenantProfileCache.get(new TenantId(entityUuid)); - data = ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile)); + TenantId tenantId = new TenantId(entityUuid); + TenantProfile tenantProfile = tenantProfileCache.get(tenantId); + ApiUsageState state = apiUsageStateService.getApiUsageState(tenantId); + builder.setData(ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile))); + builder.setApiState(ByteString.copyFrom(dataDecodingEncodingService.encode(state))); } else { throw new RuntimeException("Invalid entity profile request: " + entityType); } - return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setEntityProfileResponseMsg(GetEntityProfileResponseMsg.newBuilder().setData(data).build()).build()); + return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setEntityProfileResponseMsg(builder).build()); } private ListenableFuture getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java index 9c8690a88f..bc730e7e5a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java @@ -18,7 +18,6 @@ package org.thingsboard.server.common.data; public enum ApiUsageRecordKey { MSG_COUNT, - MSG_BYTES_COUNT, DP_TRANSPORT_COUNT, DP_STORAGE_COUNT, RE_EXEC_COUNT, diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java index 05186c7599..5144861790 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java @@ -29,18 +29,24 @@ public class ApiUsageState extends BaseData implements HasTenan private static final long serialVersionUID = 8250339805336035966L; - @Getter @Setter + @Getter + @Setter private TenantId tenantId; - @Getter @Setter + @Getter + @Setter private EntityId entityId; - @Getter @Setter - private boolean transportEnabled; - @Getter @Setter - private boolean dbStorageEnabled; - @Getter @Setter - private boolean ruleEngineEnabled; - @Getter @Setter - private boolean jsExecEnabled; + @Getter + @Setter + private boolean transportEnabled = true; + @Getter + @Setter + private boolean dbStorageEnabled = true; + @Getter + @Setter + private boolean reExecEnabled = true; + @Getter + @Setter + private boolean jsExecEnabled = true; public ApiUsageState() { super(); @@ -54,5 +60,9 @@ public class ApiUsageState extends BaseData implements HasTenan super(ur); this.tenantId = ur.getTenantId(); this.entityId = ur.getEntityId(); + this.transportEnabled = ur.isTransportEnabled(); + this.dbStorageEnabled = ur.isDbStorageEnabled(); + this.reExecEnabled = ur.isReExecEnabled(); + this.jsExecEnabled = ur.isJsExecEnabled(); } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/common/MultipleTbQueueCallbackWrapper.java b/common/queue/src/main/java/org/thingsboard/server/queue/common/MultipleTbQueueCallbackWrapper.java new file mode 100644 index 0000000000..ddedb3a606 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/common/MultipleTbQueueCallbackWrapper.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.common; + +import org.thingsboard.server.common.msg.queue.RuleEngineException; +import org.thingsboard.server.queue.TbQueueCallback; +import org.thingsboard.server.queue.TbQueueMsgMetadata; + +import java.util.concurrent.atomic.AtomicInteger; + +public class MultipleTbQueueCallbackWrapper implements TbQueueCallback { + + private final AtomicInteger tbQueueCallbackCount; + private final TbQueueCallback callback; + + public MultipleTbQueueCallbackWrapper(int tbQueueCallbackCount, TbQueueCallback callback) { + this.tbQueueCallbackCount = new AtomicInteger(tbQueueCallbackCount); + this.callback = callback; + } + + @Override + public void onSuccess(TbQueueMsgMetadata metadata) { + if (tbQueueCallbackCount.decrementAndGet() <= 0) { + callback.onSuccess(metadata); + } + } + + @Override + public void onFailure(Throwable t) { + callback.onFailure(new RuleEngineException(t.getMessage())); + } +} diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index f716bd90ae..4b5a6028e6 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -186,6 +186,7 @@ message GetEntityProfileRequestMsg { message GetEntityProfileResponseMsg { string entityType = 1; bytes data = 2; + bytes apiState = 3; } message EntityUpdateMsg { diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 2c4b2db7df..e70da7029e 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -45,6 +45,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.msg.EncryptionUtil; +import org.thingsboard.server.common.msg.tools.TbRateLimitsException; import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; @@ -630,7 +631,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @Override public void onError(Throwable e) { - log.warn("[{}] Failed to submit session event", sessionId, e); + if (e instanceof TbRateLimitsException) { + log.trace("[{}] Failed to submit session event", sessionId, e); + } else { + log.warn("[{}] Failed to submit session event", sessionId, e); + } ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE)); ctx.close(); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 4872d8e477..3b775b9f26 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -45,7 +45,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509Ce */ public interface TransportService { - GetEntityProfileResponseMsg getRoutingInfo(GetEntityProfileRequestMsg msg); + GetEntityProfileResponseMsg getEntityProfile(GetEntityProfileRequestMsg msg); void process(DeviceTransportType transportType, ValidateDeviceTokenRequestMsg msg, TransportServiceCallback callback); @@ -62,8 +62,6 @@ public interface TransportService { void process(ProvisionDeviceRequestMsg msg, TransportServiceCallback callback); - void onProfileUpdate(DeviceProfile deviceProfile); - boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback); boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints, TransportRateLimitType... limits); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java index 569f5446db..9663ae2e47 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -33,6 +33,7 @@ import java.util.concurrent.ConcurrentMap; @Slf4j public class DefaultTransportRateLimitService implements TransportRateLimitService { + private final ConcurrentMap tenantAllowed = new ConcurrentHashMap<>(); private final ConcurrentMap perTenantLimits = new ConcurrentHashMap<>(); private final ConcurrentMap perDeviceLimits = new ConcurrentHashMap<>(); @@ -46,6 +47,9 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi @Override public TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits) { + if (!tenantAllowed.getOrDefault(tenantId, Boolean.TRUE)) { + return TransportRateLimitType.TENANT_ADDED_TO_DISABLED_LIST; + } TransportRateLimit[] tenantLimits = getTenantRateLimits(tenantId); TransportRateLimit[] deviceLimits = getDeviceRateLimits(tenantId, deviceId); for (TransportRateLimitType limitType : limits) { @@ -85,6 +89,11 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi perDeviceLimits.remove(deviceId); } + @Override + public void update(TenantId tenantId, boolean allowed) { + tenantAllowed.put(tenantId, allowed); + } + private void mergeLimits(TenantId tenantId, TransportRateLimit[] newRateLimits) { TransportRateLimit[] oldRateLimits = perTenantLimits.get(tenantId); if (oldRateLimits == null) { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java index a97fbfc61d..2ab1ec38ac 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java @@ -31,4 +31,5 @@ public interface TransportRateLimitService { void remove(DeviceId deviceId); + void update(TenantId tenantId, boolean transportEnabled); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java index a3e6da6683..038de86e84 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -19,6 +19,7 @@ import lombok.Getter; public enum TransportRateLimitType { + TENANT_ADDED_TO_DISABLED_LIST("general.tenant.disabled", true, false), TENANT_MAX_MSGS("transport.tenant.msg", true, true), TENANT_TELEMETRY_MSGS("transport.tenant.telemetry", true, true), TENANT_MAX_DATA_POINTS("transport.tenant.dataPoints", true, false), diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 010a3cd899..326f996d14 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; @@ -237,7 +238,7 @@ public class DefaultTransportService implements TransportService { } @Override - public TransportProtos.GetEntityProfileResponseMsg getRoutingInfo(TransportProtos.GetEntityProfileRequestMsg msg) { + public TransportProtos.GetEntityProfileResponseMsg getEntityProfile(TransportProtos.GetEntityProfileRequestMsg msg) { TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setEntityProfileRequestMsg(msg).build()); try { @@ -641,8 +642,7 @@ public class DefaultTransportService implements TransportService { onProfileUpdate(deviceProfile); } } else if (EntityType.TENANT_PROFILE.equals(entityType)) { - TenantProfileUpdateResult update = tenantProfileCache.put(msg.getData()); - rateLimitService.update(update); + rateLimitService.update(tenantProfileCache.put(msg.getData())); } else if (EntityType.TENANT.equals(entityType)) { Optional profileOpt = dataDecodingEncodingService.decode(msg.getData().toByteArray()); if (profileOpt.isPresent()) { @@ -652,6 +652,12 @@ public class DefaultTransportService implements TransportService { rateLimitService.update(tenant.getId()); } } + } else if (EntityType.API_USAGE_STATE.equals(entityType)) { + Optional stateOpt = dataDecodingEncodingService.decode(msg.getData().toByteArray()); + if (stateOpt.isPresent()) { + ApiUsageState apiUsageState = stateOpt.get(); + rateLimitService.update(apiUsageState.getTenantId(), apiUsageState.isTransportEnabled()); + } } } else if (toSessionMsg.hasEntityDeleteMsg()) { TransportProtos.EntityDeleteMsg msg = toSessionMsg.getEntityDeleteMsg(); @@ -673,8 +679,7 @@ public class DefaultTransportService implements TransportService { } } - @Override - public void onProfileUpdate(DeviceProfile deviceProfile) { + private void onProfileUpdate(DeviceProfile deviceProfile) { long deviceProfileIdMSB = deviceProfile.getId().getId().getMostSignificantBits(); long deviceProfileIdLSB = deviceProfile.getId().getId().getLeastSignificantBits(); sessions.forEach((id, md) -> { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java index 784f7b3ae8..18c1512a27 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -18,21 +18,19 @@ package org.thingsboard.server.common.transport.service; import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportTenantProfileCache; +import org.thingsboard.server.common.transport.limits.TransportRateLimitService; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.discovery.TenantRoutingInfo; -import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; import org.thingsboard.server.queue.util.TbTransportComponent; import java.util.Collections; @@ -54,8 +52,15 @@ public class DefaultTransportTenantProfileCache implements TransportTenantProfil private final ConcurrentMap> tenantProfileIds = new ConcurrentHashMap<>(); private final DataDecodingEncodingService dataDecodingEncodingService; + private TransportRateLimitService rateLimitService; private TransportService transportService; + @Lazy + @Autowired + public void setRateLimitService(TransportRateLimitService rateLimitService) { + this.rateLimitService = rateLimitService; + } + @Lazy @Autowired public void setTransportService(TransportService transportService) { @@ -77,7 +82,8 @@ public class DefaultTransportTenantProfileCache implements TransportTenantProfil if (profileOpt.isPresent()) { TenantProfile newProfile = profileOpt.get(); log.trace("[{}] put: {}", newProfile.getId(), newProfile); - return new TenantProfileUpdateResult(newProfile, tenantProfileIds.get(newProfile.getId())); + Set affectedTenants = tenantProfileIds.get(newProfile.getId()); + return new TenantProfileUpdateResult(newProfile, affectedTenants != null ? affectedTenants : Collections.emptySet()); } else { log.warn("Failed to decode profile: {}", profileBody.toString()); return new TenantProfileUpdateResult(null, Collections.emptySet()); @@ -127,8 +133,8 @@ public class DefaultTransportTenantProfileCache implements TransportTenantProfil .setEntityIdMSB(tenantId.getId().getMostSignificantBits()) .setEntityIdLSB(tenantId.getId().getLeastSignificantBits()) .build(); - TransportProtos.GetEntityProfileResponseMsg routingInfo = transportService.getRoutingInfo(msg); - Optional profileOpt = dataDecodingEncodingService.decode(routingInfo.getData().toByteArray()); + TransportProtos.GetEntityProfileResponseMsg entityProfileMsg = transportService.getEntityProfile(msg); + Optional profileOpt = dataDecodingEncodingService.decode(entityProfileMsg.getData().toByteArray()); if (profileOpt.isPresent()) { profile = profileOpt.get(); TenantProfile existingProfile = profiles.get(profile.getId()); @@ -140,9 +146,11 @@ public class DefaultTransportTenantProfileCache implements TransportTenantProfil tenantProfileIds.computeIfAbsent(profile.getId(), id -> ConcurrentHashMap.newKeySet()).add(tenantId); tenantIds.put(tenantId, profile.getId()); } else { - log.warn("[{}] Can't decode tenant profile: {}", tenantId, routingInfo.getData()); + log.warn("[{}] Can't decode tenant profile: {}", tenantId, entityProfileMsg.getData()); throw new RuntimeException("Can't decode tenant profile!"); } + Optional apiStateOpt = dataDecodingEncodingService.decode(entityProfileMsg.getApiState().toByteArray()); + apiStateOpt.ifPresent(apiUsageState -> rateLimitService.update(tenantId, apiUsageState.isTransportEnabled())); } } finally { tenantProfileFetchLock.unlock(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index a0f5b45f02..a370fbe7f3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -445,6 +445,10 @@ public class ModelConstants { public static final String API_USAGE_STATE_TENANT_ID_COLUMN = TENANT_ID_PROPERTY; public static final String API_USAGE_STATE_ENTITY_TYPE_COLUMN = ENTITY_TYPE_COLUMN; public static final String API_USAGE_STATE_ENTITY_ID_COLUMN = ENTITY_ID_COLUMN; + public static final String API_USAGE_STATE_TRANSPORT_ENABLED_COLUMN = "transport_enabled"; + public static final String API_USAGE_STATE_DB_STORAGE_ENABLED_COLUMN = "db_storage_enabled"; + public static final String API_USAGE_STATE_RE_EXEC_ENABLED_COLUMN = "re_exec_enabled"; + public static final String API_USAGE_STATE_JS_EXEC_ENABLED_COLUMN = "js_exec_enabled"; /** * Cassandra attributes and timeseries constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java index a06ec142d3..262252517a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java @@ -17,6 +17,8 @@ package org.thingsboard.server.dao.model.sql; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.id.EntityIdFactory; @@ -51,6 +53,15 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements @Column(name = ModelConstants.API_USAGE_STATE_ENTITY_ID_COLUMN) private UUID entityId; + @Column(name = ModelConstants.API_USAGE_STATE_TRANSPORT_ENABLED_COLUMN) + private boolean transportEnabled = true; + @Column(name = ModelConstants.API_USAGE_STATE_DB_STORAGE_ENABLED_COLUMN) + private boolean dbStorageEnabled = true; + @Column(name = ModelConstants.API_USAGE_STATE_RE_EXEC_ENABLED_COLUMN) + private boolean reExecEnabled = true; + @Column(name = ModelConstants.API_USAGE_STATE_JS_EXEC_ENABLED_COLUMN) + private boolean jsExecEnabled = true; + public ApiUsageStateEntity() { } @@ -66,6 +77,10 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements this.entityType = ur.getEntityId().getEntityType().name(); this.entityId = ur.getEntityId().getId(); } + this.transportEnabled = ur.isTransportEnabled(); + this.dbStorageEnabled = ur.isDbStorageEnabled(); + this.reExecEnabled = ur.isReExecEnabled(); + this.jsExecEnabled = ur.isJsExecEnabled(); } @Override @@ -78,6 +93,10 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements if (entityId != null) { ur.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId)); } + ur.setTransportEnabled(transportEnabled); + ur.setDbStorageEnabled(dbStorageEnabled); + ur.setReExecEnabled(reExecEnabled); + ur.setJsExecEnabled(jsExecEnabled); return ur; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java similarity index 96% rename from dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java rename to dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index e59c990346..15be73a0b8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -31,13 +31,13 @@ import static org.thingsboard.server.dao.service.Validator.validateId; @Service @Slf4j -public class ApiApiUsageStateServiceImpl extends AbstractEntityService implements ApiUsageStateService { +public class ApiUsageStateServiceImpl extends AbstractEntityService implements ApiUsageStateService { public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; private final ApiUsageStateDao apiUsageStateDao; private final TenantDao tenantDao; - public ApiApiUsageStateServiceImpl(TenantDao tenantDao, ApiUsageStateDao apiUsageStateDao) { + public ApiUsageStateServiceImpl(TenantDao tenantDao, ApiUsageStateDao apiUsageStateDao) { this.tenantDao = tenantDao; this.apiUsageStateDao = apiUsageStateDao; } diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index c671cd2e19..1d82e2d493 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -411,5 +411,9 @@ CREATE TABLE IF NOT EXISTS api_usage_state ( tenant_id uuid, entity_type varchar(32), entity_id uuid, + transport_enabled boolean, + db_storage_enabled boolean, + re_exec_enabled boolean, + js_exec_enabled boolean, CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) ); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index d8539d9af9..c26fce6080 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -437,6 +437,10 @@ CREATE TABLE IF NOT EXISTS api_usage_state ( tenant_id uuid, entity_type varchar(32), entity_id uuid, + transport_enabled boolean, + db_storage_enabled boolean, + re_exec_enabled boolean, + js_exec_enabled boolean, CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) ); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java index 5f56506997..a33e190a03 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java @@ -48,4 +48,17 @@ public abstract class BaseApiUsageStateServiceTest extends AbstractServiceTest { Assert.assertNotNull(apiUsageState); } + @Test + public void testUpdateApiUsageState(){ + ApiUsageState apiUsageState = apiUsageStateService.findTenantApiUsageState(tenantId); + Assert.assertNotNull(apiUsageState); + Assert.assertTrue(apiUsageState.isTransportEnabled()); + apiUsageState.setTransportEnabled(false); + apiUsageState = apiUsageStateService.update(apiUsageState); + Assert.assertNotNull(apiUsageState); + apiUsageState = apiUsageStateService.findTenantApiUsageState(tenantId); + Assert.assertNotNull(apiUsageState); + Assert.assertFalse(apiUsageState.isTransportEnabled()); + } + } From 2ea2b57f3c4debfbb1cff09dec4c3ec25b3b1d39 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 8 Sep 2020 11:06:57 +0300 Subject: [PATCH 243/463] Top 5 rule nodes statistics (cherry picked from commit 445350b1f800b90193f50ee38556e5187acff9e7) --- .../actors/ruleChain/DefaultTbContext.java | 2 + .../RuleNodeActorMessageProcessor.java | 2 +- .../DefaultTbRuleEngineConsumerService.java | 4 +- .../service/queue/TbMsgPackCallback.java | 12 ++- .../queue/TbMsgPackProcessingContext.java | 60 ++++++++++++- .../service/queue/TbMsgProfilerInfo.java | 85 +++++++++++++++++++ .../service/queue/TbRuleNodeProfilerInfo.java | 75 ++++++++++++++++ .../BatchTbRuleEngineSubmitStrategy.java | 4 +- .../queue/TbMsgPackProcessingContextTest.java | 2 +- .../server/common/msg/queue/RuleNodeInfo.java | 4 + .../common/msg/queue/TbMsgCallback.java | 8 +- 11 files changed, 247 insertions(+), 11 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/queue/TbMsgProfilerInfo.java create mode 100644 application/src/main/java/org/thingsboard/server/service/queue/TbRuleNodeProfilerInfo.java diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index e89daf4516..30747e8f1b 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -105,6 +105,7 @@ class DefaultTbContext implements TbContext { if (nodeCtx.getSelf().isDebugMode()) { relationTypes.forEach(relationType -> mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, relationType, th)); } + msg.getCallback().onProcessingEnd(nodeCtx.getSelf().getId()); nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationTypes, msg, th != null ? th.getMessage() : null)); } @@ -214,6 +215,7 @@ class DefaultTbContext implements TbContext { if (nodeCtx.getSelf().isDebugMode()) { mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), tbMsg, "ACK", null); } + tbMsg.getCallback().onProcessingEnd(nodeCtx.getSelf().getId()); tbMsg.getCallback().onSuccess(); } diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java index 7583ea553e..db55ff8edf 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java @@ -103,7 +103,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor submitExecutor.submit(() -> { log.trace("[{}] Creating callback for message: {}", id, msg.getValue()); ToRuleEngineMsg toRuleEngineMsg = msg.getValue(); @@ -194,6 +194,8 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< if (!ctx.getFailedMap().isEmpty()) { printFirstOrAll(configuration, ctx, ctx.getFailedMap(), "Failed"); } + ctx.printProfilerStats(); + TbRuleEngineProcessingDecision decision = ackStrategy.analyze(result); if (statsEnabled) { stats.log(result, decision.isCommit()); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackCallback.java b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackCallback.java index c7925fd759..5712f29eac 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackCallback.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackCallback.java @@ -49,8 +49,14 @@ public class TbMsgPackCallback implements TbMsgCallback { } @Override - public void visit(RuleNodeInfo ruleNodeInfo) { - log.trace("[{}] ON PROCESS: {}", id, ruleNodeInfo); - ctx.visit(id, ruleNodeInfo); + public void onProcessingStart(RuleNodeInfo ruleNodeInfo) { + log.trace("[{}] ON PROCESSING START: {}", id, ruleNodeInfo); + ctx.onProcessingStart(id, ruleNodeInfo); + } + + @Override + public void onProcessingEnd(RuleNodeId ruleNodeId) { + log.trace("[{}] ON PROCESSING END: {}", id, ruleNodeId); + ctx.onProcessingEnd(id, ruleNodeId); } } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java index 1b7ffd9c30..c85544ce81 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.queue; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.RuleEngineException; @@ -24,6 +25,8 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategy; +import java.util.Comparator; +import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -31,9 +34,13 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +@Slf4j public class TbMsgPackProcessingContext { + private final String queueName; private final TbRuleEngineSubmitStrategy submitStrategy; + @Getter + private final boolean profilerEnabled; private final AtomicInteger pendingCount; private final CountDownLatch processingTimeoutLatch = new CountDownLatch(1); @Getter @@ -47,14 +54,20 @@ public class TbMsgPackProcessingContext { private final ConcurrentMap lastRuleNodeMap = new ConcurrentHashMap<>(); - public TbMsgPackProcessingContext(TbRuleEngineSubmitStrategy submitStrategy) { + public TbMsgPackProcessingContext(String queueName, TbRuleEngineSubmitStrategy submitStrategy) { + this.queueName = queueName; this.submitStrategy = submitStrategy; + this.profilerEnabled = log.isDebugEnabled(); this.pendingMap = submitStrategy.getPendingMap(); this.pendingCount = new AtomicInteger(pendingMap.size()); } public boolean await(long packProcessingTimeout, TimeUnit milliseconds) throws InterruptedException { - return processingTimeoutLatch.await(packProcessingTimeout, milliseconds); + boolean success = processingTimeoutLatch.await(packProcessingTimeout, milliseconds); + if (!success && profilerEnabled) { + msgProfilerMap.values().forEach(TbMsgProfilerInfo::onTimeout); + } + return success; } public void onSuccess(UUID id) { @@ -85,12 +98,53 @@ public class TbMsgPackProcessingContext { } } - public void visit(UUID id, RuleNodeInfo ruleNodeInfo) { + private final ConcurrentHashMap msgProfilerMap = new ConcurrentHashMap<>(); + private final ConcurrentHashMap ruleNodeProfilerMap = new ConcurrentHashMap<>(); + + public void onProcessingStart(UUID id, RuleNodeInfo ruleNodeInfo) { lastRuleNodeMap.put(id, ruleNodeInfo); + if (profilerEnabled) { + msgProfilerMap.computeIfAbsent(id, TbMsgProfilerInfo::new).onStart(ruleNodeInfo.getRuleNodeId()); + ruleNodeProfilerMap.putIfAbsent(ruleNodeInfo.getRuleNodeId().getId(), new TbRuleNodeProfilerInfo(ruleNodeInfo)); + } + } + + public void onProcessingEnd(UUID id, RuleNodeId ruleNodeId) { + if (profilerEnabled) { + long processingTime = msgProfilerMap.computeIfAbsent(id, TbMsgProfilerInfo::new).onEnd(ruleNodeId); + if (processingTime > 0) { + ruleNodeProfilerMap.computeIfAbsent(ruleNodeId.getId(), TbRuleNodeProfilerInfo::new).record(processingTime); + } + } + } + + public void onTimeout(TbMsgProfilerInfo profilerInfo) { + Map.Entry ruleNodeInfo = profilerInfo.onTimeout(); + if (ruleNodeInfo != null) { + ruleNodeProfilerMap.computeIfAbsent(ruleNodeInfo.getKey(), TbRuleNodeProfilerInfo::new).record(ruleNodeInfo.getValue()); + } } public RuleNodeInfo getLastVisitedRuleNode(UUID id) { return lastRuleNodeMap.get(id); } + public void printProfilerStats() { + if (profilerEnabled) { + log.debug("Top Rule Nodes by max execution time:"); + ruleNodeProfilerMap.values().stream() + .sorted(Comparator.comparingLong(TbRuleNodeProfilerInfo::getMaxExecutionTime).reversed()).limit(5) + .forEach(info -> log.debug("[{}][{}] max execution time: {}. {}", queueName, info.getRuleNodeId(), info.getMaxExecutionTime(), info.getLabel())); + + log.info("Top Rule Nodes by avg execution time:"); + ruleNodeProfilerMap.values().stream() + .sorted(Comparator.comparingDouble(TbRuleNodeProfilerInfo::getAvgExecutionTime).reversed()).limit(5) + .forEach(info -> log.info("[{}][{}] avg execution time: {}. {}", queueName, info.getRuleNodeId(), info.getAvgExecutionTime(), info.getLabel())); + + log.info("Top Rule Nodes by execution count:"); + ruleNodeProfilerMap.values().stream() + .sorted(Comparator.comparingInt(TbRuleNodeProfilerInfo::getExecutionCount).reversed()).limit(5) + .forEach(info -> log.info("[{}][{}] execution count: {}. {}", queueName, info.getRuleNodeId(), info.getExecutionCount(), info.getLabel())); + } + } } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbMsgProfilerInfo.java b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgProfilerInfo.java new file mode 100644 index 0000000000..f66cd1a50a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgProfilerInfo.java @@ -0,0 +1,85 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.queue; + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.msg.queue.RuleNodeInfo; + +import java.util.AbstractMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +@Slf4j +public class TbMsgProfilerInfo { + private final UUID msgId; + private AtomicLong totalProcessingTime = new AtomicLong(); + private Lock stateLock = new ReentrantLock(); + private RuleNodeId currentRuleNodeId; + private long stateChangeTime; + + public TbMsgProfilerInfo(UUID msgId) { + this.msgId = msgId; + } + + public void onStart(RuleNodeId ruleNodeId) { + long currentTime = System.currentTimeMillis(); + stateLock.lock(); + try { + currentRuleNodeId = ruleNodeId; + stateChangeTime = currentTime; + } finally { + stateLock.unlock(); + } + } + + public long onEnd(RuleNodeId ruleNodeId) { + long currentTime = System.currentTimeMillis(); + stateLock.lock(); + try { + if (ruleNodeId.equals(currentRuleNodeId)) { + long processingTime = currentTime - stateChangeTime; + stateChangeTime = currentTime; + totalProcessingTime.addAndGet(processingTime); + currentRuleNodeId = null; + return processingTime; + } else { + log.trace("[{}] Invalid sequence of rule node processing detected. Expected [{}] but was [{}]", msgId, currentRuleNodeId, ruleNodeId); + return 0; + } + } finally { + stateLock.unlock(); + } + } + + public Map.Entry onTimeout() { + long currentTime = System.currentTimeMillis(); + stateLock.lock(); + try { + if (currentRuleNodeId != null && stateChangeTime > 0) { + long timeoutTime = currentTime - stateChangeTime; + totalProcessingTime.addAndGet(timeoutTime); + return new AbstractMap.SimpleEntry<>(currentRuleNodeId.getId(), timeoutTime); + } + } finally { + stateLock.unlock(); + } + return null; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbRuleNodeProfilerInfo.java b/application/src/main/java/org/thingsboard/server/service/queue/TbRuleNodeProfilerInfo.java new file mode 100644 index 0000000000..c88532fbc3 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbRuleNodeProfilerInfo.java @@ -0,0 +1,75 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.queue; + +import lombok.Getter; +import org.thingsboard.server.common.msg.queue.RuleNodeInfo; + +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +public class TbRuleNodeProfilerInfo { + @Getter + private final UUID ruleNodeId; + @Getter + private final String label; + private AtomicInteger executionCount = new AtomicInteger(0); + private AtomicLong executionTime = new AtomicLong(0); + private AtomicLong maxExecutionTime = new AtomicLong(0); + + public TbRuleNodeProfilerInfo(RuleNodeInfo ruleNodeInfo) { + this.ruleNodeId = ruleNodeInfo.getRuleNodeId().getId(); + this.label = ruleNodeInfo.toString(); + } + + public TbRuleNodeProfilerInfo(UUID ruleNodeId) { + this.ruleNodeId = ruleNodeId; + this.label = ""; + } + + public void record(long processingTime) { + executionCount.incrementAndGet(); + executionTime.addAndGet(processingTime); + while (true) { + long value = maxExecutionTime.get(); + if (value >= processingTime) { + break; + } + if (maxExecutionTime.compareAndSet(value, processingTime)) { + break; + } + } + } + + int getExecutionCount() { + return executionCount.get(); + } + + long getMaxExecutionTime() { + return maxExecutionTime.get(); + } + + double getAvgExecutionTime() { + double executionCnt = (double) executionCount.get(); + if (executionCnt > 0) { + return executionTime.get() / executionCnt; + } else { + return 0.0; + } + } + +} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/BatchTbRuleEngineSubmitStrategy.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/BatchTbRuleEngineSubmitStrategy.java index b9741d2433..be299b39b4 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/BatchTbRuleEngineSubmitStrategy.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/BatchTbRuleEngineSubmitStrategy.java @@ -68,18 +68,20 @@ public class BatchTbRuleEngineSubmitStrategy extends AbstractTbRuleEngineSubmitS int listSize = orderedMsgList.size(); int startIdx = Math.min(packIdx.get() * batchSize, listSize); int endIdx = Math.min(startIdx + batchSize, listSize); + Map> tmpPack; synchronized (pendingPack) { pendingPack.clear(); for (int i = startIdx; i < endIdx; i++) { IdMsgPair pair = orderedMsgList.get(i); pendingPack.put(pair.uuid, pair.msg); } + tmpPack = new LinkedHashMap<>(pendingPack); } int submitSize = pendingPack.size(); if (log.isDebugEnabled() && submitSize > 0) { log.debug("[{}] submitting [{}] messages to rule engine", queueName, submitSize); } - pendingPack.forEach(msgConsumer); + tmpPack.forEach(msgConsumer); } } diff --git a/application/src/test/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContextTest.java b/application/src/test/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContextTest.java index 43cd62ea97..595f64b3a4 100644 --- a/application/src/test/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContextTest.java +++ b/application/src/test/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContextTest.java @@ -51,7 +51,7 @@ public class TbMsgPackProcessingContextTest { messages.put(UUID.randomUUID(), new TbProtoQueueMsg<>(UUID.randomUUID(), null)); } when(strategyMock.getPendingMap()).thenReturn(messages); - TbMsgPackProcessingContext context = new TbMsgPackProcessingContext(strategyMock); + TbMsgPackProcessingContext context = new TbMsgPackProcessingContext("Main", strategyMock); for (UUID uuid : messages.keySet()) { for (int i = 0; i < parallelCount; i++) { executorService.submit(() -> context.onSuccess(uuid)); diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/queue/RuleNodeInfo.java b/common/message/src/main/java/org/thingsboard/server/common/msg/queue/RuleNodeInfo.java index 0c2d38b3b2..9af3f20363 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/queue/RuleNodeInfo.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/queue/RuleNodeInfo.java @@ -15,12 +15,16 @@ */ package org.thingsboard.server.common.msg.queue; +import lombok.Getter; import org.thingsboard.server.common.data.id.RuleNodeId; public class RuleNodeInfo { private final String label; + @Getter + private final RuleNodeId ruleNodeId; public RuleNodeInfo(RuleNodeId id, String ruleChainName, String ruleNodeName) { + this.ruleNodeId = id; this.label = "[RuleChain: " + ruleChainName + "|RuleNode: " + ruleNodeName + "(" + id + ")]"; } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/queue/TbMsgCallback.java b/common/message/src/main/java/org/thingsboard/server/common/msg/queue/TbMsgCallback.java index 4103a4efc1..3f6927adb1 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/queue/TbMsgCallback.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/queue/TbMsgCallback.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.common.msg.queue; +import org.thingsboard.server.common.data.id.RuleNodeId; + public interface TbMsgCallback { TbMsgCallback EMPTY = new TbMsgCallback() { @@ -34,7 +36,11 @@ public interface TbMsgCallback { void onFailure(RuleEngineException e); - default void visit(RuleNodeInfo ruleNodeInfo) { + default void onProcessingStart(RuleNodeInfo ruleNodeInfo) { + } + + default void onProcessingEnd(RuleNodeId ruleNodeId) { } + } From 04727e81b433857382655c52b9be005b34bbb88a Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 8 Sep 2020 11:39:12 +0300 Subject: [PATCH 244/463] Detecting rule nodes that cause timeout (cherry picked from commit 35feb2fea43bbb8d39071af63fa6426f25502dd5) --- .../server/service/queue/TbMsgPackProcessingContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java index c85544ce81..6d88ed204a 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java @@ -65,7 +65,7 @@ public class TbMsgPackProcessingContext { public boolean await(long packProcessingTimeout, TimeUnit milliseconds) throws InterruptedException { boolean success = processingTimeoutLatch.await(packProcessingTimeout, milliseconds); if (!success && profilerEnabled) { - msgProfilerMap.values().forEach(TbMsgProfilerInfo::onTimeout); + msgProfilerMap.values().forEach(this::onTimeout); } return success; } From b38b47f4093b547502c3e47a72f7f88526e45032 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 22 Oct 2020 18:51:34 +0300 Subject: [PATCH 245/463] JS Invoke Service stats --- .../actors/ruleChain/DefaultTbContext.java | 2 +- .../controller/RuleChainController.java | 2 +- .../server/service/apiusage/ApiFeature.java | 15 ++ .../service/apiusage/TenantApiUsageState.java | 36 +-- .../queue/DefaultTbClusterService.java | 2 +- .../processing/AbstractConsumerService.java | 11 +- .../script/AbstractJsInvokeService.java | 13 +- .../AbstractNashornJsInvokeService.java | 11 +- .../service/script/JsExecutorService.java | 2 +- .../service/script/JsInvokeService.java | 5 +- .../script/NashornJsInvokeService.java | 5 + .../service/script/RemoteJsInvokeService.java | 5 + .../script/RuleNodeJsScriptEngine.java | 11 +- .../transport/DefaultTransportApiService.java | 2 +- .../script/RuleNodeJsScriptEngineTest.java | 250 ------------------ .../script/TestNashornJsInvokeService.java | 57 ---- .../server/common/data/ApiUsageRecordKey.java | 6 +- .../tenant/profile/TenantProfileData.java | 13 - .../MultipleTbQueueCallbackWrapper.java | 2 +- .../transport/mqtt/MqttTransportHandler.java | 2 +- .../common/transport/TransportService.java | 5 - .../DefaultTransportRateLimitFactory.java | 47 ---- .../DefaultTransportRateLimitService.java | 128 +++++---- ...ry.java => EntityTransportRateLimits.java} | 11 +- .../limits/SimpleTransportRateLimit.java | 5 + .../limits/TransportRateLimitService.java | 3 +- .../limits/TransportRateLimitType.java | 48 ---- .../service/DefaultTransportService.java | 32 +-- .../DefaultTransportTenantProfileCache.java | 2 +- .../dao/tenant/TenantProfileServiceImpl.java | 5 +- .../service/BaseTenantProfileServiceTest.java | 5 +- 31 files changed, 209 insertions(+), 534 deletions(-) delete mode 100644 application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/service/script/TestNashornJsInvokeService.java delete mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitFactory.java rename common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/{TransportRateLimitFactory.java => EntityTransportRateLimits.java} (70%) delete mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index c8bb083577..e7a54385a7 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -312,7 +312,7 @@ class DefaultTbContext implements TbContext { @Override public ScriptEngine createJsScriptEngine(String script, String... argNames) { - return new RuleNodeJsScriptEngine(mainCtx.getJsSandbox(), nodeCtx.getSelf().getId(), script, argNames); + return new RuleNodeJsScriptEngine(getTenantId(), mainCtx.getJsSandbox(), nodeCtx.getSelf().getId(), script, argNames); } @Override diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java index 6d80c05c05..2ca834d85b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -347,7 +347,7 @@ public class RuleChainController extends BaseController { String errorText = ""; ScriptEngine engine = null; try { - engine = new RuleNodeJsScriptEngine(jsInvokeService, getCurrentUser().getId(), script, argNames); + engine = new RuleNodeJsScriptEngine(getTenantId(), jsInvokeService, getCurrentUser().getId(), script, argNames); TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data); switch (scriptType) { case "update": diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java b/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java index a893a55873..8267d4addd 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.service.apiusage; public enum ApiFeature { diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java index 0e68d02b71..5dcfb34ec3 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -21,6 +21,7 @@ import org.springframework.data.util.Pair; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -100,15 +101,18 @@ public class TenantApiUsageState { } public long getProfileThreshold(ApiUsageRecordKey key) { - Object threshold = tenantProfileData.getProperties().get(key.name()); - if (threshold != null) { - if (threshold instanceof String) { - return Long.parseLong((String) threshold); - } else if (threshold instanceof Long) { - return (Long) threshold; - } else if (threshold instanceof Integer) { - return (Integer) threshold; - } + DefaultTenantProfileConfiguration config = (DefaultTenantProfileConfiguration) tenantProfileData.getConfiguration(); + switch (key) { + case TRANSPORT_MSG_COUNT: + return config.getMaxTransportMessages(); + case TRANSPORT_DP_COUNT: + return config.getMaxTransportDataPoints(); + case JS_EXEC_COUNT: + return config.getMaxJSExecutions(); + case RE_EXEC_COUNT: + return config.getMaxREExecutions(); + case STORAGE_DP_COUNT: + return config.getMaxDPStorageDays(); } return 0L; } @@ -155,12 +159,12 @@ public class TenantApiUsageState { public boolean isFeatureEnabled(ApiUsageRecordKey recordKey) { switch (recordKey) { - case MSG_COUNT: - case DP_TRANSPORT_COUNT: + case TRANSPORT_MSG_COUNT: + case TRANSPORT_DP_COUNT: return isTransportEnabled(); case RE_EXEC_COUNT: return isRuleEngineEnabled(); - case DP_STORAGE_COUNT: + case STORAGE_DP_COUNT: return isDbStorageEnabled(); case JS_EXEC_COUNT: return isJsExecEnabled(); @@ -173,8 +177,8 @@ public class TenantApiUsageState { ApiFeature feature = null; boolean currentValue = isFeatureEnabled(recordKey); switch (recordKey) { - case MSG_COUNT: - case DP_TRANSPORT_COUNT: + case TRANSPORT_MSG_COUNT: + case TRANSPORT_DP_COUNT: feature = ApiFeature.TRANSPORT; setTransportEnabled(value); break; @@ -182,7 +186,7 @@ public class TenantApiUsageState { feature = ApiFeature.RE; setRuleEngineEnabled(value); break; - case DP_STORAGE_COUNT: + case STORAGE_DP_COUNT: feature = ApiFeature.DB; setDbStorageEnabled(value); break; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index b26baf8d2e..f08092d32d 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java index 02fe877cd0..9a4d488e0f 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantProfileId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.queue.ServiceType; @@ -153,10 +154,14 @@ public abstract class AbstractConsumerService scriptIdToNameMap = new ConcurrentHashMap<>(); protected Map blackListedFunctions = new ConcurrentHashMap<>(); + protected AbstractJsInvokeService(TbUsageStatsClient apiUsageStatsClient) { + this.apiUsageStatsClient = apiUsageStatsClient; + } + public void init(long maxRequestsTimeout) { if (maxRequestsTimeout > 0) { timeoutExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("nashorn-js-timeout")); @@ -50,7 +58,7 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { } @Override - public ListenableFuture eval(JsScriptType scriptType, String scriptBody, String... argNames) { + public ListenableFuture eval(TenantId tenantId, JsScriptType scriptType, String scriptBody, String... argNames) { UUID scriptId = UUID.randomUUID(); String functionName = "invokeInternal_" + scriptId.toString().replace('-', '_'); String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames); @@ -58,12 +66,13 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { } @Override - public ListenableFuture invokeFunction(UUID scriptId, Object... args) { + public ListenableFuture invokeFunction(TenantId tenantId, UUID scriptId, Object... args) { String functionName = scriptIdToNameMap.get(scriptId); if (functionName == null) { return Futures.immediateFailedFuture(new RuntimeException("No compiled script found for scriptId: [" + scriptId + "]!")); } if (!isBlackListed(scriptId)) { + apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.JS_EXEC_COUNT, 1); return doInvokeFunction(scriptId, functionName, args); } else { return Futures.immediateFailedFuture( diff --git a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java index 8e6666bbba..17be0602f8 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java @@ -27,7 +27,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; -import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.queue.usagestats.TbUsageStatsClient; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -38,7 +38,6 @@ import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -57,9 +56,8 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer private final FutureCallback evalCallback = new JsStatCallback<>(jsEvalMsgs, jsTimeoutMsgs, jsFailedMsgs); private final FutureCallback invokeCallback = new JsStatCallback<>(jsInvokeMsgs, jsTimeoutMsgs, jsFailedMsgs); - @Autowired @Getter - private JsExecutorService jsExecutor; + private final JsExecutorService jsExecutor; @Value("${js.local.max_requests_timeout:0}") private long maxRequestsTimeout; @@ -67,6 +65,11 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer @Value("${js.local.stats.enabled:false}") private boolean statsEnabled; + public AbstractNashornJsInvokeService(TbUsageStatsClient apiUsageStatsClient, JsExecutorService jsExecutor) { + super(apiUsageStatsClient); + this.jsExecutor = jsExecutor; + } + @Scheduled(fixedDelayString = "${js.local.stats.print_interval_ms:10000}") public void printStats() { if (statsEnabled) { diff --git a/application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java b/application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java index 97e40e6f62..15fd8c7534 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java @@ -27,7 +27,7 @@ public class JsExecutorService extends AbstractListeningExecutor { @Override protected int getThreadPollSize() { - return jsExecutorThreadPoolSize; + return Math.max(jsExecutorThreadPoolSize, 1); } } diff --git a/application/src/main/java/org/thingsboard/server/service/script/JsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/JsInvokeService.java index a9f727e4a7..ae990af66c 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/JsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/JsInvokeService.java @@ -17,14 +17,15 @@ package org.thingsboard.server.service.script; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; import java.util.UUID; public interface JsInvokeService { - ListenableFuture eval(JsScriptType scriptType, String scriptBody, String... argNames); + ListenableFuture eval(TenantId tenantId, JsScriptType scriptType, String scriptBody, String... argNames); - ListenableFuture invokeFunction(UUID scriptId, Object... args); + ListenableFuture invokeFunction(TenantId tenantId, UUID scriptId, Object... args); ListenableFuture release(UUID scriptId); diff --git a/application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java index 66a14cc827..c20f3e8c08 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; +import org.thingsboard.server.queue.usagestats.TbUsageStatsClient; import java.util.concurrent.TimeUnit; @@ -42,6 +43,10 @@ public class NashornJsInvokeService extends AbstractNashornJsInvokeService { @Value("${js.local.max_black_list_duration_sec:60}") private int maxBlackListDurationSec; + public NashornJsInvokeService(TbUsageStatsClient apiUsageStatsClient, JsExecutorService jsExecutor) { + super(apiUsageStatsClient, jsExecutor); + } + @Override protected boolean useJsSandbox() { return useJsSandbox; diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java index cfb0c8a8ee..2ada577287 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java @@ -30,6 +30,7 @@ import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.queue.TbQueueRequestTemplate; import org.thingsboard.server.queue.common.TbProtoJsQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.usagestats.TbUsageStatsClient; import javax.annotation.Nullable; import javax.annotation.PostConstruct; @@ -68,6 +69,10 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { private final AtomicInteger queueFailedMsgs = new AtomicInteger(0); private final AtomicInteger queueTimeoutMsgs = new AtomicInteger(0); + public RemoteJsInvokeService(TbUsageStatsClient apiUsageStatsClient) { + super(apiUsageStatsClient); + } + @Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms}") public void printStats() { if (statsEnabled) { diff --git a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java index 110f410fad..275e645ce2 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java @@ -25,6 +25,7 @@ import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -43,13 +44,15 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S private final JsInvokeService sandboxService; private final UUID scriptId; + private final TenantId tenantId; private final EntityId entityId; - public RuleNodeJsScriptEngine(JsInvokeService sandboxService, EntityId entityId, String script, String... argNames) { + public RuleNodeJsScriptEngine(TenantId tenantId, JsInvokeService sandboxService, EntityId entityId, String script, String... argNames) { + this.tenantId = tenantId; this.sandboxService = sandboxService; this.entityId = entityId; try { - this.scriptId = this.sandboxService.eval(JsScriptType.RULE_NODE_SCRIPT, script, argNames).get(); + this.scriptId = this.sandboxService.eval(tenantId, JsScriptType.RULE_NODE_SCRIPT, script, argNames).get(); } catch (Exception e) { Throwable t = e; if (e instanceof ExecutionException) { @@ -203,7 +206,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S private JsonNode executeScript(TbMsg msg) throws ScriptException { try { String[] inArgs = prepareArgs(msg); - String eval = sandboxService.invokeFunction(this.scriptId, inArgs[0], inArgs[1], inArgs[2]).get().toString(); + String eval = sandboxService.invokeFunction(tenantId, this.scriptId, inArgs[0], inArgs[1], inArgs[2]).get().toString(); return mapper.readTree(eval); } catch (ExecutionException e) { if (e.getCause() instanceof ScriptException) { @@ -220,7 +223,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S private ListenableFuture executeScriptAsync(TbMsg msg) { String[] inArgs = prepareArgs(msg); - return Futures.transformAsync(sandboxService.invokeFunction(this.scriptId, inArgs[0], inArgs[1], inArgs[2]), + return Futures.transformAsync(sandboxService.invokeFunction(tenantId, this.scriptId, inArgs[0], inArgs[1], inArgs[2]), o -> { try { return Futures.immediateFuture(mapper.readTree(o.toString())); diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 6d0b7a0aa8..ab566fb147 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java b/application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java deleted file mode 100644 index 60495b119d..0000000000 --- a/application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java +++ /dev/null @@ -1,250 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.script; - -import com.datastax.oss.driver.api.core.uuid.Uuids; -import com.google.common.collect.Sets; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.thingsboard.rule.engine.api.ScriptEngine; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.RuleNodeId; -import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.TbMsgDataType; -import org.thingsboard.server.common.msg.TbMsgMetaData; - -import javax.script.ScriptException; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.junit.Assert.*; - -public class RuleNodeJsScriptEngineTest { - - private ScriptEngine scriptEngine; - private TestNashornJsInvokeService jsSandboxService; - - private EntityId ruleNodeId = new RuleNodeId(Uuids.timeBased()); - - @Before - public void beforeTest() throws Exception { - jsSandboxService = new TestNashornJsInvokeService(false, 1, 100, 3); - } - - @After - public void afterTest() throws Exception { - jsSandboxService.stop(); - } - - @Test - public void msgCanBeUpdated() throws ScriptException { - String function = "metadata.temp = metadata.temp * 10; return {metadata: metadata};"; - scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function); - - TbMsgMetaData metaData = new TbMsgMetaData(); - metaData.putValue("temp", "7"); - metaData.putValue("humidity", "99"); - String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; - - TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson); - - TbMsg actual = scriptEngine.executeUpdate(msg); - assertEquals("70", actual.getMetaData().getValue("temp")); - scriptEngine.destroy(); - } - - @Test - public void newAttributesCanBeAddedInMsg() throws ScriptException { - String function = "metadata.newAttr = metadata.humidity - msg.passed; return {metadata: metadata};"; - scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function); - TbMsgMetaData metaData = new TbMsgMetaData(); - metaData.putValue("temp", "7"); - metaData.putValue("humidity", "99"); - String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; - - TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson); - - TbMsg actual = scriptEngine.executeUpdate(msg); - assertEquals("94", actual.getMetaData().getValue("newAttr")); - scriptEngine.destroy(); - } - - @Test - public void payloadCanBeUpdated() throws ScriptException { - String function = "msg.passed = msg.passed * metadata.temp; msg.bigObj.newProp = 'Ukraine'; return {msg: msg};"; - scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function); - TbMsgMetaData metaData = new TbMsgMetaData(); - metaData.putValue("temp", "7"); - metaData.putValue("humidity", "99"); - String rawJson = "{\"name\":\"Vit\",\"passed\": 5,\"bigObj\":{\"prop\":42}}"; - - TbMsg msg =TbMsg.newMsg("USER", null, metaData, TbMsgDataType.JSON, rawJson); - - TbMsg actual = scriptEngine.executeUpdate(msg); - - String expectedJson = "{\"name\":\"Vit\",\"passed\":35,\"bigObj\":{\"prop\":42,\"newProp\":\"Ukraine\"}}"; - assertEquals(expectedJson, actual.getData()); - scriptEngine.destroy(); - } - - @Test - public void metadataAccessibleForFilter() throws ScriptException { - String function = "return metadata.humidity < 15;"; - scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function); - TbMsgMetaData metaData = new TbMsgMetaData(); - metaData.putValue("temp", "7"); - metaData.putValue("humidity", "99"); - String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; - - TbMsg msg = TbMsg.newMsg("USER", null, metaData, TbMsgDataType.JSON, rawJson); - assertFalse(scriptEngine.executeFilter(msg)); - scriptEngine.destroy(); - } - - @Test - public void dataAccessibleForFilter() throws ScriptException { - String function = "return msg.passed < 15 && msg.name === 'Vit' && metadata.temp == 7 && msg.bigObj.prop == 42;"; - scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function); - TbMsgMetaData metaData = new TbMsgMetaData(); - metaData.putValue("temp", "7"); - metaData.putValue("humidity", "99"); - String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; - - TbMsg msg = TbMsg.newMsg( "USER", null, metaData,TbMsgDataType.JSON, rawJson); - assertTrue(scriptEngine.executeFilter(msg)); - scriptEngine.destroy(); - } - - @Test - public void dataAccessibleForSwitch() throws ScriptException { - String jsCode = "function nextRelation(metadata, msg) {\n" + - " if(msg.passed == 5 && metadata.temp == 10)\n" + - " return 'one'\n" + - " else\n" + - " return 'two';\n" + - "};\n" + - "\n" + - "return nextRelation(metadata, msg);"; - scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, jsCode); - TbMsgMetaData metaData = new TbMsgMetaData(); - metaData.putValue("temp", "10"); - metaData.putValue("humidity", "99"); - String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; - - TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson); - Set actual = scriptEngine.executeSwitch(msg); - assertEquals(Sets.newHashSet("one"), actual); - scriptEngine.destroy(); - } - - @Test - public void multipleRelationsReturnedFromSwitch() throws ScriptException { - String jsCode = "function nextRelation(metadata, msg) {\n" + - " if(msg.passed == 5 && metadata.temp == 10)\n" + - " return ['three', 'one']\n" + - " else\n" + - " return 'two';\n" + - "};\n" + - "\n" + - "return nextRelation(metadata, msg);"; - scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, jsCode); - TbMsgMetaData metaData = new TbMsgMetaData(); - metaData.putValue("temp", "10"); - metaData.putValue("humidity", "99"); - String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; - - TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson); - Set actual = scriptEngine.executeSwitch(msg); - assertEquals(Sets.newHashSet("one", "three"), actual); - scriptEngine.destroy(); - } - - @Test - public void concurrentReleasedCorrectly() throws InterruptedException, ExecutionException { - String code = "metadata.temp = metadata.temp * 10; return {metadata: metadata};"; - - int repeat = 1000; - ExecutorService service = Executors.newFixedThreadPool(repeat); - Map scriptIds = new ConcurrentHashMap<>(); - CountDownLatch startLatch = new CountDownLatch(repeat); - CountDownLatch finishLatch = new CountDownLatch(repeat); - AtomicInteger failedCount = new AtomicInteger(0); - - for (int i = 0; i < repeat; i++) { - service.submit(() -> runScript(startLatch, finishLatch, failedCount, scriptIds, code)); - } - - finishLatch.await(); - assertTrue(scriptIds.size() == 1); - assertTrue(failedCount.get() == 0); - - CountDownLatch nextStart = new CountDownLatch(repeat); - CountDownLatch nextFinish = new CountDownLatch(repeat); - for (int i = 0; i < repeat; i++) { - service.submit(() -> runScript(nextStart, nextFinish, failedCount, scriptIds, code)); - } - - nextFinish.await(); - assertTrue(scriptIds.size() == 1); - assertTrue(failedCount.get() == 0); - service.shutdownNow(); - } - - @Test - public void concurrentFailedEvaluationShouldThrowException() throws InterruptedException { - String code = "metadata.temp = metadata.temp * 10; urn {metadata: metadata};"; - - int repeat = 10000; - ExecutorService service = Executors.newFixedThreadPool(repeat); - Map scriptIds = new ConcurrentHashMap<>(); - CountDownLatch startLatch = new CountDownLatch(repeat); - CountDownLatch finishLatch = new CountDownLatch(repeat); - AtomicInteger failedCount = new AtomicInteger(0); - for (int i = 0; i < repeat; i++) { - service.submit(() -> { - service.submit(() -> runScript(startLatch, finishLatch, failedCount, scriptIds, code)); - }); - } - - finishLatch.await(); - assertTrue(scriptIds.isEmpty()); - assertEquals(repeat, failedCount.get()); - service.shutdownNow(); - } - - private void runScript(CountDownLatch startLatch, CountDownLatch finishLatch, AtomicInteger failedCount, - Map scriptIds, String code) { - try { - for (int k = 0; k < 10; k++) { - startLatch.countDown(); - startLatch.await(); - UUID scriptId = jsSandboxService.eval(JsScriptType.RULE_NODE_SCRIPT, code).get(); - scriptIds.put(scriptId, new Object()); - jsSandboxService.invokeFunction(scriptId, "{}", "{}", "TEXT").get(); - jsSandboxService.release(scriptId).get(); - } - } catch (Throwable th) { - failedCount.incrementAndGet(); - } finally { - finishLatch.countDown(); - } - } - -} diff --git a/application/src/test/java/org/thingsboard/server/service/script/TestNashornJsInvokeService.java b/application/src/test/java/org/thingsboard/server/service/script/TestNashornJsInvokeService.java deleted file mode 100644 index 8a89de5dff..0000000000 --- a/application/src/test/java/org/thingsboard/server/service/script/TestNashornJsInvokeService.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.script; - -public class TestNashornJsInvokeService extends AbstractNashornJsInvokeService { - - private boolean useJsSandbox; - private final int monitorThreadPoolSize; - private final long maxCpuTime; - private final int maxErrors; - - public TestNashornJsInvokeService(boolean useJsSandbox, int monitorThreadPoolSize, long maxCpuTime, int maxErrors) { - this.useJsSandbox = useJsSandbox; - this.monitorThreadPoolSize = monitorThreadPoolSize; - this.maxCpuTime = maxCpuTime; - this.maxErrors = maxErrors; - init(); - } - - @Override - protected boolean useJsSandbox() { - return useJsSandbox; - } - - @Override - protected int getMonitorThreadPoolSize() { - return monitorThreadPoolSize; - } - - @Override - protected long getMaxCpuTime() { - return maxCpuTime; - } - - @Override - protected int getMaxErrors() { - return maxErrors; - } - - @Override - protected long getMaxBlacklistDuration() { - return 100000; - } -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java index bc730e7e5a..0048e325f3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java @@ -17,9 +17,9 @@ package org.thingsboard.server.common.data; public enum ApiUsageRecordKey { - MSG_COUNT, - DP_TRANSPORT_COUNT, - DP_STORAGE_COUNT, + TRANSPORT_MSG_COUNT, + TRANSPORT_DP_COUNT, + STORAGE_DP_COUNT, RE_EXEC_COUNT, JS_EXEC_COUNT diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileData.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileData.java index 6336cb4ace..0996fc9850 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileData.java @@ -28,17 +28,4 @@ public class TenantProfileData { private TenantProfileConfiguration configuration; - @JsonIgnore - private Map properties = new HashMap<>(); - - @JsonAnyGetter - public Map properties() { - return this.properties; - } - - @JsonAnySetter - public void put(String name, Object value) { - this.properties.put(name, value); - } - } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/common/MultipleTbQueueCallbackWrapper.java b/common/queue/src/main/java/org/thingsboard/server/queue/common/MultipleTbQueueCallbackWrapper.java index ddedb3a606..c28f3637b7 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/common/MultipleTbQueueCallbackWrapper.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/common/MultipleTbQueueCallbackWrapper.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index e70da7029e..14712a7cb3 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 3b775b9f26..980aa33221 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -19,7 +19,6 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.common.transport.limits.TransportRateLimitType; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; @@ -62,10 +61,6 @@ public interface TransportService { void process(ProvisionDeviceRequestMsg msg, TransportServiceCallback callback); - boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback); - - boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints, TransportRateLimitType... limits); - void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback callback); void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback callback); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitFactory.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitFactory.java deleted file mode 100644 index 362502bf17..0000000000 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitFactory.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.common.transport.limits; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; -import org.thingsboard.server.common.msg.tools.TbRateLimits; - -@Slf4j -@Component -public class DefaultTransportRateLimitFactory implements TransportRateLimitFactory { - - private static final DummyTransportRateLimit ALWAYS_TRUE = new DummyTransportRateLimit(); - - @Override - public TransportRateLimit create(TransportRateLimitType type, Object configuration) { - if (!StringUtils.isEmpty(configuration)) { - try { - return new SimpleTransportRateLimit(new TbRateLimits(configuration.toString()), configuration.toString()); - } catch (Exception e) { - log.warn("[{}] Failed to init rate limit with configuration: {}", type, configuration, e); - return ALWAYS_TRUE; - } - } else { - return ALWAYS_TRUE; - } - } - - @Override - public TransportRateLimit createDefault(TransportRateLimitType type) { - return ALWAYS_TRUE; - } -} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java index bc415cd92a..b6f160ad1e 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -17,76 +17,96 @@ package org.thingsboard.server.common.transport.limits; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; +import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; import org.thingsboard.server.queue.util.TbTransportComponent; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.function.BiConsumer; +import java.util.function.Function; @Service @TbTransportComponent @Slf4j public class DefaultTransportRateLimitService implements TransportRateLimitService { + private final static DummyTransportRateLimit ALLOW = new DummyTransportRateLimit(); private final ConcurrentMap tenantAllowed = new ConcurrentHashMap<>(); - private final ConcurrentMap perTenantLimits = new ConcurrentHashMap<>(); - private final ConcurrentMap perDeviceLimits = new ConcurrentHashMap<>(); + private final ConcurrentMap> tenantDevices = new ConcurrentHashMap<>(); + private final ConcurrentMap perTenantLimits = new ConcurrentHashMap<>(); + private final ConcurrentMap perDeviceLimits = new ConcurrentHashMap<>(); - private final TransportRateLimitFactory rateLimitFactory; private final TransportTenantProfileCache tenantProfileCache; - public DefaultTransportRateLimitService(TransportRateLimitFactory rateLimitFactory, TransportTenantProfileCache tenantProfileCache) { - this.rateLimitFactory = rateLimitFactory; + public DefaultTransportRateLimitService(TransportTenantProfileCache tenantProfileCache) { this.tenantProfileCache = tenantProfileCache; } @Override - public TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits) { + public EntityType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints) { if (!tenantAllowed.getOrDefault(tenantId, Boolean.TRUE)) { - return TransportRateLimitType.TENANT_ADDED_TO_DISABLED_LIST; + return EntityType.TENANT; } - TransportRateLimit[] tenantLimits = getTenantRateLimits(tenantId); - TransportRateLimit[] deviceLimits = getDeviceRateLimits(tenantId, deviceId); - for (TransportRateLimitType limitType : limits) { - TransportRateLimit rateLimit; - if (limitType.isTenantLevel()) { - rateLimit = tenantLimits[limitType.ordinal()]; - } else { - rateLimit = deviceLimits[limitType.ordinal()]; - } - if (!rateLimit.tryConsume(limitType.isMessageLevel() ? 1L : dataPoints)) { - return limitType; - } + if (!checkEntityRateLimit(dataPoints, getTenantRateLimits(tenantId))) { + return EntityType.TENANT; + } + if (!checkEntityRateLimit(dataPoints, getDeviceRateLimits(tenantId, deviceId))) { + return EntityType.DEVICE; } return null; } + private boolean checkEntityRateLimit(int dataPoints, EntityTransportRateLimits tenantLimits) { + if (dataPoints > 0) { + return tenantLimits.getTelemetryMsgRateLimit().tryConsume() && tenantLimits.getTelemetryDataPointsRateLimit().tryConsume(dataPoints); + } else { + return tenantLimits.getRegularMsgRateLimit().tryConsume(); + } + } + @Override public void update(TenantProfileUpdateResult update) { - TransportRateLimit[] newLimits = createTransportRateLimits(update.getProfile()); + EntityTransportRateLimits tenantRateLimitPrototype = createRateLimits(update.getProfile(), true); + EntityTransportRateLimits deviceRateLimitPrototype = createRateLimits(update.getProfile(), false); for (TenantId tenantId : update.getAffectedTenants()) { - mergeLimits(tenantId, newLimits); + mergeLimits(tenantId, tenantRateLimitPrototype, perTenantLimits::get, perTenantLimits::put); + tenantDevices.get(tenantId).forEach(deviceId -> { + mergeLimits(deviceId, deviceRateLimitPrototype, perDeviceLimits::get, perDeviceLimits::put); + }); } } @Override public void update(TenantId tenantId) { - mergeLimits(tenantId, fetchProfileAndInit(tenantId)); + EntityTransportRateLimits tenantRateLimitPrototype = createRateLimits(tenantProfileCache.get(tenantId), true); + EntityTransportRateLimits deviceRateLimitPrototype = createRateLimits(tenantProfileCache.get(tenantId), false); + mergeLimits(tenantId, tenantRateLimitPrototype, perTenantLimits::get, perTenantLimits::put); + tenantDevices.get(tenantId).forEach(deviceId -> { + mergeLimits(deviceId, deviceRateLimitPrototype, perDeviceLimits::get, perDeviceLimits::put); + }); } @Override public void remove(TenantId tenantId) { perTenantLimits.remove(tenantId); + tenantDevices.remove(tenantId); } @Override public void remove(DeviceId deviceId) { perDeviceLimits.remove(deviceId); + tenantDevices.values().forEach(set -> set.remove(deviceId)); } @Override @@ -94,48 +114,66 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi tenantAllowed.put(tenantId, allowed); } - private void mergeLimits(TenantId tenantId, TransportRateLimit[] newRateLimits) { - TransportRateLimit[] oldRateLimits = perTenantLimits.get(tenantId); + private void mergeLimits(T deviceId, EntityTransportRateLimits newRateLimits, + Function getFunction, + BiConsumer putFunction) { + EntityTransportRateLimits oldRateLimits = getFunction.apply(deviceId); if (oldRateLimits == null) { - perTenantLimits.put(tenantId, newRateLimits); + putFunction.accept(deviceId, newRateLimits); } else { - for (int i = 0; i < TransportRateLimitType.values().length; i++) { - TransportRateLimit newLimit = newRateLimits[i]; - TransportRateLimit oldLimit = oldRateLimits[i]; - if (newLimit != null && (oldLimit == null || !oldLimit.getConfiguration().equals(newLimit.getConfiguration()))) { - oldRateLimits[i] = newLimit; - } + EntityTransportRateLimits updated = merge(oldRateLimits, newRateLimits); + if (updated != null) { + putFunction.accept(deviceId, updated); } } } - private TransportRateLimit[] fetchProfileAndInit(TenantId tenantId) { - return perTenantLimits.computeIfAbsent(tenantId, tmp -> createTransportRateLimits(tenantProfileCache.get(tenantId))); + private EntityTransportRateLimits merge(EntityTransportRateLimits oldRateLimits, EntityTransportRateLimits newRateLimits) { + boolean regularUpdate = !oldRateLimits.getRegularMsgRateLimit().getConfiguration().equals(newRateLimits.getRegularMsgRateLimit().getConfiguration()); + boolean telemetryMsgRateUpdate = !oldRateLimits.getTelemetryMsgRateLimit().getConfiguration().equals(newRateLimits.getTelemetryMsgRateLimit().getConfiguration()); + boolean telemetryDataPointUpdate = !oldRateLimits.getTelemetryDataPointsRateLimit().getConfiguration().equals(newRateLimits.getTelemetryDataPointsRateLimit().getConfiguration()); + if (regularUpdate || telemetryMsgRateUpdate || telemetryDataPointUpdate) { + return new EntityTransportRateLimits( + regularUpdate ? newLimit(newRateLimits.getRegularMsgRateLimit().getConfiguration()) : oldRateLimits.getRegularMsgRateLimit(), + telemetryMsgRateUpdate ? newLimit(newRateLimits.getTelemetryMsgRateLimit().getConfiguration()) : oldRateLimits.getTelemetryMsgRateLimit(), + telemetryDataPointUpdate ? newLimit(newRateLimits.getTelemetryDataPointsRateLimit().getConfiguration()) : oldRateLimits.getTelemetryDataPointsRateLimit()); + } else { + return null; + } } - private TransportRateLimit[] createTransportRateLimits(TenantProfile tenantProfile) { + private EntityTransportRateLimits createRateLimits(TenantProfile tenantProfile, boolean tenant) { TenantProfileData profileData = tenantProfile.getProfileData(); - TransportRateLimit[] rateLimits = new TransportRateLimit[TransportRateLimitType.values().length]; - for (TransportRateLimitType type : TransportRateLimitType.values()) { - rateLimits[type.ordinal()] = rateLimitFactory.create(type, profileData.getProperties().get(type.getConfigurationKey())); + DefaultTenantProfileConfiguration profile = (DefaultTenantProfileConfiguration) profileData.getConfiguration(); + if (profile == null) { + return new EntityTransportRateLimits(ALLOW, ALLOW, ALLOW); + } else { + TransportRateLimit regularMsgRateLimit = newLimit(tenant ? profile.getTransportTenantMsgRateLimit() : profile.getTransportDeviceMsgRateLimit()); + TransportRateLimit telemetryMsgRateLimit = newLimit(tenant ? profile.getTransportTenantTelemetryMsgRateLimit() : profile.getTransportDeviceTelemetryMsgRateLimit()); + TransportRateLimit telemetryDpRateLimit = newLimit(tenant ? profile.getTransportTenantTelemetryDataPointsRateLimit() : profile.getTransportTenantTelemetryDataPointsRateLimit()); + return new EntityTransportRateLimits(regularMsgRateLimit, telemetryMsgRateLimit, telemetryDpRateLimit); } - return rateLimits; } - private TransportRateLimit[] getTenantRateLimits(TenantId tenantId) { - TransportRateLimit[] limits = perTenantLimits.get(tenantId); + private static TransportRateLimit newLimit(String config) { + return StringUtils.isEmpty(config) ? ALLOW : new SimpleTransportRateLimit(config); + } + + private EntityTransportRateLimits getTenantRateLimits(TenantId tenantId) { + EntityTransportRateLimits limits = perTenantLimits.get(tenantId); if (limits == null) { - limits = fetchProfileAndInit(tenantId); + limits = createRateLimits(tenantProfileCache.get(tenantId), true); perTenantLimits.put(tenantId, limits); } return limits; } - private TransportRateLimit[] getDeviceRateLimits(TenantId tenantId, DeviceId deviceId) { - TransportRateLimit[] limits = perDeviceLimits.get(deviceId); + private EntityTransportRateLimits getDeviceRateLimits(TenantId tenantId, DeviceId deviceId) { + EntityTransportRateLimits limits = perDeviceLimits.get(deviceId); if (limits == null) { - limits = fetchProfileAndInit(tenantId); + limits = createRateLimits(tenantProfileCache.get(tenantId), false); perDeviceLimits.put(deviceId, limits); + tenantDevices.computeIfAbsent(tenantId, id -> ConcurrentHashMap.newKeySet()).add(deviceId); } return limits; } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitFactory.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/EntityTransportRateLimits.java similarity index 70% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitFactory.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/EntityTransportRateLimits.java index 54d83610c2..50831ccff7 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitFactory.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/EntityTransportRateLimits.java @@ -15,10 +15,15 @@ */ package org.thingsboard.server.common.transport.limits; -public interface TransportRateLimitFactory { +import lombok.AllArgsConstructor; +import lombok.Data; - TransportRateLimit create(TransportRateLimitType type, Object config); +@Data +@AllArgsConstructor +public class EntityTransportRateLimits { - TransportRateLimit createDefault(TransportRateLimitType type); + private TransportRateLimit regularMsgRateLimit; + private TransportRateLimit telemetryMsgRateLimit; + private TransportRateLimit telemetryDataPointsRateLimit; } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java index 3253272ded..0ea920a4ba 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java @@ -26,6 +26,11 @@ public class SimpleTransportRateLimit implements TransportRateLimit { @Getter private final String configuration; + public SimpleTransportRateLimit(String configuration) { + this.configuration = configuration; + this.rateLimit = new TbRateLimits(configuration); + } + @Override public boolean tryConsume() { return rateLimit.tryConsume(); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java index 2ab1ec38ac..3918927c0b 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java @@ -15,13 +15,14 @@ */ package org.thingsboard.server.common.transport.limits; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; public interface TransportRateLimitService { - TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits); + EntityType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints); void update(TenantProfileUpdateResult update); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java deleted file mode 100644 index 038de86e84..0000000000 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.common.transport.limits; - -import lombok.Getter; - -public enum TransportRateLimitType { - - TENANT_ADDED_TO_DISABLED_LIST("general.tenant.disabled", true, false), - TENANT_MAX_MSGS("transport.tenant.msg", true, true), - TENANT_TELEMETRY_MSGS("transport.tenant.telemetry", true, true), - TENANT_MAX_DATA_POINTS("transport.tenant.dataPoints", true, false), - DEVICE_MAX_MSGS("transport.device.msg", false, true), - DEVICE_TELEMETRY_MSGS("transport.device.telemetry", false, true), - DEVICE_MAX_DATA_POINTS("transport.device.dataPoints", false, false); - - @Getter - private final String configurationKey; - @Getter - private final boolean tenantLevel; - @Getter - private final boolean deviceLevel; - @Getter - private final boolean messageLevel; - @Getter - private final boolean dataPointLevel; - - TransportRateLimitType(String configurationKey, boolean tenantLevel, boolean messageLevel) { - this.configurationKey = configurationKey; - this.tenantLevel = tenantLevel; - this.deviceLevel = !tenantLevel; - this.messageLevel = messageLevel; - this.dataPointLevel = !messageLevel; - } -} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 326f996d14..f77a3ce44b 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -55,8 +55,6 @@ import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGateway import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.limits.TransportRateLimitService; -import org.thingsboard.server.common.transport.limits.TransportRateLimitType; -import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.gen.transport.TransportProtos; @@ -364,7 +362,7 @@ public class DefaultTransportService implements TransportService { for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { dataPoints += tsKv.getKvCount(); } - if (checkLimits(sessionInfo, msg, callback, dataPoints, TELEMETRY)) { + if (checkLimits(sessionInfo, msg, callback, dataPoints)) { reportActivityInternal(sessionInfo); TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); @@ -385,7 +383,7 @@ public class DefaultTransportService implements TransportService { @Override public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback callback) { - if (checkLimits(sessionInfo, msg, callback, msg.getKvCount(), TELEMETRY)) { + if (checkLimits(sessionInfo, msg, callback, msg.getKvCount())) { reportActivityInternal(sessionInfo); TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); @@ -575,31 +573,23 @@ public class DefaultTransportService implements TransportService { sessions.remove(toSessionId(sessionInfo)); } - private TransportRateLimitType[] DEFAULT = new TransportRateLimitType[]{TransportRateLimitType.TENANT_MAX_MSGS, TransportRateLimitType.DEVICE_MAX_MSGS}; - private TransportRateLimitType[] TELEMETRY = TransportRateLimitType.values(); - - @Override - public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback) { - return checkLimits(sessionInfo, msg, callback, 0, DEFAULT); + private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback) { + return checkLimits(sessionInfo, msg, callback, 0); } - @Override - public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints, TransportRateLimitType... limits) { + private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints) { if (log.isTraceEnabled()) { log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg); } TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); - TransportRateLimitType limit = rateLimitService.checkLimits(tenantId, deviceId, 0, limits); - if (limit == null) { + EntityType rateLimitedEntityType = rateLimitService.checkLimits(tenantId, deviceId, dataPoints); + if (rateLimitedEntityType == null) { return true; } else { if (callback != null) { - callback.onError(new TbRateLimitsException(limit.isTenantLevel() ? EntityType.TENANT : EntityType.DEVICE)); - } - if (log.isTraceEnabled()) { - log.trace("[{}][{}] {} rateLimit detected: {}", toSessionId(sessionInfo), tenantId, limit, msg); + callback.onError(new TbRateLimitsException(rateLimitedEntityType)); } return false; } @@ -831,8 +821,8 @@ public class DefaultTransportService implements TransportService { @Override public void onSuccess(T msg) { try { - apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.MSG_COUNT, 1); - apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.DP_TRANSPORT_COUNT, dataPoints); + apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.TRANSPORT_MSG_COUNT, 1); + apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.TRANSPORT_DP_COUNT, dataPoints); } finally { callback.onSuccess(msg); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java index 18c1512a27..6773d09417 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java index 864679ce2a..af327d3505 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; @@ -145,7 +146,9 @@ public class TenantProfileServiceImpl extends AbstractEntityService implements T defaultTenantProfile = new TenantProfile(); defaultTenantProfile.setDefault(true); defaultTenantProfile.setName("Default"); - defaultTenantProfile.setProfileData(new TenantProfileData()); + TenantProfileData profileData = new TenantProfileData(); + profileData.setConfiguration(new DefaultTenantProfileConfiguration()); + defaultTenantProfile.setProfileData(profileData); defaultTenantProfile.setDescription("Default tenant profile"); defaultTenantProfile.setIsolatedTbCore(false); defaultTenantProfile.setIsolatedTbRuleEngine(false); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java index 22830bed8c..f93fc2fb1c 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.dao.exception.DataValidationException; @@ -262,7 +263,9 @@ public class BaseTenantProfileServiceTest extends AbstractServiceTest { TenantProfile tenantProfile = new TenantProfile(); tenantProfile.setName(name); tenantProfile.setDescription(name + " Test"); - tenantProfile.setProfileData(new TenantProfileData()); + TenantProfileData profileData = new TenantProfileData(); + profileData.setConfiguration(new DefaultTenantProfileConfiguration()); + tenantProfile.setProfileData(profileData); tenantProfile.setDefault(false); tenantProfile.setIsolatedTbCore(false); tenantProfile.setIsolatedTbRuleEngine(false); From 4eafa92cb73fb72f265a925ebd5ebd2f27484f08 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 23 Oct 2020 15:40:37 +0300 Subject: [PATCH 246/463] added parser tests --- .../mqtt/AbstractMqttIntegrationTest.java | 40 +- .../server/mqtt/MqttSqlTestSuite.java | 3 +- ...tMqttAttributesRequestIntegrationTest.java | 1 + ...AttributesRequestProtoIntegrationTest.java | 3 +- ...tMqttAttributesUpdatesIntegrationTest.java | 18 +- ...tAttributesUpdatesJsonIntegrationTest.java | 18 +- ...AttributesUpdatesProtoIntegrationTest.java | 2 - .../server/mqtt/proto/DynamicProtoTest.java | 408 ++++++++++++++++++ ...MqttServerSideRpcProtoIntegrationTest.java | 4 - ...toDeviceProfileTransportConfiguration.java | 244 ++++++++--- pom.xml | 2 +- 11 files changed, 659 insertions(+), 84 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java diff --git a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java index e7a3a3deb9..0ac5f679e0 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java @@ -62,6 +62,41 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest private static final AtomicInteger atomicInteger = new AtomicInteger(2); + protected static final String DEVICE_TELEMETRY_PROTO_SCHEMA = "syntax = \"proto2\";\n" + + "package test;\n" + + "\n" + + "option java_package = \"org.thingsboard.server.gen.test\";\n" + + "option java_outer_classname = \"TestProtos\";\n" + + "\n" + + "message nestedPostTelemetry {\n" + + " required bool bool_v = 1;\n" + + " required int64 long_v = 2;\n" + + " required double double_v = 3;\n" + + " required string string_v = 4;\n" + + " required string json_v = 5;\n" + + "}\n" + + "\n" + + "message PostTelemetry {\n" + + " repeated nestedPostTelemetry postTelemetry = 1;\n" + + "}"; + protected static final String DEVICE_ATTRIBUTES_PROTO_SCHEMA = "syntax = \"proto2\";\n" + + "package test;\n" + + "\n" + + "option java_package = \"org.thingsboard.server.gen.test\";\n" + + "option java_outer_classname = \"TestProtos\";\n" + + "\n" + + "message nestedPostAttributes {\n" + + " required bool bool_v = 1;\n" + + " required int64 long_v = 2;\n" + + " required double double_v = 3;\n" + + " required string string_v = 4;\n" + + " required string json_v = 5;\n" + + "}\n" + + "\n" + + "message PostAttributes {\n" + + " repeated nestedPostAttributes postAttributes = 1;\n" + + "}"; + protected Tenant savedTenant; protected User tenantAdmin; @@ -219,7 +254,10 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest if (TransportPayloadType.JSON.equals(transportPayloadType)) { transportConfiguration = new MqttJsonDeviceProfileTransportConfiguration(); } else { - transportConfiguration = new MqttProtoDeviceProfileTransportConfiguration(); + MqttProtoDeviceProfileTransportConfiguration protoTransportConfiguration = new MqttProtoDeviceProfileTransportConfiguration(); + protoTransportConfiguration.setDeviceAttributesProtoSchema(DEVICE_TELEMETRY_PROTO_SCHEMA); + protoTransportConfiguration.setDeviceTelemetryProtoSchema(DEVICE_ATTRIBUTES_PROTO_SCHEMA); + transportConfiguration = protoTransportConfiguration; } if (!StringUtils.isEmpty(telemetryTopic)) { transportConfiguration.setDeviceTelemetryTopic(telemetryTopic); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java index f71e8f37a2..440c587856 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java @@ -32,7 +32,8 @@ import java.util.Arrays; "org.thingsboard.server.mqtt.attributes.updates.sql.*Test", "org.thingsboard.server.mqtt.attributes.request.sql.*Test", "org.thingsboard.server.mqtt.claim.sql.*Test", - "org.thingsboard.server.mqtt.provision.sql.*Test" + "org.thingsboard.server.mqtt.provision.sql.*Test", + "org.thingsboard.server.mqtt.proto.sql.*Test" }) public class MqttSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java index 9e4529caf2..438757bfff 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java @@ -25,6 +25,7 @@ import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.device.profile.MqttTopics; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java index 80a469ed12..d2981de353 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java @@ -55,15 +55,14 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends processAfterTest(); } - @Test @Ignore + @Test public void testRequestAttributesValuesFromTheServer() throws Exception { processTestRequestAttributesValuesFromTheServer(); } @Test - @Ignore public void testRequestAttributesValuesFromTheServerGateway() throws Exception { processTestGatewayRequestAttributesValuesFromTheServer(); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java index d2febdf357..c40c2f2914 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java @@ -65,15 +65,15 @@ public abstract class AbstractMqttAttributesUpdatesIntegrationTest extends Abstr processAfterTest(); } - @Test - public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { - processTestSubscribeToAttributesUpdates(); - } - - @Test - public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { - processGatewayTestSubscribeToAttributesUpdates(); - } +// @Test +// public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { +// processTestSubscribeToAttributesUpdates(); +// } +// +// @Test +// public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { +// processGatewayTestSubscribeToAttributesUpdates(); +// } protected void processTestSubscribeToAttributesUpdates() throws Exception { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java index 58379e4016..4f5a5f9369 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java @@ -39,13 +39,13 @@ public abstract class AbstractMqttAttributesUpdatesJsonIntegrationTest extends A processAfterTest(); } - @Test - public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { - processTestSubscribeToAttributesUpdates(); - } - - @Test - public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { - processGatewayTestSubscribeToAttributesUpdates(); - } +// @Test +// public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { +// processTestSubscribeToAttributesUpdates(); +// } +// +// @Test +// public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { +// processGatewayTestSubscribeToAttributesUpdates(); +// } } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java index e27fdda95c..16ccc789ee 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java @@ -48,13 +48,11 @@ public abstract class AbstractMqttAttributesUpdatesProtoIntegrationTest extends } @Test - @Ignore public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { processTestSubscribeToAttributesUpdates(); } @Test - @Ignore public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { processGatewayTestSubscribeToAttributesUpdates(); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java b/application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java new file mode 100644 index 0000000000..eb6912f257 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java @@ -0,0 +1,408 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.mqtt.proto; + +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; +import com.squareup.wire.schema.internal.parser.ProtoParser; +import lombok.extern.slf4j.Slf4j; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; +import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; + +import java.util.List; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration.LOCATION; + +@Slf4j +@RunWith(MockitoJUnitRunner.class) +public class DynamicProtoTest { + + private static final String PROTO_SCHEMA_WITH_NESTED_MSG_TYPES = "syntax = \"proto3\";\n" + + "\n" + + "package testnested;\n" + + "\n" + + "message Outer {\n" + + " message MiddleAA {\n" + + " message Inner {\n" + + " int64 ival = 1;\n" + + " bool booly = 2;\n" + + " }\n" + + " Inner inner = 1;\n" + + " }\n" + + " message MiddleBB {\n" + + " message Inner {\n" + + " int32 ival = 1;\n" + + " bool booly = 2;\n" + + " }\n" + + " Inner inner = 1;\n" + + " }\n" + + " MiddleAA middleAA = 1;\n" + + " MiddleBB middleBB = 2;\n" + + "}\n"; + + private static final String PROTO_SCHEMA_WITH_ONE_OFS = "syntax = \"proto3\";\n" + + "\n" + + "package testoneofs;\n" + + "\n" + + "message SubMessage {\n" + + " repeated string name = 1;\n" + + "}\n" + + "\n" + + "message SampleMessage {\n" + + " oneof testOneOf {\n" + + " string name = 4;\n" + + " SubMessage subMessage = 9;\n" + + " }\n" + + "}"; + + private static final String IVALID_PROTO_SCHEMA_REQUIRED_FIELD_EXISTS = "syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " required int32 parameter = 1;\n" + + "}"; + + private static final String INVALID_PROTO_SCHEMA_NOT_VALID_SYNTAX = "syntax = \"proto2\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " required int32 parameter = 1;\n" + + "}"; + + private static final String INVALID_PROTO_SCHEMA_OPTIONS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + + "\n" + + "option java_package = \"com.test.schemavalidation\";\n" + + "option java_multiple_files = true;\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " int32 parameter = 1;\n" + + "}"; + + private static final String INVALID_PROTO_SCHEMA_PUBLIC_IMPORTS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + + "\n" + + "import public \"oldschema.proto\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " int32 parameter = 1;\n" + + "}"; + + private static final String INVALID_PROTO_SCHEMA_IMPORTS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + + "\n" + + "import \"oldschema.proto\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " int32 parameter = 1;\n" + + "}"; + + private static final String INVALID_PROTO_SCHEMA_EXTEND_DECLARATION_NOT_SUPPORTED = "syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "extend google.protobuf.MethodOptions {\n" + + " MyMessage my_method_option = 50007;\n" + + "}"; + + private static final String INVALID_PROTO_SCHEMA_ENUM_OPTIONS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "enum testEnum {\n" + + " option allow_alias = true;\n" + + " DEFAULT = 0;\n" + + " STARTED = 1;\n" + + " RUNNING = 2;\n" + + "}\n" + + "\n" + + "message testMessage {\n" + + " int32 parameter = 1;\n" + + "}\n"; + + private static final String INVALID_PROTO_SCHEMA_NO_MESSAGE_TYPES_EXISTS = "syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "enum testEnum {\n" + + " DEFAULT = 0;\n" + + " STARTED = 1;\n" + + " RUNNING = 2;\n" + + "}"; + + private static final String INVALID_PROTO_SCHEMA_MESSAGE_OPTIONS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message testMessage {\n" + + " option allow_alias = true;\n" + + " int32 parameter = 1;\n" + + "}"; + + private static final String INVALID_PROTO_SCHEMA_MESSAGE_EXTENSIONS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message TestMessage {\n" + + " extensions 100 to 199;\n" + + "}\n"; + + private static final String INVALID_PROTO_SCHEMA_MESSAGE_GROUPS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message TestMessage {\n" + + " repeated group Result = 1 {\n" + + " string url = 2;\n" + + " string title = 3;\n" + + " repeated string snippets = 4;\n" + + " }\n" + + "}\n"; + + private static final String INVALID_PROTO_SCHEMA_MESSAGE_RESERVED_NOT_SUPPORTED = "syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message Foo {\n" + + " reserved 2, 15, 9 to 11;\n" + + " reserved \"foo\", \"bar\";\n" + + "}"; + + private static final String INVALID_PROTO_SCHEMA_ONE_OFS_GROUPS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SampleMessage {\n" + + " oneof test_oneof {\n" + + " string name = 1;\n" + + " group Result = 2 {\n" + + " \tstring url = 3;\n" + + " \tstring title = 4;\n" + + " \trepeated string snippets = 5;\n" + + " }\n" + + " }\n" + + "}"; + + private static final MqttProtoDeviceProfileTransportConfiguration mqttProtoDeviceProfileTransportConfiguration = new MqttProtoDeviceProfileTransportConfiguration(); + + private static void validateTransportProtoSchema(String schema, String schemaName) { + mqttProtoDeviceProfileTransportConfiguration.validateTransportProtoSchema(schema, schemaName); + } + + private static DynamicSchema getDynamicSchema(String schema, String schemaName) { + ProtoFileElement protoFileElement = getTransportProtoSchema(schema); + return mqttProtoDeviceProfileTransportConfiguration.getDynamicSchema(protoFileElement, schemaName); + } + + @Rule + public ExpectedException exceptionRule = ExpectedException.none(); + + @Test + public void testDynamicSchemaProtoFileValidation() { + processValidation("Failed to parse: testParseToProtoFile schema due to: Syntax error in :6:4: 'required' label forbidden in proto3 field declarations", IVALID_PROTO_SCHEMA_REQUIRED_FIELD_EXISTS, "testParseToProtoFile"); + } + + @Test + public void testDynamicSchemaSyntaxValidation() { + processValidation("Invalid schema syntax: proto2 for: testSyntaxValidation provided! Only proto3 allowed!", INVALID_PROTO_SCHEMA_NOT_VALID_SYNTAX, "testSyntaxValidation"); + } + + @Test + public void testDynamicSchemaOptionsValidation() { + processValidation("Invalid testOptionsValidation schema provided! Schema options don't support!", INVALID_PROTO_SCHEMA_OPTIONS_NOT_SUPPORTED, "testOptionsValidation"); + } + + @Test + public void testDynamicSchemaPublicImportsValidation() { + processValidation("Invalid testPublicImportsValidation schema provided! Schema public imports don't support!", INVALID_PROTO_SCHEMA_PUBLIC_IMPORTS_NOT_SUPPORTED, "testPublicImportsValidation"); + } + + @Test + public void testDynamicSchemaImportsValidation() { + processValidation("Invalid testImportsValidation schema provided! Schema imports don't support!", INVALID_PROTO_SCHEMA_IMPORTS_NOT_SUPPORTED, "testImportsValidation"); + } + + @Test + public void testDynamicSchemaExtendDeclarationsValidation() { + processValidation("Invalid testExtendDeclarationsValidation schema provided! Schema extend declarations don't support!", INVALID_PROTO_SCHEMA_EXTEND_DECLARATION_NOT_SUPPORTED, "testExtendDeclarationsValidation"); + } + + @Test + public void testDynamicSchemaEnumOptionsValidation() { + processValidation("Invalid testEnumOptionsValidation schema provided! Enum definitions options are not supported!", INVALID_PROTO_SCHEMA_ENUM_OPTIONS_NOT_SUPPORTED, "testEnumOptionsValidation"); + } + + @Test + public void testDynamicSchemaNoOneMessageTypeExistsValidation() { + processValidation("Invalid noOneMessageTypeExists schema provided! At least one Message definition should exists!", INVALID_PROTO_SCHEMA_NO_MESSAGE_TYPES_EXISTS, "noOneMessageTypeExists"); + } + + @Test + public void testDynamicSchemaMessageTypeOptionsValidation() { + processValidation("Invalid messageTypeOptions schema provided! Message definition options don't support!", INVALID_PROTO_SCHEMA_MESSAGE_OPTIONS_NOT_SUPPORTED, "messageTypeOptions"); + } + + @Test + public void testDynamicSchemaMessageTypeExtensionsValidation() { + processValidation("Invalid messageTypeExtensions schema provided! Message definition extensions don't support!", INVALID_PROTO_SCHEMA_MESSAGE_EXTENSIONS_NOT_SUPPORTED, "messageTypeExtensions"); + } + + @Test + public void testDynamicSchemaMessageTypeReservedElementsValidation() { + processValidation("Invalid messageTypeReservedElements schema provided! Message definition reserved elements don't support!", INVALID_PROTO_SCHEMA_MESSAGE_RESERVED_NOT_SUPPORTED, "messageTypeReservedElements"); + } + + @Test + public void testDynamicSchemaMessageTypeGroupsElementsValidation() { + processValidation("Invalid messageTypeGroupsElements schema provided! Message definition groups don't support!", INVALID_PROTO_SCHEMA_MESSAGE_GROUPS_NOT_SUPPORTED, "messageTypeGroupsElements"); + } + + @Test + public void testDynamicSchemaOneOfsTypeGroupsElementsValidation() { + processValidation("Invalid oneOfsTypeGroupsElements schema provided! OneOf definition groups don't support!", INVALID_PROTO_SCHEMA_ONE_OFS_GROUPS_NOT_SUPPORTED, "oneOfsTypeGroupsElements"); + } + + private void processValidation(String expectedMessage, String schema, String schemaName) { + exceptionRule.expect(IllegalArgumentException.class); + exceptionRule.expectMessage(expectedMessage); + validateTransportProtoSchema(schema, schemaName); + } + + @Test + public void testDynamicSchemaCreationWithMessageNestedTypes() throws Exception { + String testNestedTypesProtoSchema = "testNestedTypesProtoSchema"; + validateTransportProtoSchema(PROTO_SCHEMA_WITH_NESTED_MSG_TYPES, testNestedTypesProtoSchema); + DynamicSchema dynamicSchema = getDynamicSchema(PROTO_SCHEMA_WITH_NESTED_MSG_TYPES, testNestedTypesProtoSchema); + assertNotNull(dynamicSchema); + Set messageTypes = dynamicSchema.getMessageTypes(); + assertEquals(5, messageTypes.size()); + assertTrue(messageTypes.contains("testnested.Outer")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleAA")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleAA.Inner")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleBB")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleBB.Inner")); + + DynamicMessage.Builder middleAAInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); + Descriptors.Descriptor middleAAInnerMsgDescriptor = middleAAInnerMsgBuilder.getDescriptorForType(); + DynamicMessage middleAAInnerMsg = middleAAInnerMsgBuilder + .setField(middleAAInnerMsgDescriptor.findFieldByName("ival"), 1L) + .setField(middleAAInnerMsgDescriptor.findFieldByName("booly"), true) + .build(); + + DynamicMessage.Builder middleAAMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA"); + Descriptors.Descriptor middleAAMsgDescriptor = middleAAMsgBuilder.getDescriptorForType(); + DynamicMessage middleAAMsg = middleAAMsgBuilder + .setField(middleAAMsgDescriptor.findFieldByName("inner"), middleAAInnerMsg) + .build(); + + DynamicMessage.Builder middleBBInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); + Descriptors.Descriptor middleBBInnerMsgDescriptor = middleBBInnerMsgBuilder.getDescriptorForType(); + DynamicMessage middleBBInnerMsg = middleBBInnerMsgBuilder + .setField(middleBBInnerMsgDescriptor.findFieldByName("ival"), 0L) + .setField(middleBBInnerMsgDescriptor.findFieldByName("booly"), false) + .build(); + + DynamicMessage.Builder middleBBMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleBB"); + Descriptors.Descriptor middleBBMsgDescriptor = middleBBMsgBuilder.getDescriptorForType(); + DynamicMessage middleBBMsg = middleBBMsgBuilder + .setField(middleBBMsgDescriptor.findFieldByName("inner"), middleBBInnerMsg) + .build(); + + + DynamicMessage.Builder outerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer"); + Descriptors.Descriptor outerMsgBuilderDescriptor = outerMsgBuilder.getDescriptorForType(); + DynamicMessage outerMsg = outerMsgBuilder + .setField(outerMsgBuilderDescriptor.findFieldByName("middleAA"), middleAAMsg) + .setField(outerMsgBuilderDescriptor.findFieldByName("middleBB"), middleBBMsg) + .build(); + + assertEquals("{\n" + + " \"middleAA\": {\n" + + " \"inner\": {\n" + + " \"ival\": \"1\",\n" + + " \"booly\": true\n" + + " }\n" + + " },\n" + + " \"middleBB\": {\n" + + " \"inner\": {\n" + + " \"ival\": 0,\n" + + " \"booly\": false\n" + + " }\n" + + " }\n" + + "}", dynamicMsgToJson(outerMsgBuilderDescriptor, outerMsg.toByteArray())); + } + + @Test + public void testDynamicSchemaCreationWithMessageOneOfs() throws Exception { + String testOneOfsProtoSchema = "testOneOfsProtoSchema"; + validateTransportProtoSchema(PROTO_SCHEMA_WITH_ONE_OFS, testOneOfsProtoSchema); + DynamicSchema dynamicSchema = getDynamicSchema(PROTO_SCHEMA_WITH_ONE_OFS, testOneOfsProtoSchema); + assertNotNull(dynamicSchema); + Set messageTypes = dynamicSchema.getMessageTypes(); + assertEquals(2, messageTypes.size()); + assertTrue(messageTypes.contains("testoneofs.SubMessage")); + assertTrue(messageTypes.contains("testoneofs.SampleMessage")); + + DynamicMessage.Builder sampleMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SampleMessage"); + Descriptors.Descriptor sampleMsgDescriptor = sampleMsgBuilder.getDescriptorForType(); + assertNotNull(sampleMsgDescriptor); + + List fields = sampleMsgDescriptor.getFields(); + assertEquals(2, fields.size()); + DynamicMessage sampleMsg = sampleMsgBuilder + .setField(sampleMsgDescriptor.findFieldByName("name"), "Bob") + .build(); + assertEquals("{\n" + " \"name\": \"Bob\"\n" + "}", dynamicMsgToJson(sampleMsgDescriptor, sampleMsg.toByteArray())); + + DynamicMessage.Builder subMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SubMessage"); + Descriptors.Descriptor subMsgDescriptor = subMsgBuilder.getDescriptorForType(); + DynamicMessage subMsg = subMsgBuilder + .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "Alice") + .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "John") + .build(); + + DynamicMessage sampleMsgWithOneOfSubMessage = sampleMsgBuilder.setField(sampleMsgDescriptor.findFieldByName("subMessage"), subMsg).build(); + assertEquals("{\n" + " \"subMessage\": {\n" + " \"name\": [\"Alice\", \"John\"]\n" + " }\n" + "}", + dynamicMsgToJson(sampleMsgDescriptor, sampleMsgWithOneOfSubMessage.toByteArray())); + + } + + private String dynamicMsgToJson(Descriptors.Descriptor descriptor, byte[] payload) throws InvalidProtocolBufferException { + DynamicMessage dynamicMessage = DynamicMessage.parseFrom(descriptor, payload); + return JsonFormat.printer().includingDefaultValueFields().print(dynamicMessage); + } + + private static ProtoFileElement getTransportProtoSchema(String protoSchema) { + return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); + } +} diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java index 41d5d380f4..759a5da912 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java @@ -46,25 +46,21 @@ public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends Abst } @Test - @Ignore public void testServerMqttOneWayRpc() throws Exception { processOneWayRpcTest(); } @Test - @Ignore public void testServerMqttTwoWayRpc() throws Exception { processTwoWayRpcTest(); } @Test - @Ignore public void testGatewayServerMqttOneWayRpc() throws Exception { processOneWayRpcTestGateway("Gateway Device OneWay RPC Proto"); } @Test - @Ignore public void testGatewayServerMqttTwoWayRpc() throws Exception { processTwoWayRpcTestGateway("Gateway Device TwoWay RPC Proto"); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java index 06e8d57c8e..c96a1a565f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java @@ -22,12 +22,13 @@ import com.github.os72.protobuf.dynamic.EnumDefinition; import com.github.os72.protobuf.dynamic.MessageDefinition; import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; -import com.squareup.wire.schema.Field; +import com.squareup.wire.Syntax; import com.squareup.wire.schema.Location; import com.squareup.wire.schema.internal.parser.EnumConstantElement; import com.squareup.wire.schema.internal.parser.EnumElement; import com.squareup.wire.schema.internal.parser.FieldElement; import com.squareup.wire.schema.internal.parser.MessageElement; +import com.squareup.wire.schema.internal.parser.OneOfElement; import com.squareup.wire.schema.internal.parser.ProtoFileElement; import com.squareup.wire.schema.internal.parser.ProtoParser; import com.squareup.wire.schema.internal.parser.TypeElement; @@ -37,6 +38,8 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -50,6 +53,10 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf public static final String ATTRIBUTES_PROTO_SCHEMA = "attributes proto schema"; public static final String TELEMETRY_PROTO_SCHEMA = "telemetry proto schema"; + public static String invalidSchemaProvidedMessage(String schemaName) { + return "Invalid " + schemaName + " schema provided!"; + } + private String deviceTelemetryProtoSchema; private String deviceAttributesProtoSchema; @@ -69,85 +76,212 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf try { protoFileElement = schemaParser.readProtoFile(); } catch (Exception e) { - throw new IllegalArgumentException("Failed to parse: " + schemaName + " due to: " + e.getMessage()); - } - List types = protoFileElement.getTypes(); - if (!types.isEmpty()) { - if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) { - throw new IllegalArgumentException("Invalid " + schemaName + " provided! At least one Message definition should exists!"); - } - } else { - throw new IllegalArgumentException("Invalid " + schemaName + " provided!"); + throw new IllegalArgumentException("Failed to parse: " + schemaName + " schema due to: " + e.getMessage()); } + checkProtoFileSyntax(schemaName, protoFileElement); + checkProtoFileCommonSettings(schemaName, protoFileElement.getOptions().isEmpty(), " Schema options don't support!"); + checkProtoFileCommonSettings(schemaName, protoFileElement.getPublicImports().isEmpty(), " Schema public imports don't support!"); + checkProtoFileCommonSettings(schemaName, protoFileElement.getImports().isEmpty(), " Schema imports don't support!"); + checkProtoFileCommonSettings(schemaName, protoFileElement.getExtendDeclarations().isEmpty(), " Schema extend declarations don't support!"); + checkTypeElements(schemaName, protoFileElement); } public Descriptors.Descriptor getDynamicMessageDescriptor(String protoSchema, String schemaName) { - ProtoFileElement protoFileElement = getTransportProtoSchema(protoSchema); + try { + ProtoFileElement protoFileElement = getTransportProtoSchema(protoSchema); + DynamicSchema dynamicSchema = getDynamicSchema(protoFileElement, schemaName); + String lastMsgName = getMessageTypes(protoFileElement.getTypes()).stream() + .map(MessageElement::getName).reduce((previous, last) -> last).get(); + DynamicMessage.Builder builder = dynamicSchema.newMessageBuilder(lastMsgName); + return builder.getDescriptorForType(); + } catch (Exception e) { + log.warn("Failed to get Message Descriptor due to {}", e.getMessage()); + return null; + } + } + + public DynamicSchema getDynamicSchema(ProtoFileElement protoFileElement, String schemaName) { DynamicSchema.Builder schemaBuilder = DynamicSchema.newBuilder(); schemaBuilder.setName(schemaName); schemaBuilder.setPackage(!isEmptyStr(protoFileElement.getPackageName()) ? protoFileElement.getPackageName() : schemaName.toLowerCase()); + List types = protoFileElement.getTypes(); + List messageTypes = getMessageTypes(types); + + if (!messageTypes.isEmpty()) { + List enumTypes = getEnumElements(types); + if (!enumTypes.isEmpty()) { + enumTypes.forEach(enumElement -> { + EnumDefinition enumDefinition = getEnumDefinition(enumElement); + schemaBuilder.addEnumDefinition(enumDefinition); + }); + } + List messageDefinitions = getMessageDefinitions(messageTypes); + messageDefinitions.forEach(schemaBuilder::addMessageDefinition); + try { + return schemaBuilder.build(); + } catch (Descriptors.DescriptorValidationException e) { + throw new RuntimeException("Failed to create dynamic schema due to: " + e.getMessage()); + } + } else { + throw new RuntimeException("Failed to get Dynamic Schema! Message types is empty for schema:" + schemaName); + } + } + + private void checkProtoFileSyntax(String schemaName, ProtoFileElement protoFileElement) { + if (protoFileElement.getSyntax() == null || !protoFileElement.getSyntax().equals(Syntax.PROTO_3)) { + throw new IllegalArgumentException("Invalid schema syntax: " + protoFileElement.getSyntax() + + " for: " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!"); + } + } + + private void checkProtoFileCommonSettings(String schemaName, boolean isEmptySettings, String invalidSettingsMessage) { + if (!isEmptySettings) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + invalidSettingsMessage); + } + } + private void checkTypeElements(String schemaName, ProtoFileElement protoFileElement) { List types = protoFileElement.getTypes(); + if (!types.isEmpty()) { + if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " At least one Message definition should exists!"); + } else { + checkEnumElements(schemaName, getEnumElements(types)); + checkMessageElements(schemaName, getMessageTypes(types)); + } + } else { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Type elements is empty!"); + } + } - List enumTypes = types.stream() - .filter(typeElement -> typeElement instanceof EnumElement) - .map(typeElement -> (EnumElement) typeElement) - .collect(Collectors.toList()); + private void checkEnumElements(String schemaName, List enumTypes) { + if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getNestedTypes().isEmpty())) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Nested types in Enum definitions are not supported!"); + } + if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getOptions().isEmpty())) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Enum definitions options are not supported!"); + } + } + + private void checkMessageElements(String schemaName, List messageElementsList) { + if (!messageElementsList.isEmpty()) { + messageElementsList.forEach(messageElement -> { + checkProtoFileCommonSettings(schemaName, messageElement.getGroups().isEmpty(), + " Message definition groups don't support!"); + checkProtoFileCommonSettings(schemaName, messageElement.getOptions().isEmpty(), + " Message definition options don't support!"); + checkProtoFileCommonSettings(schemaName, messageElement.getExtensions().isEmpty(), + " Message definition extensions don't support!"); + checkProtoFileCommonSettings(schemaName, messageElement.getReserveds().isEmpty(), + " Message definition reserved elements don't support!"); + List oneOfs = messageElement.getOneOfs(); + if (!oneOfs.isEmpty()) { + oneOfs.forEach(oneOfElement -> + checkProtoFileCommonSettings(schemaName, oneOfElement.getGroups().isEmpty(), + " OneOf definition groups don't support!")); + } + List nestedTypes = messageElement.getNestedTypes(); + if (!nestedTypes.isEmpty()) { + List nestedEnumTypes = getEnumElements(nestedTypes); + if (!nestedEnumTypes.isEmpty()) { + checkEnumElements(schemaName, nestedEnumTypes); + } + List nestedMessageTypes = getMessageTypes(nestedTypes); + checkMessageElements(schemaName, nestedMessageTypes); + } + }); + } + } + + private ProtoFileElement getTransportProtoSchema(String protoSchema) { + return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); + } - List messageTypes = types.stream() + private List getMessageTypes(List types) { + return types.stream() .filter(typeElement -> typeElement instanceof MessageElement) .map(typeElement -> (MessageElement) typeElement) .collect(Collectors.toList()); + } - if (!enumTypes.isEmpty()) { - enumTypes.forEach(enumElement -> { - List enumElementTypeConstants = enumElement.getConstants(); - EnumDefinition.Builder enumDefinitionBuilder = EnumDefinition.newBuilder(enumElement.getName()); - if (!enumElementTypeConstants.isEmpty()) { - enumElementTypeConstants.forEach(constantElement -> enumDefinitionBuilder.addValue(constantElement.getName(), constantElement.getTag())); - } - EnumDefinition enumDefinition = enumDefinitionBuilder.build(); - schemaBuilder.addEnumDefinition(enumDefinition); - }); - } + private List getEnumElements(List types) { + return types.stream() + .filter(typeElement -> typeElement instanceof EnumElement) + .map(typeElement -> (EnumElement) typeElement) + .collect(Collectors.toList()); + } - if (!messageTypes.isEmpty()) { - messageTypes.forEach(messageElement -> { - List messageElementFields = messageElement.getFields(); + private List getMessageDefinitions(List messageElementsList) { + if (!messageElementsList.isEmpty()) { + List messageDefinitions = new ArrayList<>(); + messageElementsList.forEach(messageElement -> { MessageDefinition.Builder messageDefinitionBuilder = MessageDefinition.newBuilder(messageElement.getName()); + List messageElementFields = messageElement.getFields(); + List oneOfs = messageElement.getOneOfs(); + + List nestedTypes = messageElement.getNestedTypes(); if (!messageElementFields.isEmpty()) { - messageElementFields.forEach(fieldElement -> { - Field.Label label = fieldElement.getLabel(); - String labelStr = label != null ? label.name() : null; - messageDefinitionBuilder.addField( - labelStr, - fieldElement.getType(), - fieldElement.getName(), - fieldElement.getTag(), - fieldElement.getDefaultValue()); - }); + addMessageFieldsToTheMessageDefinition(messageElementFields, messageDefinitionBuilder); + } + if (!oneOfs.isEmpty()) { + for (OneOfElement oneOfelement: oneOfs) { + MessageDefinition.OneofBuilder oneofBuilder = messageDefinitionBuilder.addOneof(oneOfelement.getName()); + addMessageFieldsToTheOneOfDefinition(oneOfelement.getFields(), oneofBuilder); + } } - MessageDefinition messageDefinition = messageDefinitionBuilder.build(); - schemaBuilder.addMessageDefinition(messageDefinition); + if (!nestedTypes.isEmpty()) { + List nestedEnumTypes = getEnumElements(nestedTypes); + if (!nestedEnumTypes.isEmpty()) { + nestedEnumTypes.forEach(enumElement -> { + EnumDefinition nestedEnumDefinition = getEnumDefinition(enumElement); + messageDefinitionBuilder.addEnumDefinition(nestedEnumDefinition); + }); + } + List nestedMessageTypes = getMessageTypes(nestedTypes); + List nestedMessageDefinitions = getMessageDefinitions(nestedMessageTypes); + nestedMessageDefinitions.forEach(messageDefinitionBuilder::addMessageDefinition); + } + messageDefinitions.add(messageDefinitionBuilder.build()); }); - MessageElement lastMsg = messageTypes.stream().reduce((previous, last) -> last).get(); - try { - DynamicSchema dynamicSchema = schemaBuilder.build(); - DynamicMessage.Builder builder = dynamicSchema.newMessageBuilder(lastMsg.getName()); - return builder.getDescriptorForType(); - } catch (Descriptors.DescriptorValidationException e) { - log.error("Failed to create dynamic schema due to: ", e); - return null; - } + return messageDefinitions; } else { - log.error("Failed to get Message Descriptor! Message types is empty for {} schema!", schemaName); - return null; + return Collections.emptyList(); } } - private ProtoFileElement getTransportProtoSchema(String protoSchema) { - return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); + private EnumDefinition getEnumDefinition(EnumElement enumElement) { + List enumElementTypeConstants = enumElement.getConstants(); + EnumDefinition.Builder enumDefinitionBuilder = EnumDefinition.newBuilder(enumElement.getName()); + if (!enumElementTypeConstants.isEmpty()) { + enumElementTypeConstants.forEach(constantElement -> enumDefinitionBuilder.addValue(constantElement.getName(), constantElement.getTag())); + } + return enumDefinitionBuilder.build(); + } + + + private void addMessageFieldsToTheMessageDefinition(List messageElementFields, MessageDefinition.Builder messageDefinitionBuilder) { + messageElementFields.forEach(fieldElement -> { + String labelStr = null; + if (fieldElement.getLabel() != null) { + labelStr = fieldElement.getLabel().name().toLowerCase(); + } + messageDefinitionBuilder.addField( + labelStr, + fieldElement.getType(), + fieldElement.getName(), + fieldElement.getTag(), + fieldElement.getDefaultValue()); + }); + } + + private void addMessageFieldsToTheOneOfDefinition(List oneOfsElementFields, MessageDefinition.OneofBuilder oneofBuilder) { + oneOfsElementFields.forEach(fieldElement -> oneofBuilder.addField( + fieldElement.getType(), + fieldElement.getName(), + fieldElement.getTag(), + fieldElement.getDefaultValue())); + oneofBuilder.msgDefBuilder(); } private boolean isEmptyStr(String str) { diff --git a/pom.xml b/pom.xml index ef98bd3b79..c6fb306f25 100755 --- a/pom.xml +++ b/pom.xml @@ -106,7 +106,7 @@ 3.2.2 1.5.0 1.5.2 - 1.0.1 + 1.0.2 3.4.0 From 0c1fd9785097cec8948135cb196d65fe8870eabc Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 23 Oct 2020 17:50:15 +0300 Subject: [PATCH 247/463] UI: Improvement photo camera input widget --- .../system/widget_bundles/input_widgets.json | 8 +- ...html => photo-camera-input.component.html} | 17 +-- ...scss => photo-camera-input.component.scss} | 8 ++ ...ent.ts => photo-camera-input.component.ts} | 108 ++++++++++++------ .../widget/widget-components.module.ts | 6 +- .../assets/locale/locale.constant-en_US.json | 5 +- 6 files changed, 98 insertions(+), 54 deletions(-) rename ui-ngx/src/app/modules/home/components/widget/lib/{web-camera-input.component.html => photo-camera-input.component.html} (81%) rename ui-ngx/src/app/modules/home/components/widget/lib/{web-camera-input.component.scss => photo-camera-input.component.scss} (92%) rename ui-ngx/src/app/modules/home/components/widget/lib/{web-camera-input.component.ts => photo-camera-input.component.ts} (72%) diff --git a/application/src/main/data/json/system/widget_bundles/input_widgets.json b/application/src/main/data/json/system/widget_bundles/input_widgets.json index ac3486223a..097c31dac4 100644 --- a/application/src/main/data/json/system/widget_bundles/input_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/input_widgets.json @@ -391,16 +391,16 @@ }, { "alias": "web_camera_input", - "name": "Web Camera Input", + "name": "Photo camera input", "descriptor": { "type": "latest", "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "\n", + "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.webCameraInputWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Web Camera\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"imageFormat\": {\n \"title\": \"Image Format\",\n \"type\": \"string\",\n \"default\": \"image/png\"\n },\n \"imageQuality\":{\n \"title\":\"Image quality that use lossy compression such as jpeg and webp\",\n \"type\":\"number\",\n \"default\": 0.92,\n \"min\": 0,\n \"max\": 1\n },\n \"maxWidth\": {\n \"title\": \"The maximal image width\",\n \"type\": \"number\",\n \"default\": 640\n }, \n \"maxHeight\": {\n \"title\": \"The maximal image heigth\",\n \"type\": \"number\",\n \"default\": 480\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n {\n \"key\": \"imageFormat\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"image/jpeg\",\n \"label\": \"JPEG\"\n },\n {\n \"value\": \"image/png\",\n \"label\": \"PNG\"\n },\n {\n \"value\": \"image/webp\",\n \"label\": \"WEBP\"\n }\n ]\n },\n \"imageQuality\",\n \"maxWidth\",\n \"maxHeight\"\n ]\n}", + "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.photoCameraInputWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n}\n", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Photo Camera\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"imageFormat\": {\n \"title\": \"Image Format\",\n \"type\": \"string\",\n \"default\": \"image/png\"\n },\n \"imageQuality\":{\n \"title\":\"Image quality that use lossy compression such as jpeg and webp\",\n \"type\":\"number\",\n \"default\": 0.92,\n \"min\": 0,\n \"max\": 1\n },\n \"maxWidth\": {\n \"title\": \"The maximal image width\",\n \"type\": \"number\",\n \"default\": 640\n }, \n \"maxHeight\": {\n \"title\": \"The maximal image heigth\",\n \"type\": \"number\",\n \"default\": 480\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n {\n \"key\": \"imageFormat\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"image/jpeg\",\n \"label\": \"JPEG\"\n },\n {\n \"value\": \"image/png\",\n \"label\": \"PNG\"\n },\n {\n \"value\": \"image/webp\",\n \"label\": \"WEBP\"\n }\n ]\n },\n \"imageQuality\",\n \"maxWidth\",\n \"maxHeight\"\n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Web Camera Input\",\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/web-camera-input.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.html similarity index 81% rename from ui-ngx/src/app/modules/home/components/widget/lib/web-camera-input.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.html index 88e5475a40..b3b457849b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/web-camera-input.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.html @@ -16,13 +16,13 @@ -->
-
+
widgets.input-widgets.no-image last photo
-
@@ -59,16 +59,7 @@
-
- {{ 'widgets.input-widgets.no-entity-selected' | translate }} -
-
- {{ 'widgets.input-widgets.no-datakey-selected' | translate }} -
-
- {{ 'widgets.input-widgets.no-support-web-camera' | translate }} -
-
- {{ 'widgets.input-widgets.no-support-web-camera' | translate }} +
+ {{ textMessage | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/web-camera-input.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.scss similarity index 92% rename from ui-ngx/src/app/modules/home/components/widget/lib/web-camera-input.component.scss rename to ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.scss index 94a51e6870..ea364e17a1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/web-camera-input.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.scss @@ -18,6 +18,7 @@ &__last-photo { width: 100%; + min-height: 0; margin: 5px 0; text-align: center; border: solid 1px; @@ -71,4 +72,11 @@ color: #a0a0a0; text-align: center; } + + .image-container{ + height: 100%; + min-height: 0; + width: 100%; + min-width: 100%; + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/web-camera-input.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.ts similarity index 72% rename from ui-ngx/src/app/modules/home/components/widget/lib/web-camera-input.component.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.ts index ee92ee8fe0..aebc500faf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/web-camera-input.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.ts @@ -40,7 +40,7 @@ import { Observable } from 'rxjs'; import { isString } from '@core/utils'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; -interface WebCameraInputWidgetSettings { +interface PhotoCameraInputWidgetSettings { widgetTitle: string; imageQuality: number; imageFormat: string; @@ -50,12 +50,12 @@ interface WebCameraInputWidgetSettings { // @dynamic @Component({ - selector: 'tb-web-camera-widget', - templateUrl: './web-camera-input.component.html', - styleUrls: ['./web-camera-input.component.scss'], + selector: 'tb-photo-camera-widget', + templateUrl: './photo-camera-input.component.html', + styleUrls: ['./photo-camera-input.component.scss'], encapsulation: ViewEncapsulation.None }) -export class WebCameraInputWidgetComponent extends PageComponent implements OnInit, OnDestroy { +export class PhotoCameraInputWidgetComponent extends PageComponent implements OnInit, OnDestroy { constructor(@Inject(WINDOW) private window: Window, protected store: Store, @@ -93,11 +93,11 @@ export class WebCameraInputWidgetComponent extends PageComponent implements OnIn @Input() ctx: WidgetContext; - @ViewChild('videoStream', {static: true}) videoStreamRef: ElementRef; - @ViewChild('canvas', {static: true}) canvasRef: ElementRef; + @ViewChild('videoStream', {static: false}) videoStreamRef: ElementRef; + @ViewChild('canvas', {static: false}) canvasRef: ElementRef; private videoInputsIndex = 0; - private settings: WebCameraInputWidgetSettings; + private settings: PhotoCameraInputWidgetSettings; private datasource: Datasource; private width = 640; private height = 480; @@ -106,10 +106,13 @@ export class WebCameraInputWidgetComponent extends PageComponent implements OnIn isEntityDetected = false; dataKeyDetected = false; + isProtocolHttps = false; isCameraSupport = false; isDeviceDetect = false; isShowCamera = false; isPreviewPhoto = false; + isHavePermissionCamera = true; + isLoading = false; singleDevice = true; updatePhoto = false; previewPhoto: SafeUrl; @@ -132,7 +135,8 @@ export class WebCameraInputWidgetComponent extends PageComponent implements OnIn } ngOnInit(): void { - this.ctx.$scope.webCameraInputWidget = this; + this.ctx.$scope.photoCameraInputWidget = this; + this.isLoading = true; this.settings = this.ctx.settings; this.datasource = this.ctx.datasources[0]; @@ -168,25 +172,33 @@ export class WebCameraInputWidgetComponent extends PageComponent implements OnIn } public onDataUpdated() { - this.ngZone.run(() => { - this.updateWidgetData(this.ctx.defaultSubscription.data); - this.ctx.detectChanges(); - }); + this.updateWidgetData(this.ctx.defaultSubscription.data); + this.ctx.detectChanges(); } private detectAvailableDevices(): void { - if (WebCameraInputWidgetComponent.hasGetUserMedia()) { - this.isCameraSupport = true; - WebCameraInputWidgetComponent.getAvailableVideoInputs().then((devices) => { - this.isDeviceDetect = !!devices.length; - this.singleDevice = devices.length < 2; - this.availableVideoInputs = devices; - this.ctx.detectChanges(); - }, () => { - this.availableVideoInputs = []; - } - ) + if (this.window.location.protocol === 'https:' || this.window.location.hostname === 'localhost') { + this.isProtocolHttps = true; + + if (PhotoCameraInputWidgetComponent.hasGetUserMedia()) { + this.isCameraSupport = true; + PhotoCameraInputWidgetComponent.getAvailableVideoInputs().then((devices) => { + this.isLoading = false; + this.isDeviceDetect = !!devices.length; + this.singleDevice = devices.length < 2; + this.availableVideoInputs = devices; + this.ctx.detectChanges(); + }, () => { + this.isLoading = false; + this.availableVideoInputs = []; + } + ); + } else { + this.isLoading = false; + } + } else { + this.isLoading = false; } } @@ -206,8 +218,7 @@ export class WebCameraInputWidgetComponent extends PageComponent implements OnIn } takePhoto() { - this.isShowCamera = true; - this.initWebCamera(this.availableVideoInputs[this.videoInputsIndex].deviceId); + this.inititedVideoStream(this.availableVideoInputs[this.videoInputsIndex].deviceId, true); } closeCamera() { @@ -243,13 +254,13 @@ export class WebCameraInputWidgetComponent extends PageComponent implements OnIn this.closeCamera(); }, () => { this.updatePhoto = false; - }) + }); } switchWebCamera() { this.videoInputsIndex = (this.videoInputsIndex + 1) % this.availableVideoInputs.length; this.stopMediaTracks(); - this.initWebCamera(this.availableVideoInputs[this.videoInputsIndex].deviceId) + this.inititedVideoStream(this.availableVideoInputs[this.videoInputsIndex].deviceId); } createPhoto() { @@ -257,22 +268,53 @@ export class WebCameraInputWidgetComponent extends PageComponent implements OnIn this.canvasElement.height = this.videoHeight; this.canvasElement.getContext('2d').drawImage(this.videoElement, 0, 0, this.videoWidth, this.videoHeight); - const mimeType: string = this.settings.imageFormat ? this.settings.imageFormat : WebCameraInputWidgetComponent.DEFAULT_IMAGE_TYPE; - const quality: number = this.settings.imageQuality ? this.settings.imageQuality : WebCameraInputWidgetComponent.DEFAULT_IMAGE_QUALITY; - this.previewPhoto = this.sanitizer.bypassSecurityTrustUrl(this.canvasElement.toDataURL(mimeType, quality)); + const mimeType: string = this.settings.imageFormat ? this.settings.imageFormat : PhotoCameraInputWidgetComponent.DEFAULT_IMAGE_TYPE; + const quality: number = this.settings.imageQuality ? this.settings.imageQuality : PhotoCameraInputWidgetComponent.DEFAULT_IMAGE_QUALITY; + this.previewPhoto = this.canvasElement.toDataURL(mimeType, quality); this.isPreviewPhoto = true; } - private initWebCamera(deviceId?: string) { + private inititedVideoStream(deviceId?: string, init = false) { if (window.navigator.mediaDevices && window.navigator.mediaDevices.getUserMedia) { const videoTrackConstraints = { video: {deviceId: deviceId !== '' ? {exact: deviceId} : undefined} }; window.navigator.mediaDevices.getUserMedia(videoTrackConstraints).then((stream: MediaStream) => { + if (init) { + this.isShowCamera = true; + } this.mediaStream = stream; this.videoElement.srcObject = stream; - }) + this.ctx.detectChanges(); + }, () => { + this.isHavePermissionCamera = false; + }); + } + } + + get textMessage() { + if (this.isLoading) { + return ''; + } + if (!this.isProtocolHttps) { + return 'widgets.input-widgets.enable-https-use-widget'; + } + if (!this.isCameraSupport) { + return 'widgets.input-widgets.no-support-web-camera'; + } + if (!this.isEntityDetected) { + return 'widgets.input-widgets.no-entity-selected'; + } + if (!this.dataKeyDetected) { + return 'widgets.input-widgets.no-datakey-selected'; + } + if (!this.isDeviceDetect) { + return 'widgets.input-widgets.no-found-your-camera'; + } + if (!this.isHavePermissionCamera) { + return 'widgets.input-widgets.no-permission-camera'; } + return null; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index a97096a953..c2a158fcdf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts @@ -32,7 +32,7 @@ import { } from '@home/components/widget/lib/date-range-navigator/date-range-navigator.component'; import { MultipleInputWidgetComponent } from './lib/multiple-input-widget.component'; import { TripAnimationComponent } from './trip-animation/trip-animation.component'; -import { WebCameraInputWidgetComponent } from './lib/web-camera-input.component'; +import { PhotoCameraInputWidgetComponent } from './lib/photo-camera-input.component'; import { GatewayFormComponent } from './lib/gateway/gateway-form.component'; import { ImportExportService } from '@home/components/import-export/import-export.service'; @@ -49,7 +49,7 @@ import { ImportExportService } from '@home/components/import-export/import-expor DateRangeNavigatorPanelComponent, MultipleInputWidgetComponent, TripAnimationComponent, - WebCameraInputWidgetComponent, + PhotoCameraInputWidgetComponent, GatewayFormComponent ], imports: [ @@ -67,7 +67,7 @@ import { ImportExportService } from '@home/components/import-export/import-expor DateRangeNavigatorWidgetComponent, MultipleInputWidgetComponent, TripAnimationComponent, - WebCameraInputWidgetComponent, + PhotoCameraInputWidgetComponent, GatewayFormComponent ], providers: [ diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 453d5d2abd..20a01cc99a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2283,7 +2283,10 @@ "no-entity-selected": "No entity selected", "no-image": "No image", "no-support-geolocation": "Your browser doesn't support geolocation", - "no-support-web-camera": "No supported web camera", + "no-support-web-camera": "Your browser does not support cameras", + "enable-https-use-widget": "Please enable HTTPS to use this widget", + "no-found-your-camera": "Can't find your camera", + "no-permission-camera": "Permission was denied by the user / This site doesn't have permission to use the camera", "no-timeseries-selected": "No timeseries selected", "secret-key": "Secret key", "secret-key-required": "Secret key is required", From c40f6bf47c58cfc26d6d04729093da50e0a3b054 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 23 Oct 2020 18:42:07 +0300 Subject: [PATCH 248/463] UI: Added new options for legend config (sort datakeys in legend) --- ui-ngx/src/app/core/api/widget-subscription.ts | 3 --- .../components/widget/legend-config-panel.component.html | 3 +++ .../components/widget/legend-config-panel.component.ts | 1 + .../app/modules/home/components/widget/legend.component.ts | 7 +++++-- ui-ngx/src/app/shared/models/widget.models.ts | 2 ++ ui-ngx/src/assets/locale/locale.constant-en_US.json | 1 + 6 files changed, 12 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index f7017652d8..123fe010c8 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -1226,9 +1226,6 @@ export class WidgetSubscription implements IWidgetSubscription { }); }); } - if (this.displayLegend) { - this.legendData.keys = this.legendData.keys.sort((key1, key2) => key1.dataKey.label.localeCompare(key2.dataKey.label)); - } if (this.caulculateLegendData) { this.data.forEach((dataSetHolder, keyIndex) => { this.updateLegend(keyIndex, dataSetHolder.data, false); diff --git a/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.html index 15e5466e80..28cabf7cd8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.html @@ -38,6 +38,9 @@ + + {{ 'legend.sort-legend' | translate }} + {{ 'legend.show-min' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.ts index 5f5abff7a3..05e515100e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.ts @@ -68,6 +68,7 @@ export class LegendConfigPanelComponent extends PageComponent implements OnInit this.legendConfigForm = this.fb.group({ direction: [this.data.legendConfig.direction, []], position: [this.data.legendConfig.position, []], + sortDataKeys: [this.data.legendConfig.sortDataKeys, []], showMin: [this.data.legendConfig.showMin, []], showMax: [this.data.legendConfig.showMax, []], showAvg: [this.data.legendConfig.showAvg, []], diff --git a/ui-ngx/src/app/modules/home/components/widget/legend.component.ts b/ui-ngx/src/app/modules/home/components/widget/legend.component.ts index 55fffa2b5b..3691742406 100644 --- a/ui-ngx/src/app/modules/home/components/widget/legend.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/legend.component.ts @@ -60,8 +60,11 @@ export class LegendComponent implements OnInit { } legendKeys(): LegendKey[] { - return this.legendData.keys - .filter(legendKey => this.legendData.keys[legendKey.dataIndex].dataKey.inLegend); + let keys = this.legendData.keys; + if (this.legendConfig.sortDataKeys) { + keys = this.legendData.keys.sort((key1, key2) => key1.dataKey.label.localeCompare(key2.dataKey.label)); + } + return keys.filter(legendKey => this.legendData.keys[legendKey.dataIndex].dataKey.inLegend); } } diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 8ea338a98f..a97a66a1ef 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -203,6 +203,7 @@ export const legendPositionTranslationMap = new Map( export interface LegendConfig { position: LegendPosition; direction?: LegendDirection; + sortDataKeys: boolean; showMin: boolean; showMax: boolean; showAvg: boolean; @@ -213,6 +214,7 @@ export function defaultLegendConfig(wType: widgetType): LegendConfig { return { direction: LegendDirection.column, position: LegendPosition.bottom, + sortDataKeys: false, showMin: false, showMax: false, showAvg: wType === widgetType.timeseries, diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 453d5d2abd..11cf7dcbc0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1664,6 +1664,7 @@ "legend": { "direction": "Legend direction", "position": "Legend position", + "sort-legend": "Sort datakeys in legend", "show-max": "Show max value", "show-min": "Show min value", "show-avg": "Show average value", From fec424f5c536f9e6d3ff015a0685e131d1b278cc Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Sat, 24 Oct 2020 20:56:34 +0300 Subject: [PATCH 249/463] UI: Fixed problem for json-form component selector --- ui-ngx/package.json | 6 +-- ui-ngx/yarn.lock | 124 +++++++++++++++++++++++++++----------------- 2 files changed, 80 insertions(+), 50 deletions(-) diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 4612631ff6..8e3ede2934 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -76,8 +76,8 @@ "prettier": "^2.1.2", "prop-types": "^15.7.2", "raphael": "^2.3.0", - "rc-select": "^11.3.3", - "react": "^16.13.1", + "rc-select": "~10.5.1", + "react": "~16.14.0", "react-ace": "^9.1.4", "react-dom": "^16.13.1", "react-dropzone": "^11.2.0", @@ -92,7 +92,7 @@ "tslib": "^2.0.2", "tv4": "^1.3.0", "typeface-roboto": "^1.1.13", - "zone.js": "~0.11.1" + "zone.js": "~0.10.3" }, "devDependencies": { "@angular-builders/custom-webpack": "^10.0.1", diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index 58d275db1b..dcdc0909c0 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -272,6 +272,11 @@ dependencies: tslib "^2.0.0" +"@ant-design/css-animation@^1.7.2": + version "1.7.3" + resolved "https://registry.yarnpkg.com/@ant-design/css-animation/-/css-animation-1.7.3.tgz#60a1c970014e86b28f940510d69e503e428f1136" + integrity sha512-LrX0OGZtW+W6iLnTAqnTaoIsRelYeuLZWsrmBJFUXDALQphPsN8cE5DCsmoSlL0QYb94BQxINiuS70Ar/8BNgA== + "@auth0/angular-jwt@^5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@auth0/angular-jwt/-/angular-jwt-5.0.1.tgz#37851d3ca2a0e88b3e673afd7dd2891f0c61bdf5" @@ -1787,6 +1792,13 @@ acorn@^6.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== +add-dom-event-listener@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz#6a92db3a0dd0abc254e095c0f1dc14acbbaae310" + integrity sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw== + dependencies: + object-assign "4.x" + adjust-sourcemap-loader@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/adjust-sourcemap-loader/-/adjust-sourcemap-loader-2.0.0.tgz#6471143af75ec02334b219f54bc7970c52fb29a4" @@ -6752,7 +6764,7 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@4.x, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -7645,7 +7657,7 @@ promise-retry@^1.1.1: err-code "^1.0.0" retry "^0.10.0" -prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.10, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -7810,6 +7822,13 @@ querystringify@^2.1.1: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== +raf@^3.4.0, raf@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== + dependencies: + performance-now "^2.1.0" + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -7866,50 +7885,63 @@ rc-align@^4.0.0: rc-util "^5.3.0" resize-observer-polyfill "^1.5.1" -rc-motion@^2.0.0, rc-motion@^2.0.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-2.3.1.tgz#a0c9f402c267bd03543ef80a970297a6ba77c503" - integrity sha512-UAB2gwS9c1DuCFKVCimAkL2JUEGCwzgCbb+VslhIMFg6vY7oyMxYIQ6hkoji1PzgDEw0tHIhP+a17R6Y5DGMrQ== +rc-animate@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/rc-animate/-/rc-animate-3.1.1.tgz#defdd863f56816c222534e4dc68feddecd081386" + integrity sha512-8wg2Zg3EETy0k/9kYuis30NJNQg1D6/WSQwnCiz6SvyxQXNet/rVraRz3bPngwY6rcU2nlRvoShiYOorXyF7Sg== dependencies: - "@babel/runtime" "^7.11.1" - classnames "^2.2.1" - rc-util "^5.2.1" + "@ant-design/css-animation" "^1.7.2" + classnames "^2.2.6" + raf "^3.4.0" + rc-util "^4.15.3" -rc-resize-observer@^0.2.3: - version "0.2.5" - resolved "https://registry.yarnpkg.com/rc-resize-observer/-/rc-resize-observer-0.2.5.tgz#03e3a5c3dfccd6c996a547e4f82721e4f20f6156" - integrity sha512-cc4sOI722MVoCkGf/ZZybDVsjxvnH0giyDdA7wBJLTiMSFJ0eyxBMnr0JLYoClxftjnr75Xzl/VUB3HDrAx04Q== +rc-motion@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-1.1.2.tgz#07212f1b96c715b8245ec121339146c4a9b0968c" + integrity sha512-YC/E7SSWKBFakYg4PENhSRWD4ZLDqkI7FKmutJcrMewZ91/ZIWfoZSDvPaBdKO0hsFrrzWepFhXQIq0FNnCMWA== dependencies: - "@babel/runtime" "^7.10.1" + "@babel/runtime" "^7.11.1" classnames "^2.2.1" - rc-util "^5.0.0" - resize-observer-polyfill "^1.5.1" + raf "^3.4.1" + rc-util "^5.0.6" -rc-select@^11.3.3: - version "11.3.3" - resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-11.3.3.tgz#ba445ac4d2d933dd1f80b796c1de28ce6c81bbf8" - integrity sha512-YMsGVEZxXctj15nIZKlFCkiOxMe0PNBeACN6nHqDozDYKR/aqP8J3XZqZ5Gw/fcgS4bI50zPVMieJKlY8/6Wfw== +rc-select@~10.5.1: + version "10.5.1" + resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-10.5.1.tgz#4d4c5d4f8d2fd3b7e3dccf74e4c43142b2979247" + integrity sha512-fZraoNNhjUmDJccfk6VYgrgHBWFmHWh/NJZh2Xttcm/usOolYI1RjO9ikP4QGhzlJBFtnTLH2pE2nchjn3TCXA== dependencies: "@babel/runtime" "^7.10.1" classnames "2.x" - rc-motion "^2.0.1" - rc-trigger "^5.0.4" + rc-animate "^3.0.0" + rc-trigger "^4.3.0" rc-util "^5.0.1" - rc-virtual-list "^3.0.3" + rc-virtual-list "^1.1.2" warning "^4.0.3" -rc-trigger@^5.0.4: - version "5.0.6" - resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-5.0.6.tgz#7e84717525871a7923a671edb5a290e9e616525b" - integrity sha512-L/xIX5OO7a/bdTH0yYT9mwAsV6oM1inAqAbLjoUzpcIW+UUE3jjVOjm5VaKDfHd41rzDzOfN05URmhet5QzXKQ== +rc-trigger@^4.3.0: + version "4.4.3" + resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-4.4.3.tgz#ed449cd6cce446555bc57f4394229c5c7154f4b0" + integrity sha512-yq/WyuiPwxd2q6jy+VPyy0GUCRFJ2eFqAaCwPE27AOftXeIupOcJ/2t1wakSq63cfk7qtzev5DKHUAjb8LOJCw== dependencies: "@babel/runtime" "^7.11.2" classnames "^2.2.6" + raf "^3.4.1" rc-align "^4.0.0" - rc-motion "^2.0.0" - rc-util "^5.3.4" + rc-motion "^1.0.0" + rc-util "^5.0.1" -rc-util@^5.0.0, rc-util@^5.0.1, rc-util@^5.0.7, rc-util@^5.2.1, rc-util@^5.3.0, rc-util@^5.3.4: +rc-util@^4.15.3: + version "4.21.1" + resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-4.21.1.tgz#88602d0c3185020aa1053d9a1e70eac161becb05" + integrity sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg== + dependencies: + add-dom-event-listener "^1.1.0" + prop-types "^15.5.10" + react-is "^16.12.0" + react-lifecycles-compat "^3.0.4" + shallowequal "^1.1.0" + +rc-util@^5.0.0, rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.3.0: version "5.4.0" resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.4.0.tgz#688eaeecfdae9dae2bfdf10bedbe884591dba004" integrity sha512-kXDn1JyLJTAWLBFt+fjkTcUtXhxKkipQCobQmxIEVrX62iXgo24z8YKoWehWfMxPZFPE+RXqrmEu9j5kHz/Lrg== @@ -7917,14 +7949,14 @@ rc-util@^5.0.0, rc-util@^5.0.1, rc-util@^5.0.7, rc-util@^5.2.1, rc-util@^5.3.0, react-is "^16.12.0" shallowequal "^1.1.0" -rc-virtual-list@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.1.0.tgz#ca7ddbb291dace89c00cc4198ca7ef6e5e2034f7" - integrity sha512-DYU3wOjVuQo4hzYvmmpnoNtxrd8OIcutazA90x374ZFGGm4xYoSjCdh6UhBLi47IJI2BRry4l583nuoi7+06GA== +rc-virtual-list@^1.1.2: + version "1.1.6" + resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-1.1.6.tgz#b255baf9aacde149a8893324e6307214094f4c0a" + integrity sha512-u3+izqWL8p8bQy8nYH48qWpiGyxR/ye8D2k0zJlXmfYeL55/xh83YrzHqiDzO78uj0Ewag3nXDA0JTVrYO7ygQ== dependencies: classnames "^2.2.6" - rc-resize-observer "^0.2.3" - rc-util "^5.0.7" + raf "^3.4.1" + rc-util "^5.0.0" react-ace@^9.1.4: version "9.1.4" @@ -7961,6 +7993,11 @@ react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + react-transition-group@^4.0.0, react-transition-group@^4.4.0: version "4.4.1" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" @@ -7971,10 +8008,10 @@ react-transition-group@^4.0.0, react-transition-group@^4.4.0: loose-envify "^1.4.0" prop-types "^15.6.2" -react@^16.13.1: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" - integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== +react@~16.14.0: + version "16.14.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" + integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" @@ -10206,10 +10243,3 @@ zone.js@~0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.10.3.tgz#3e5e4da03c607c9dcd92e37dd35687a14a140c16" integrity sha512-LXVLVEq0NNOqK/fLJo3d0kfzd4sxwn2/h67/02pjCjfKDxgx1i9QqpvtHD8CrBnSSwMw5+dy11O7FRX5mkO7Cg== - -zone.js@~0.11.1: - version "0.11.1" - resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.11.1.tgz#0301d00d26febb2722f074c46aac4a948698ce39" - integrity sha512-KcZawpmVgS+3U2rzKTM6fLKaCX1QDv3//NxiSOOsqpQY/r5hl+xpYikPwY93Sp7CAB+J5mZJpb/YubxEYLGK5g== - dependencies: - tslib "^2.0.0" From d6c2eefe05d6cc8427e2fda8b2c9d0b50e374442 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 23 Oct 2020 15:28:59 +0300 Subject: [PATCH 250/463] base url improvements --- .../server/controller/AuthController.java | 7 +++--- .../server/controller/UserController.java | 10 +++++--- .../Oauth2AuthenticationFailureHandler.java | 8 ++++++- .../Oauth2AuthenticationSuccessHandler.java | 11 ++++++--- .../system/DefaultSystemSecurityService.java | 24 ++++++++++++++++--- .../system/SystemSecurityService.java | 5 ++++ .../controller/AbstractControllerTest.java | 8 +++++++ ui/src/app/admin/general-settings.tpl.html | 5 ++++ ui/src/app/locale/locale.constant-en_US.json | 1 + 9 files changed, 66 insertions(+), 13 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java index 9f6c6f3b44..d57346cf48 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java @@ -171,7 +171,8 @@ public class AuthController extends BaseController { try { String email = resetPasswordByEmailRequest.get("email").asText(); UserCredentials userCredentials = userService.requestPasswordReset(TenantId.SYS_TENANT_ID, email); - String baseUrl = MiscUtils.constructBaseUrl(request); + User user = userService.findUserById(TenantId.SYS_TENANT_ID, userCredentials.getUserId()); + String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request); String resetUrl = String.format("%s/api/noauth/resetPassword?resetToken=%s", baseUrl, userCredentials.getResetToken()); @@ -219,7 +220,7 @@ public class AuthController extends BaseController { User user = userService.findUserById(TenantId.SYS_TENANT_ID, credentials.getUserId()); UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail()); SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled(), principal); - String baseUrl = MiscUtils.constructBaseUrl(request); + String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request); String loginUrl = String.format("%s/login", baseUrl); String email = user.getEmail(); @@ -266,7 +267,7 @@ public class AuthController extends BaseController { User user = userService.findUserById(TenantId.SYS_TENANT_ID, userCredentials.getUserId()); UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail()); SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), principal); - String baseUrl = MiscUtils.constructBaseUrl(request); + String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request); String loginUrl = String.format("%s/login", baseUrl); String email = user.getEmail(); mailService.sendPasswordWasResetEmail(loginUrl, email); diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index b3d66adc53..d1868b2916 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -52,6 +52,7 @@ import org.thingsboard.server.service.security.model.token.JwtToken; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; +import org.thingsboard.server.service.security.system.SystemSecurityService; import org.thingsboard.server.utils.MiscUtils; import javax.servlet.http.HttpServletRequest; @@ -78,6 +79,9 @@ public class UserController extends BaseController { @Autowired private RefreshTokenRepository refreshTokenRepository; + @Autowired + private SystemSecurityService systemSecurityService; + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET) @@ -145,7 +149,7 @@ public class UserController extends BaseController { if (sendEmail) { SecurityUser authUser = getCurrentUser(); UserCredentials userCredentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), savedUser.getId()); - String baseUrl = MiscUtils.constructBaseUrl(request); + String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl, userCredentials.getActivateToken()); String email = savedUser.getEmail(); @@ -185,7 +189,7 @@ public class UserController extends BaseController { UserCredentials userCredentials = userService.findUserCredentialsByUserId(getCurrentUser().getTenantId(), user.getId()); if (!userCredentials.isEnabled()) { - String baseUrl = MiscUtils.constructBaseUrl(request); + String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl, userCredentials.getActivateToken()); mailService.sendActivationEmail(activateUrl, email); @@ -210,7 +214,7 @@ public class UserController extends BaseController { SecurityUser authUser = getCurrentUser(); UserCredentials userCredentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), user.getId()); if (!userCredentials.isEnabled()) { - String baseUrl = MiscUtils.constructBaseUrl(request); + String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl, userCredentials.getActivateToken()); return activateUrl; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java index 653be85f72..10fe5fe3fd 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java @@ -19,6 +19,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.service.security.system.SystemSecurityService; import org.thingsboard.server.utils.MiscUtils; import javax.servlet.ServletException; @@ -32,11 +36,13 @@ import java.nio.charset.StandardCharsets; @ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true") public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { + private SystemSecurityService systemSecurityService; + @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { - String baseUrl = MiscUtils.constructBaseUrl(request); + String baseUrl = systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" + URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8.toString())); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index 3375d046e2..ece8708fd3 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -21,13 +21,16 @@ import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.oauth2.OAuth2Client; import org.thingsboard.server.dao.oauth2.OAuth2Configuration; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.token.JwtToken; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; -import org.thingsboard.server.utils.MiscUtils; +import org.thingsboard.server.service.security.system.SystemSecurityService; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -43,16 +46,18 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS private final RefreshTokenRepository refreshTokenRepository; private final OAuth2ClientMapperProvider oauth2ClientMapperProvider; private final OAuth2Configuration oauth2Configuration; + private final SystemSecurityService systemSecurityService; @Autowired public Oauth2AuthenticationSuccessHandler(final JwtTokenFactory tokenFactory, final RefreshTokenRepository refreshTokenRepository, final OAuth2ClientMapperProvider oauth2ClientMapperProvider, - final OAuth2Configuration oauth2Configuration) { + final OAuth2Configuration oauth2Configuration, SystemSecurityService systemSecurityService) { this.tokenFactory = tokenFactory; this.refreshTokenRepository = refreshTokenRepository; this.oauth2ClientMapperProvider = oauth2ClientMapperProvider; this.oauth2Configuration = oauth2Configuration; + this.systemSecurityService = systemSecurityService; } @Override @@ -60,7 +65,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS HttpServletResponse response, Authentication authentication) throws IOException { - String baseUrl = MiscUtils.constructBaseUrl(request); + String baseUrl = systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); try { OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java index 11857f25b6..4219dbc609 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java @@ -40,17 +40,20 @@ import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.UserCredentials; +import org.thingsboard.server.common.data.security.model.SecuritySettings; +import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserServiceImpl; import org.thingsboard.server.service.security.exception.UserPasswordExpiredException; -import org.thingsboard.server.common.data.security.model.SecuritySettings; -import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; +import org.thingsboard.server.utils.MiscUtils; import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -146,7 +149,7 @@ public class DefaultSystemSecurityService implements SystemSecurityService { if (isPositiveInteger(securitySettings.getPasswordPolicy().getPasswordExpirationPeriodDays())) { if ((userCredentials.getCreatedTime() + TimeUnit.DAYS.toMillis(securitySettings.getPasswordPolicy().getPasswordExpirationPeriodDays())) - < System.currentTimeMillis()) { + < System.currentTimeMillis()) { userCredentials = userService.requestExpiredPasswordReset(tenantId, userCredentials.getId()); throw new UserPasswordExpiredException("User password expired!", userCredentials.getResetToken()); } @@ -197,6 +200,21 @@ public class DefaultSystemSecurityService implements SystemSecurityService { } } + @Override + public String getBaseUrl(TenantId tenantId, CustomerId customerId, HttpServletRequest httpServletRequest) { + String baseUrl; + AdminSettings generalSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "general"); + + JsonNode prohibitDifferentUrl = generalSettings.getJsonValue().get("prohibitDifferentUrl"); + + if (prohibitDifferentUrl != null && prohibitDifferentUrl.asBoolean()) { + baseUrl = generalSettings.getJsonValue().get("baseUrl").asText(); + } else { + baseUrl = MiscUtils.constructBaseUrl(httpServletRequest); + } + return baseUrl; + } + private static boolean isPositiveInteger(Integer val) { return val != null && val.intValue() > 0; } diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java index afb7834cef..01367d53f1 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java @@ -16,11 +16,14 @@ package org.thingsboard.server.service.security.system; import org.springframework.security.core.AuthenticationException; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.common.data.security.model.SecuritySettings; +import javax.servlet.http.HttpServletRequest; + public interface SystemSecurityService { SecuritySettings getSecuritySettings(TenantId tenantId); @@ -31,4 +34,6 @@ public interface SystemSecurityService { void validatePassword(TenantId tenantId, String password, UserCredentials userCredentials) throws DataValidationException; + String getBaseUrl(TenantId tenantId, CustomerId customerId, HttpServletRequest httpServletRequest); + } diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java index ca1991ed12..c0d79204d0 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java @@ -18,6 +18,7 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Header; import io.jsonwebtoken.Jwt; @@ -58,6 +59,7 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilde import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.context.WebApplicationContext; +import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Tenant; @@ -68,6 +70,7 @@ import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.config.ThingsboardSecurityConfiguration; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.service.mail.TestMailService; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest; import org.thingsboard.server.service.security.auth.rest.LoginRequest; @@ -174,6 +177,11 @@ public abstract class AbstractControllerTest { } loginSysAdmin(); + ObjectNode generalSettings = JacksonUtil.OBJECT_MAPPER.createObjectNode(); + AdminSettings adminSettings = new AdminSettings(); + adminSettings.setKey("general"); + adminSettings.setJsonValue(generalSettings); + Tenant tenant = new Tenant(); tenant.setTitle(TEST_TENANT_NAME); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); diff --git a/ui/src/app/admin/general-settings.tpl.html b/ui/src/app/admin/general-settings.tpl.html index b6eea094bf..cc50e990d4 100644 --- a/ui/src/app/admin/general-settings.tpl.html +++ b/ui/src/app/admin/general-settings.tpl.html @@ -34,6 +34,11 @@
admin.base-url-required
+ + {{ 'admin.prohibit-different-url' | translate }} +
{{'action.save' | translate}}
diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 2b21ec44be..f3ec840d7d 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -74,6 +74,7 @@ "test-mail-sent": "Test mail was successfully sent!", "base-url": "Base URL", "base-url-required": "Base URL is required.", + "prohibit-different-url": "Prohibit different URL", "mail-from": "Mail From", "mail-from-required": "Mail From is required.", "smtp-protocol": "SMTP protocol", From b9cc400a15779cf8c9621ea1f52125c2534e99f6 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 26 Oct 2020 12:52:13 +0200 Subject: [PATCH 251/463] base url improvements --- .../server/service/install/DefaultSystemDataLoaderService.java | 1 + ui/src/app/admin/general-settings.tpl.html | 3 +++ ui/src/app/locale/locale.constant-en_US.json | 3 ++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 95eb966a1a..d0ec2367df 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -116,6 +116,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { generalSettings.setKey("general"); ObjectNode node = objectMapper.createObjectNode(); node.put("baseUrl", "http://localhost:8080"); + node.put("prohibitDifferentUrl", true); generalSettings.setJsonValue(node); adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, generalSettings); diff --git a/ui/src/app/admin/general-settings.tpl.html b/ui/src/app/admin/general-settings.tpl.html index cc50e990d4..8a97f2300a 100644 --- a/ui/src/app/admin/general-settings.tpl.html +++ b/ui/src/app/admin/general-settings.tpl.html @@ -39,6 +39,9 @@ ng-model="vm.settings.jsonValue.prohibitDifferentUrl"> {{ 'admin.prohibit-different-url' | translate }} +
+ admin.prohibit-different-url-hint +
{{'action.save' | translate}}
diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index f3ec840d7d..046fa4df66 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -74,7 +74,8 @@ "test-mail-sent": "Test mail was successfully sent!", "base-url": "Base URL", "base-url-required": "Base URL is required.", - "prohibit-different-url": "Prohibit different URL", + "prohibit-different-url": "Prohibit to use hostname from the client request headers", + "prohibit-different-url-hint": "This setting should be enabled for production environments. May cause security issues when disabled", "mail-from": "Mail From", "mail-from-required": "Mail From is required.", "smtp-protocol": "SMTP protocol", From 174570610bd2e26339eaaacfb48e49467dba1484 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 23 Oct 2020 17:16:08 +0300 Subject: [PATCH 252/463] added circles validation in BaseRuleChainService --- .../server/dao/rule/BaseRuleChainService.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index fdd99f97f7..a57ce62957 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.rule; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -38,6 +39,7 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; @@ -123,6 +125,10 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC return null; } + if (CollectionUtils.isNotEmpty(ruleChainMetaData.getConnections())) { + validateCircles(ruleChainMetaData.getConnections()); + } + List nodes = ruleChainMetaData.getNodes(); List toAddOrUpdate = new ArrayList<>(); List toDelete = new ArrayList<>(); @@ -205,6 +211,28 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC return loadRuleChainMetaData(tenantId, ruleChainMetaData.getRuleChainId()); } + private void validateCircles(List connectionInfos) { + Map> connectionsMap = new HashMap<>(); + for (NodeConnectionInfo nodeConnection : connectionInfos) { + connectionsMap + .computeIfAbsent(nodeConnection.getFromIndex(), from -> new ArrayList<>()) + .add(nodeConnection.getToIndex()); + } + connectionsMap.keySet().forEach(key -> validateCircles(key, connectionsMap.get(key), connectionsMap)); + } + + private void validateCircles(int from, List toList, Map> connectionsMap) { + if (toList == null) { + return; + } + for (Integer to : toList) { + if (from == to) { + throw new IncorrectParameterException("Can't create circling relations in rule chain."); + } + validateCircles(from, connectionsMap.get(to), connectionsMap); + } + } + @Override public RuleChainMetaData loadRuleChainMetaData(TenantId tenantId, RuleChainId ruleChainId) { Validator.validateId(ruleChainId, "Incorrect rule chain id."); From 44a700e89a6cf783df6e4e6bf681ea33db072497 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 26 Oct 2020 12:24:48 +0200 Subject: [PATCH 253/463] rule chain circles validation improvements --- .../server/dao/rule/BaseRuleChainService.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index a57ce62957..ab05febc3a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -47,8 +47,10 @@ import org.thingsboard.server.dao.tenant.TenantDao; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; @@ -212,16 +214,19 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC } private void validateCircles(List connectionInfos) { - Map> connectionsMap = new HashMap<>(); + Map> connectionsMap = new HashMap<>(); for (NodeConnectionInfo nodeConnection : connectionInfos) { + if (nodeConnection.getFromIndex() == nodeConnection.getToIndex()) { + throw new IncorrectParameterException("Can't create the relation to yourself."); + } connectionsMap - .computeIfAbsent(nodeConnection.getFromIndex(), from -> new ArrayList<>()) + .computeIfAbsent(nodeConnection.getFromIndex(), from -> new HashSet<>()) .add(nodeConnection.getToIndex()); } connectionsMap.keySet().forEach(key -> validateCircles(key, connectionsMap.get(key), connectionsMap)); } - private void validateCircles(int from, List toList, Map> connectionsMap) { + private void validateCircles(int from, Set toList, Map> connectionsMap) { if (toList == null) { return; } From 8bd617e3b583ae4db532055fef01da83f047b2d1 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 27 Oct 2020 17:43:12 +0200 Subject: [PATCH 254/463] created tests for rule chain metadata validator --- .../server/dao/rule/BaseRuleChainService.java | 6 +- .../dao/service/BaseRuleChainServiceTest.java | 90 +++++++++++++++++++ 2 files changed, 92 insertions(+), 4 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index ab05febc3a..a99ad20ea6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -39,7 +39,6 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; @@ -52,7 +51,6 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; /** * Created by igor on 3/12/18. @@ -217,7 +215,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC Map> connectionsMap = new HashMap<>(); for (NodeConnectionInfo nodeConnection : connectionInfos) { if (nodeConnection.getFromIndex() == nodeConnection.getToIndex()) { - throw new IncorrectParameterException("Can't create the relation to yourself."); + throw new DataValidationException("Can't create the relation to yourself."); } connectionsMap .computeIfAbsent(nodeConnection.getFromIndex(), from -> new HashSet<>()) @@ -232,7 +230,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC } for (Integer to : toList) { if (from == to) { - throw new IncorrectParameterException("Can't create circling relations in rule chain."); + throw new DataValidationException("Can't create circling relations in rule chain."); } validateCircles(from, connectionsMap.get(to), connectionsMap); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java index e6c6eaa699..8c9e9c37e3 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java @@ -317,6 +317,16 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest { ruleChainService.deleteRuleChainById(tenantId, savedRuleChainMetaData.getRuleChainId()); } + @Test(expected = DataValidationException.class) + public void testUpdateRuleChainMetaDataWithCirclingRelation() throws Exception { + ruleChainService.saveRuleChainMetaData(tenantId, createRuleChainMetadataWithCirclingRelation()); + } + + @Test(expected = DataValidationException.class) + public void testUpdateRuleChainMetaDataWithCirclingRelation2() throws Exception { + ruleChainService.saveRuleChainMetaData(tenantId, createRuleChainMetadataWithCirclingRelation2()); + } + private RuleChainMetaData createRuleChainMetadata() throws Exception { RuleChain ruleChain = new RuleChain(); ruleChain.setName("My RuleChain"); @@ -357,5 +367,85 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest { return ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData); } + private RuleChainMetaData createRuleChainMetadataWithCirclingRelation() throws Exception { + RuleChain ruleChain = new RuleChain(); + ruleChain.setName("My RuleChain"); + ruleChain.setTenantId(tenantId); + RuleChain savedRuleChain = ruleChainService.saveRuleChain(ruleChain); + + RuleChainMetaData ruleChainMetaData = new RuleChainMetaData(); + ruleChainMetaData.setRuleChainId(savedRuleChain.getId()); + + ObjectMapper mapper = new ObjectMapper(); + + RuleNode ruleNode1 = new RuleNode(); + ruleNode1.setName("name1"); + ruleNode1.setType("type1"); + ruleNode1.setConfiguration(mapper.readTree("\"key1\": \"val1\"")); + + RuleNode ruleNode2 = new RuleNode(); + ruleNode2.setName("name2"); + ruleNode2.setType("type2"); + ruleNode2.setConfiguration(mapper.readTree("\"key2\": \"val2\"")); + + RuleNode ruleNode3 = new RuleNode(); + ruleNode3.setName("name3"); + ruleNode3.setType("type3"); + ruleNode3.setConfiguration(mapper.readTree("\"key3\": \"val3\"")); + + List ruleNodes = new ArrayList<>(); + ruleNodes.add(ruleNode1); + ruleNodes.add(ruleNode2); + ruleNodes.add(ruleNode3); + ruleChainMetaData.setFirstNodeIndex(0); + ruleChainMetaData.setNodes(ruleNodes); + + ruleChainMetaData.addConnectionInfo(0,1,"success"); + ruleChainMetaData.addConnectionInfo(0,2,"fail"); + ruleChainMetaData.addConnectionInfo(1,2,"success"); + ruleChainMetaData.addConnectionInfo(2,2,"success"); + + return ruleChainMetaData; + } + + private RuleChainMetaData createRuleChainMetadataWithCirclingRelation2() throws Exception { + RuleChain ruleChain = new RuleChain(); + ruleChain.setName("My RuleChain"); + ruleChain.setTenantId(tenantId); + RuleChain savedRuleChain = ruleChainService.saveRuleChain(ruleChain); + + RuleChainMetaData ruleChainMetaData = new RuleChainMetaData(); + ruleChainMetaData.setRuleChainId(savedRuleChain.getId()); + + ObjectMapper mapper = new ObjectMapper(); + + RuleNode ruleNode1 = new RuleNode(); + ruleNode1.setName("name1"); + ruleNode1.setType("type1"); + ruleNode1.setConfiguration(mapper.readTree("\"key1\": \"val1\"")); + + RuleNode ruleNode2 = new RuleNode(); + ruleNode2.setName("name2"); + ruleNode2.setType("type2"); + ruleNode2.setConfiguration(mapper.readTree("\"key2\": \"val2\"")); + + RuleNode ruleNode3 = new RuleNode(); + ruleNode3.setName("name3"); + ruleNode3.setType("type3"); + ruleNode3.setConfiguration(mapper.readTree("\"key3\": \"val3\"")); + + List ruleNodes = new ArrayList<>(); + ruleNodes.add(ruleNode1); + ruleNodes.add(ruleNode2); + ruleNodes.add(ruleNode3); + ruleChainMetaData.setFirstNodeIndex(0); + ruleChainMetaData.setNodes(ruleNodes); + + ruleChainMetaData.addConnectionInfo(0,1,"success"); + ruleChainMetaData.addConnectionInfo(0,2,"fail"); + ruleChainMetaData.addConnectionInfo(1,2,"success"); + ruleChainMetaData.addConnectionInfo(2,0,"success"); + return ruleChainMetaData; + } } From 702a09132901836b9edea9e238c5b2ed9af5a7a1 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 26 Oct 2020 12:18:03 +0200 Subject: [PATCH 255/463] fix/AlarmActionEvents --- .../actors/ruleChain/DefaultTbContext.java | 16 ++++++++-------- .../server/controller/AlarmController.java | 2 ++ .../thingsboard/rule/engine/api/TbContext.java | 2 +- .../rule/engine/action/TbAbstractAlarmNode.java | 15 ++++++++++----- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 30747e8f1b..8c53a6c02c 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -252,26 +252,26 @@ class DefaultTbContext implements TbContext { } public TbMsg customerCreatedMsg(Customer customer, RuleNodeId ruleNodeId) { - return entityCreatedMsg(customer, customer.getId(), ruleNodeId); + return entityActionMsg(customer, customer.getId(), ruleNodeId, DataConstants.ENTITY_CREATED); } public TbMsg deviceCreatedMsg(Device device, RuleNodeId ruleNodeId) { - return entityCreatedMsg(device, device.getId(), ruleNodeId); + return entityActionMsg(device, device.getId(), ruleNodeId, DataConstants.ENTITY_CREATED); } public TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId) { - return entityCreatedMsg(asset, asset.getId(), ruleNodeId); + return entityActionMsg(asset, asset.getId(), ruleNodeId, DataConstants.ENTITY_CREATED); } - public TbMsg alarmCreatedMsg(Alarm alarm, RuleNodeId ruleNodeId) { - return entityCreatedMsg(alarm, alarm.getId(), ruleNodeId); + public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action) { + return entityActionMsg(alarm, alarm.getId(), ruleNodeId, action); } - public TbMsg entityCreatedMsg(E entity, I id, RuleNodeId ruleNodeId) { + public TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, String action) { try { - return TbMsg.newMsg(DataConstants.ENTITY_CREATED, id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity))); + return TbMsg.newMsg(action, id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity))); } catch (JsonProcessingException | IllegalArgumentException e) { - throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " created msg: " + e); + throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " " + action + " msg: " + e); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 7553448c09..6f3350143b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -124,6 +124,7 @@ public class AlarmController extends BaseController { long ackTs = System.currentTimeMillis(); alarmService.ackAlarm(getCurrentUser().getTenantId(), alarmId, ackTs).get(); alarm.setAckTs(ackTs); + alarm.setStatus(alarm.getStatus().isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK); logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_ACK, null); } catch (Exception e) { throw handleException(e); @@ -141,6 +142,7 @@ public class AlarmController extends BaseController { long clearTs = System.currentTimeMillis(); alarmService.clearAlarm(getCurrentUser().getTenantId(), alarmId, null, clearTs).get(); alarm.setClearTs(clearTs); + alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK); logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_CLEAR, null); } catch (Exception e) { throw handleException(e); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 032b285693..006fb1351b 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -141,7 +141,7 @@ public interface TbContext { TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId); // TODO: Does this changes the message? - TbMsg alarmCreatedMsg(Alarm alarm, RuleNodeId ruleNodeId); + TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action); /* * diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java index b3344105bb..6ed5a05589 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java @@ -25,6 +25,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -61,13 +62,11 @@ public abstract class TbAbstractAlarmNode ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Created"), - throwable -> ctx.tellFailure(toAlarmMsg(ctx, alarmResult, msg), throwable)); + tellNext(ctx, msg, alarmResult, DataConstants.ENTITY_CREATED, "Created"); } else if (alarmResult.isUpdated) { - ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Updated"); + tellNext(ctx, msg, alarmResult, DataConstants.ENTITY_UPDATED, "Updated"); } else if (alarmResult.isCleared) { - ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Cleared"); + tellNext(ctx, msg, alarmResult, DataConstants.ALARM_CLEAR, "Cleared"); } else { ctx.tellSuccess(msg); } @@ -126,4 +125,10 @@ public abstract class TbAbstractAlarmNode ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), alarmResultMsgType), + throwable -> ctx.tellFailure(toAlarmMsg(ctx, alarmResult, msg), throwable)); + } } From b78d9ca18959f4b9719813545bdf594f6b50cd57 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 26 Oct 2020 14:21:33 +0200 Subject: [PATCH 256/463] fix AlarmNodeTests --- .../thingsboard/rule/engine/action/TbAlarmNodeTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java index 114967eb51..b54bd1d633 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java @@ -34,6 +34,7 @@ import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.DeviceId; @@ -249,6 +250,9 @@ public class TbAlarmNodeTest { node.onMsg(ctx, msg); + verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); + successCaptor.getValue().run(); + verify(ctx).tellNext(any(), eq("Updated")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); @@ -297,6 +301,8 @@ public class TbAlarmNodeTest { node.onMsg(ctx, msg); + verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); + successCaptor.getValue().run(); verify(ctx).tellNext(any(), eq("Cleared")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); @@ -345,6 +351,8 @@ public class TbAlarmNodeTest { node.onMsg(ctx, msg); + verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); + successCaptor.getValue().run(); verify(ctx).tellNext(any(), eq("Cleared")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); From 606ae2e5347c9646be2d29290b0455fbe348f8d7 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 26 Oct 2020 16:38:57 +0200 Subject: [PATCH 257/463] fix typo --- .../java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java index b54bd1d633..ba6a98afb1 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java @@ -252,7 +252,6 @@ public class TbAlarmNodeTest { verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); successCaptor.getValue().run(); - verify(ctx).tellNext(any(), eq("Updated")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); From 67e8bf8c2fe2600f3a7d730f0ef52e268b7b65d2 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 26 Oct 2020 12:18:03 +0200 Subject: [PATCH 258/463] push ENTITY_UPDATED events on ALARM updates,clears --- .../actors/ruleChain/DefaultTbContext.java | 16 ++++++++-------- .../thingsboard/rule/engine/api/TbContext.java | 2 +- .../rule/engine/action/TbAbstractAlarmNode.java | 14 +++++++++----- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index c8bb083577..3ab4ac8b9c 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -257,26 +257,26 @@ class DefaultTbContext implements TbContext { } public TbMsg customerCreatedMsg(Customer customer, RuleNodeId ruleNodeId) { - return entityCreatedMsg(customer, customer.getId(), ruleNodeId); + return entityActionMsg(customer, customer.getId(), ruleNodeId, DataConstants.ENTITY_CREATED); } public TbMsg deviceCreatedMsg(Device device, RuleNodeId ruleNodeId) { - return entityCreatedMsg(device, device.getId(), ruleNodeId); + return entityActionMsg(device, device.getId(), ruleNodeId, DataConstants.ENTITY_CREATED); } public TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId) { - return entityCreatedMsg(asset, asset.getId(), ruleNodeId); + return entityActionMsg(asset, asset.getId(), ruleNodeId, DataConstants.ENTITY_CREATED); } - public TbMsg alarmCreatedMsg(Alarm alarm, RuleNodeId ruleNodeId) { - return entityCreatedMsg(alarm, alarm.getId(), ruleNodeId); + public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action) { + return entityActionMsg(alarm, alarm.getId(), ruleNodeId, action); } - public TbMsg entityCreatedMsg(E entity, I id, RuleNodeId ruleNodeId) { + public TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, String action) { try { - return TbMsg.newMsg(DataConstants.ENTITY_CREATED, id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity))); + return TbMsg.newMsg(action, id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity))); } catch (JsonProcessingException | IllegalArgumentException e) { - throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " created msg: " + e); + throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " " + action + " msg: " + e); } } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 3d45fcf445..f48151f498 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -144,7 +144,7 @@ public interface TbContext { TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId); // TODO: Does this changes the message? - TbMsg alarmCreatedMsg(Alarm alarm, RuleNodeId ruleNodeId); + TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action); /* * diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java index 103da4706a..bf0ac8f859 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java @@ -58,13 +58,11 @@ public abstract class TbAbstractAlarmNode ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Created"), - throwable -> ctx.tellFailure(toAlarmMsg(ctx, alarmResult, msg), throwable)); + tellNext(ctx, msg, alarmResult, DataConstants.ENTITY_CREATED, "Created"); } else if (alarmResult.isUpdated) { - ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Updated"); + tellNext(ctx, msg, alarmResult, DataConstants.ENTITY_UPDATED, "Updated"); } else if (alarmResult.isCleared) { - ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Cleared"); + tellNext(ctx, msg, alarmResult, DataConstants.ENTITY_UPDATED, "Cleared"); } else { ctx.tellSuccess(msg); } @@ -109,4 +107,10 @@ public abstract class TbAbstractAlarmNode ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), alarmAction), + throwable -> ctx.tellFailure(toAlarmMsg(ctx, alarmResult, msg), throwable)); + } + } From 41796a4d891093bbcc90955d573c15229d1c20ea Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 26 Oct 2020 14:21:33 +0200 Subject: [PATCH 259/463] fix AlarmNodeTests --- .../thingsboard/rule/engine/action/TbAlarmNodeTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java index 721d053b9c..908e263400 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java @@ -35,6 +35,7 @@ import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.DeviceId; @@ -249,6 +250,9 @@ public class TbAlarmNodeTest { node.onMsg(ctx, msg); + verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); + successCaptor.getValue().run(); + verify(ctx).tellNext(any(), eq("Updated")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); @@ -298,6 +302,8 @@ public class TbAlarmNodeTest { node.onMsg(ctx, msg); + verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); + successCaptor.getValue().run(); verify(ctx).tellNext(any(), eq("Cleared")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); @@ -346,6 +352,8 @@ public class TbAlarmNodeTest { node.onMsg(ctx, msg); + verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); + successCaptor.getValue().run(); verify(ctx).tellNext(any(), eq("Cleared")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); From d042707441b1a568c8ccebc4a6d055af934420af Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 26 Oct 2020 14:34:19 +0200 Subject: [PATCH 260/463] cherry-pick from 2.5.5 --- .../java/org/thingsboard/server/controller/AlarmController.java | 2 ++ .../org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 4d063d07f1..47aa4aa550 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -126,6 +126,7 @@ public class AlarmController extends BaseController { long ackTs = System.currentTimeMillis(); alarmService.ackAlarm(getCurrentUser().getTenantId(), alarmId, ackTs).get(); alarm.setAckTs(ackTs); + alarm.setStatus(alarm.getStatus().isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK); logEntityAction(alarm.getOriginator(), alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_ACK, null); } catch (Exception e) { throw handleException(e); @@ -143,6 +144,7 @@ public class AlarmController extends BaseController { long clearTs = System.currentTimeMillis(); alarmService.clearAlarm(getCurrentUser().getTenantId(), alarmId, null, clearTs).get(); alarm.setClearTs(clearTs); + alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK); logEntityAction(alarm.getOriginator(), alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_CLEAR, null); } catch (Exception e) { throw handleException(e); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java index bf0ac8f859..e23b0d4af0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java @@ -62,7 +62,7 @@ public abstract class TbAbstractAlarmNode Date: Mon, 26 Oct 2020 16:36:59 +0200 Subject: [PATCH 261/463] fix typo --- .../org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java | 1 - .../java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java | 1 - 2 files changed, 2 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java index e23b0d4af0..def7afecd2 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java @@ -112,5 +112,4 @@ public abstract class TbAbstractAlarmNode ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), alarmAction), throwable -> ctx.tellFailure(toAlarmMsg(ctx, alarmResult, msg), throwable)); } - } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java index 908e263400..fa2eeb7ba3 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java @@ -252,7 +252,6 @@ public class TbAlarmNodeTest { verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); successCaptor.getValue().run(); - verify(ctx).tellNext(any(), eq("Updated")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); From 03581c294161fdbebe38b775c080e8f9dad5a4d1 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 27 Oct 2020 18:30:23 +0200 Subject: [PATCH 262/463] Usage records --- .../server/actors/ActorSystemContext.java | 11 +- .../RuleChainActorMessageProcessor.java | 8 +- .../ruleChain/RuleChainManagerActor.java | 6 + .../RuleNodeActorMessageProcessor.java | 5 + .../server/actors/tenant/TenantActor.java | 63 ++++++---- .../server/service/apiusage/ApiFeature.java | 11 +- .../DefaultTbApiUsageStateService.java | 110 ++++++++++++------ .../service/apiusage/TenantApiUsageState.java | 24 +--- .../DefaultSystemDataLoaderService.java | 3 +- .../script/AbstractJsInvokeService.java | 61 ++++++---- .../AbstractNashornJsInvokeService.java | 8 +- .../script/NashornJsInvokeService.java | 7 +- .../service/script/RemoteJsInvokeService.java | 7 +- .../server/common/data/ApiUsageRecordKey.java | 22 +++- .../DefaultTenantProfileConfiguration.java | 19 +++ .../profile/TenantProfileConfiguration.java | 4 + ...ient.java => DefaultTbApiUsageClient.java} | 8 +- ...StatsClient.java => TbApiUsageClient.java} | 3 +- .../service/DefaultTransportService.java | 13 ++- .../sql/tenant/TenantProfileRepository.java | 1 + .../server/dao/tenant/TenantProfileDao.java | 1 + .../usagerecord/ApiUsageStateServiceImpl.java | 35 +++++- 22 files changed, 295 insertions(+), 135 deletions(-) rename common/queue/src/main/java/org/thingsboard/server/queue/usagestats/{DefaultTbUsageStatsClient.java => DefaultTbApiUsageClient.java} (93%) rename common/queue/src/main/java/org/thingsboard/server/queue/usagestats/{TbUsageStatsClient.java => TbApiUsageClient.java} (90%) diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 7f028932f2..5a0e291ac2 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -32,7 +32,6 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.MailService; -import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.actors.tenant.DebugTbRateLimits; import org.thingsboard.server.common.data.DataConstants; @@ -65,6 +64,8 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; @@ -106,6 +107,14 @@ public class ActorSystemContext { return debugPerTenantLimits; } + @Autowired + @Getter + private TbApiUsageStateService apiUsageStateService; + + @Autowired + @Getter + private TbApiUsageClient apiUsageClient; + @Autowired @Getter @Setter diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java index 5ca394be35..f5df62cfd3 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java @@ -23,6 +23,7 @@ import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.actors.TbEntityActorId; import org.thingsboard.server.actors.service.DefaultActorService; import org.thingsboard.server.actors.shared.ComponentMsgProcessor; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; @@ -46,6 +47,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.common.MultipleTbQueueTbMsgCallbackWrapper; import org.thingsboard.server.queue.common.TbQueueTbMsgCallbackWrapper; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.service.queue.TbClusterService; import java.util.ArrayList; @@ -68,15 +70,16 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor> nodeRoutes; private final RuleChainService service; private final TbClusterService clusterService; + private final TbApiUsageClient apiUsageClient; private String ruleChainName; private RuleNodeId firstId; private RuleNodeCtx firstNode; private boolean started; - RuleChainActorMessageProcessor(TenantId tenantId, RuleChain ruleChain, ActorSystemContext systemContext - , TbActorRef parent, TbActorRef self) { + RuleChainActorMessageProcessor(TenantId tenantId, RuleChain ruleChain, ActorSystemContext systemContext, TbActorRef parent, TbActorRef self) { super(systemContext, tenantId, ruleChain.getId()); + this.apiUsageClient = systemContext.getApiUsageClient(); this.ruleChainName = ruleChain.getName(); this.parent = parent; this.self = self; @@ -331,6 +334,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor(link -> ruleChainService.findTenantRuleChains(tenantId, link), ContextAwareActor.ENTITY_PACK_LIMIT)) { + ctx.stop(new TbEntityActorId(ruleChain.getId())); + } + } + protected void visit(RuleChain entity, TbActorRef actorRef) { if (entity != null && entity.isRoot()) { rootChain = entity; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java index db55ff8edf..52bd9f8c28 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java @@ -21,6 +21,7 @@ import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.TbActorCtx; import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.actors.shared.ComponentMsgProcessor; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; @@ -28,6 +29,7 @@ import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.common.msg.queue.RuleNodeException; import org.thingsboard.server.common.msg.queue.RuleNodeInfo; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; /** * @author Andrew Shvayka @@ -36,6 +38,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor isolatedTenantId = systemContext.getServiceInfoProvider().getIsolatedTenant(); TenantProfile tenantProfile = systemContext.getTenantProfileCache().get(tenant.getTenantProfileId()); - isRuleEngineForCurrentTenant = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE); isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE); - + isRuleEngineForCurrentTenant = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE); if (isRuleEngineForCurrentTenant) { try { if (isolatedTenantId.map(id -> id.equals(tenantId)).orElseGet(() -> !tenantProfile.isIsolatedTbRuleEngine())) { - log.info("[{}] Going to init rule chains", tenantId); - initRuleChains(); + if (apiUsageState.isReExecEnabled()) { + log.info("[{}] Going to init rule chains", tenantId); + initRuleChains(); + } else { + log.info("[{}] Skip init of the rule chains due to API limits", tenantId); + } } else { isRuleEngineForCurrentTenant = false; } @@ -98,8 +105,6 @@ public class TenantActor extends RuleChainManagerActor { } } catch (Exception e) { log.warn("[{}] Unknown failure", tenantId, e); -// TODO: throw this in 3.1? -// throw new TbActorException("Failed to init actor", e); } } @@ -115,7 +120,7 @@ public class TenantActor extends RuleChainManagerActor { if (msg.getMsgType().equals(MsgType.QUEUE_TO_RULE_ENGINE_MSG)) { QueueToRuleEngineMsg queueMsg = (QueueToRuleEngineMsg) msg; queueMsg.getTbMsg().getCallback().onSuccess(); - } else if (msg.getMsgType().equals(MsgType.TRANSPORT_TO_DEVICE_ACTOR_MSG)){ + } else if (msg.getMsgType().equals(MsgType.TRANSPORT_TO_DEVICE_ACTOR_MSG)) { TransportToDeviceActorMsgWrapper transportMsg = (TransportToDeviceActorMsgWrapper) msg; transportMsg.getCallback().onSuccess(); } @@ -173,26 +178,33 @@ public class TenantActor extends RuleChainManagerActor { return; } TbMsg tbMsg = msg.getTbMsg(); - if (tbMsg.getRuleChainId() == null) { - if (getRootChainActor() != null) { - getRootChainActor().tell(msg); + if (apiUsageState.isReExecEnabled()) { + if (tbMsg.getRuleChainId() == null) { + if (getRootChainActor() != null) { + getRootChainActor().tell(msg); + } else { + tbMsg.getCallback().onFailure(new RuleEngineException("No Root Rule Chain available!")); + log.info("[{}] No Root Chain: {}", tenantId, msg); + } } else { - tbMsg.getCallback().onFailure(new RuleEngineException("No Root Rule Chain available!")); - log.info("[{}] No Root Chain: {}", tenantId, msg); + try { + ctx.tell(new TbEntityActorId(tbMsg.getRuleChainId()), msg); + } catch (TbActorNotRegisteredException ex) { + log.trace("Received message for non-existing rule chain: [{}]", tbMsg.getRuleChainId()); + //TODO: 3.1 Log it to dead letters queue; + tbMsg.getCallback().onSuccess(); + } } } else { - try { - ctx.tell(new TbEntityActorId(tbMsg.getRuleChainId()), msg); - } catch (TbActorNotRegisteredException ex) { - log.trace("Received message for non-existing rule chain: [{}]", tbMsg.getRuleChainId()); - //TODO: 3.1 Log it to dead letters queue; - tbMsg.getCallback().onSuccess(); - } + log.trace("[{}] Ack message because Rule Engine is disabled", tenantId); + tbMsg.getCallback().onSuccess(); } } private void onRuleChainMsg(RuleChainAwareMsg msg) { - getOrCreateActor(msg.getRuleChainId()).tell(msg); + if (apiUsageState.isReExecEnabled()) { + getOrCreateActor(msg.getRuleChainId()).tell(msg); + } } private void onToDeviceActorMsg(DeviceAwareMsg msg, boolean priority) { @@ -208,6 +220,17 @@ public class TenantActor extends RuleChainManagerActor { } private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { + if (msg.getEntityId().getEntityType().equals(EntityType.API_USAGE_STATE)) { + ApiUsageState old = apiUsageState; + apiUsageState = new ApiUsageState(systemContext.getApiUsageStateService().getApiUsageState(tenantId)); + if (old.isReExecEnabled() && !apiUsageState.isReExecEnabled()) { + log.info("[{}] Received API state update. Going to DISABLE Rule Engine execution.", tenantId); + destroyRuleChains(); + } else if (!old.isReExecEnabled() && apiUsageState.isReExecEnabled()) { + log.info("[{}] Received API state update. Going to ENABLE Rule Engine execution.", tenantId); + initRuleChains(); + } + } if (isRuleEngineForCurrentTenant) { TbActorRef target = getEntityActorRef(msg.getEntityId()); if (target != null) { diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java b/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java index 8267d4addd..f2dfab54d4 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java @@ -15,6 +15,15 @@ */ package org.thingsboard.server.service.apiusage; +import lombok.Getter; + public enum ApiFeature { - TRANSPORT, DB, RE, JS + TRANSPORT("transportApiState"), DB("dbApiState"), RE("ruleEngineApiState"), JS("jsExecutionApiState"); + + @Getter + private final String apiStateKey; + + ApiFeature(String apiStateKey) { + this.apiStateKey = apiStateKey; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 61d0baca0c..1b1a2853c7 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -15,18 +15,24 @@ */ package org.thingsboard.server.service.apiusage; +import com.google.common.util.concurrent.FutureCallback; import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.tools.SchedulerUtils; @@ -34,7 +40,6 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; -import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; @@ -42,6 +47,7 @@ import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.profile.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import javax.annotation.PostConstruct; import java.util.ArrayList; @@ -60,11 +66,23 @@ import java.util.concurrent.locks.ReentrantLock; @Service public class DefaultTbApiUsageStateService implements TbApiUsageStateService { - public static final String HOURLY = "HOURLY_"; + public static final String HOURLY = "Hourly"; + public static final FutureCallback VOID_CALLBACK = new FutureCallback() { + @Override + public void onSuccess(@Nullable Void result) { + + } + + @Override + public void onFailure(Throwable t) { + + } + }; private final TbClusterService clusterService; private final PartitionService partitionService; private final ApiUsageStateService apiUsageStateService; private final TimeseriesService tsService; + private final TelemetrySubscriptionService tsWsService; private final SchedulerComponent scheduler; private final TbTenantProfileCache tenantProfileCache; @@ -84,13 +102,14 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { public DefaultTbApiUsageStateService(TbClusterService clusterService, PartitionService partitionService, ApiUsageStateService apiUsageStateService, - TimeseriesService tsService, + TimeseriesService tsService, TelemetrySubscriptionService tsWsService, SchedulerComponent scheduler, TbTenantProfileCache tenantProfileCache) { this.clusterService = clusterService; this.partitionService = partitionService; this.apiUsageStateService = apiUsageStateService; this.tsService = tsService; + this.tsWsService = tsWsService; this.scheduler = scheduler; this.tenantProfileCache = tenantProfileCache; } @@ -122,9 +141,9 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { for (UsageStatsKVProto kvProto : statsMsg.getValuesList()) { ApiUsageRecordKey recordKey = ApiUsageRecordKey.valueOf(kvProto.getKey()); long newValue = tenantState.add(recordKey, kvProto.getValue()); - updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.name(), newValue))); + updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.getApiCountKey(), newValue))); long newHourlyValue = tenantState.addToHourly(recordKey, kvProto.getValue()); - updatedEntries.add(new BasicTsKvEntry(hourTs, new LongDataEntry(HOURLY + recordKey.name(), newHourlyValue))); + updatedEntries.add(new BasicTsKvEntry(hourTs, new LongDataEntry(recordKey.getApiCountKey() + HOURLY, newHourlyValue))); Pair update = tenantState.checkStateUpdatedDueToThreshold(recordKey); if (update != null) { result.put(update.getFirst(), update.getSecond()); @@ -133,10 +152,11 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } finally { updateLock.unlock(); } - tsService.save(tenantId, tenantState.getApiUsageState().getId(), updatedEntries, 0L); + tsWsService.saveAndNotify(tenantId, tenantState.getApiUsageState().getId(), updatedEntries, 0L, VOID_CALLBACK); if (!result.isEmpty()) { persistAndNotify(tenantState, result); } + callback.onSuccess(); } @Override @@ -149,27 +169,38 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @Override public ApiUsageState getApiUsageState(TenantId tenantId) { - if (partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) { - TenantApiUsageState state = getOrFetchState(tenantId); - return state.getApiUsageState(); + TenantApiUsageState tenantState = myTenantStates.get(tenantId); + if (tenantState != null) { + return tenantState.getApiUsageState(); } else { ApiUsageState state = otherTenantStates.get(tenantId); - if (state == null) { - updateLock.lock(); - try { - state = otherTenantStates.get(tenantId); - if (state == null) { - state = apiUsageStateService.findTenantApiUsageState(tenantId); - otherTenantStates.put(tenantId, state); + if (state != null) { + return state; + } else { + if (partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) { + return getOrFetchState(tenantId).getApiUsageState(); + } else { + updateLock.lock(); + try { + state = otherTenantStates.get(tenantId); + if (state == null) { + state = apiUsageStateService.findTenantApiUsageState(tenantId); + otherTenantStates.put(tenantId, state); + } + } finally { + updateLock.unlock(); } - } finally { - updateLock.unlock(); + return state; } } - return state; } } + @Override + public void onApiUsageStateUpdate(TenantId tenantId) { + otherTenantStates.remove(tenantId); + } + @Override public void onTenantProfileUpdate(TenantProfileId tenantProfileId) { TenantProfile tenantProfile = tenantProfileCache.get(tenantProfileId); @@ -199,29 +230,38 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } - @Override - public void onApiUsageStateUpdate(TenantId tenantId) { - - } - private void updateTenantState(TenantApiUsageState state, TenantProfile tenantProfile) { + TenantProfileData oldProfileData = state.getTenantProfileData(); state.setTenantProfileData(tenantProfile.getProfileData()); Map result = state.checkStateUpdatedDueToThresholds(); if (!result.isEmpty()) { persistAndNotify(state, result); } + updateProfileThresholds(state.getTenantId(), state.getApiUsageState().getId(), + oldProfileData.getConfiguration(), tenantProfile.getProfileData().getConfiguration()); + } + + private void updateProfileThresholds(TenantId tenantId, ApiUsageStateId id, + TenantProfileConfiguration oldData, TenantProfileConfiguration newData) { + long ts = System.currentTimeMillis(); + List profileThresholds = new ArrayList<>(); + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { + if (oldData == null || oldData.getProfileThreshold(key) != newData.getProfileThreshold(key)) { + profileThresholds.add(new BasicTsKvEntry(ts, new LongDataEntry(key.getApiLimitKey(), newData.getProfileThreshold(key)))); + } + } + if (!profileThresholds.isEmpty()) { + tsWsService.saveAndNotify(tenantId, id, profileThresholds, 0L, VOID_CALLBACK); + } } private void persistAndNotify(TenantApiUsageState state, Map result) { - // TODO: - // 1. Broadcast to everyone notifications about enabled/disabled features. - // 2. Report rule engine and js executor metrics - // 4. UI for configuration of the thresholds - // 5. Max rule node executions per message. apiUsageStateService.update(state.getApiUsageState()); - if (result.containsKey(ApiFeature.TRANSPORT)) { - clusterService.onApiStateChange(state.getApiUsageState(), null); - } + clusterService.onApiStateChange(state.getApiUsageState(), null); + long ts = System.currentTimeMillis(); + List stateTelemetry = new ArrayList<>(); + result.forEach(((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new BooleanDataEntry(apiFeature.getApiStateKey(), aState))))); + tsWsService.saveAndNotify(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, 0L, VOID_CALLBACK); } private void checkStartOfNextCycle() { @@ -252,15 +292,15 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { TenantProfile tenantProfile = tenantProfileCache.get(tenantId); tenantState = new TenantApiUsageState(tenantProfile, dbStateEntity); try { - List dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getEntityId()).get(); + List dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getId()).get(); for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { boolean cycleEntryFound = false; boolean hourlyEntryFound = false; for (TsKvEntry tsKvEntry : dbValues) { - if (tsKvEntry.getKey().equals(key.name())) { + if (tsKvEntry.getKey().equals(key.getApiCountKey())) { cycleEntryFound = true; tenantState.put(key, tsKvEntry.getLongValue().get()); - } else if (tsKvEntry.getKey().equals(HOURLY + key.name())) { + } else if (tsKvEntry.getKey().equals(key.getApiCountKey() + HOURLY)) { hourlyEntryFound = true; if (tsKvEntry.getTs() == tenantState.getCurrentHourTs()) { tenantState.putHourly(key, tsKvEntry.getLongValue().get()); diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java index 5dcfb34ec3..8fcc1d46cb 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -21,11 +21,10 @@ import org.springframework.data.util.Pair; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; -import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; -import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.msg.tools.SchedulerUtils; import java.util.HashMap; @@ -101,30 +100,13 @@ public class TenantApiUsageState { } public long getProfileThreshold(ApiUsageRecordKey key) { - DefaultTenantProfileConfiguration config = (DefaultTenantProfileConfiguration) tenantProfileData.getConfiguration(); - switch (key) { - case TRANSPORT_MSG_COUNT: - return config.getMaxTransportMessages(); - case TRANSPORT_DP_COUNT: - return config.getMaxTransportDataPoints(); - case JS_EXEC_COUNT: - return config.getMaxJSExecutions(); - case RE_EXEC_COUNT: - return config.getMaxREExecutions(); - case STORAGE_DP_COUNT: - return config.getMaxDPStorageDays(); - } - return 0L; + return tenantProfileData.getConfiguration().getProfileThreshold(key); } public TenantId getTenantId() { return apiUsageState.getTenantId(); } - public EntityId getEntityId() { - return apiUsageState.getEntityId(); - } - public boolean isTransportEnabled() { return apiUsageState.isTransportEnabled(); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index eeef99fff6..e6d44f9de9 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -129,8 +129,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { tenantProfileService.findOrCreateDefaultTenantProfile(TenantId.SYS_TENANT_ID); TenantProfileData tenantProfileData = new TenantProfileData(); - DefaultTenantProfileConfiguration configuration = new DefaultTenantProfileConfiguration(); - tenantProfileData.setConfiguration(configuration); + tenantProfileData.setConfiguration(new DefaultTenantProfileConfiguration()); TenantProfile isolatedTbCoreProfile = new TenantProfile(); isolatedTbCoreProfile.setDefault(false); diff --git a/application/src/main/java/org/thingsboard/server/service/script/AbstractJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/AbstractJsInvokeService.java index eb5d3e3b49..8d9fa898dd 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/AbstractJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/AbstractJsInvokeService.java @@ -21,7 +21,8 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.queue.usagestats.TbUsageStatsClient; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import java.util.Map; import java.util.UUID; @@ -36,13 +37,15 @@ import java.util.concurrent.atomic.AtomicInteger; @Slf4j public abstract class AbstractJsInvokeService implements JsInvokeService { - private final TbUsageStatsClient apiUsageStatsClient; + private final TbApiUsageStateService apiUsageStateService; + private final TbApiUsageClient apiUsageClient; protected ScheduledExecutorService timeoutExecutorService; protected Map scriptIdToNameMap = new ConcurrentHashMap<>(); - protected Map blackListedFunctions = new ConcurrentHashMap<>(); + protected Map disabledFunctions = new ConcurrentHashMap<>(); - protected AbstractJsInvokeService(TbUsageStatsClient apiUsageStatsClient) { - this.apiUsageStatsClient = apiUsageStatsClient; + protected AbstractJsInvokeService(TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient) { + this.apiUsageStateService = apiUsageStateService; + this.apiUsageClient = apiUsageClient; } public void init(long maxRequestsTimeout) { @@ -59,24 +62,32 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { @Override public ListenableFuture eval(TenantId tenantId, JsScriptType scriptType, String scriptBody, String... argNames) { - UUID scriptId = UUID.randomUUID(); - String functionName = "invokeInternal_" + scriptId.toString().replace('-', '_'); - String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames); - return doEval(scriptId, functionName, jsScript); + if (apiUsageStateService.getApiUsageState(tenantId).isJsExecEnabled()) { + UUID scriptId = UUID.randomUUID(); + String functionName = "invokeInternal_" + scriptId.toString().replace('-', '_'); + String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames); + return doEval(scriptId, functionName, jsScript); + } else { + return Futures.immediateFailedFuture(new RuntimeException("JS Execution is disabled due to API limits!")); + } } @Override public ListenableFuture invokeFunction(TenantId tenantId, UUID scriptId, Object... args) { - String functionName = scriptIdToNameMap.get(scriptId); - if (functionName == null) { - return Futures.immediateFailedFuture(new RuntimeException("No compiled script found for scriptId: [" + scriptId + "]!")); - } - if (!isBlackListed(scriptId)) { - apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.JS_EXEC_COUNT, 1); - return doInvokeFunction(scriptId, functionName, args); + if (apiUsageStateService.getApiUsageState(tenantId).isJsExecEnabled()) { + String functionName = scriptIdToNameMap.get(scriptId); + if (functionName == null) { + return Futures.immediateFailedFuture(new RuntimeException("No compiled script found for scriptId: [" + scriptId + "]!")); + } + if (!isDisabled(scriptId)) { + apiUsageClient.report(tenantId, ApiUsageRecordKey.JS_EXEC_COUNT, 1); + return doInvokeFunction(scriptId, functionName, args); + } else { + return Futures.immediateFailedFuture( + new RuntimeException("Script invocation is blocked due to maximum error count " + getMaxErrors() + "!")); + } } else { - return Futures.immediateFailedFuture( - new RuntimeException("Script is blacklisted due to maximum error count " + getMaxErrors() + "!")); + return Futures.immediateFailedFuture(new RuntimeException("JS Execution is disabled due to API limits!")); } } @@ -86,7 +97,7 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { if (functionName != null) { try { scriptIdToNameMap.remove(scriptId); - blackListedFunctions.remove(scriptId); + disabledFunctions.remove(scriptId); doRelease(scriptId, functionName); } catch (Exception e) { return Futures.immediateFailedFuture(e); @@ -106,7 +117,7 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { protected abstract long getMaxBlacklistDuration(); protected void onScriptExecutionError(UUID scriptId) { - blackListedFunctions.computeIfAbsent(scriptId, key -> new BlackListInfo()).incrementAndGet(); + disabledFunctions.computeIfAbsent(scriptId, key -> new DisableListInfo()).incrementAndGet(); } private String generateJsScript(JsScriptType scriptType, String functionName, String scriptBody, String... argNames) { @@ -116,11 +127,11 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { throw new RuntimeException("No script factory implemented for scriptType: " + scriptType); } - private boolean isBlackListed(UUID scriptId) { - BlackListInfo errorCount = blackListedFunctions.get(scriptId); + private boolean isDisabled(UUID scriptId) { + DisableListInfo errorCount = disabledFunctions.get(scriptId); if (errorCount != null) { if (errorCount.getExpirationTime() <= System.currentTimeMillis()) { - blackListedFunctions.remove(scriptId); + disabledFunctions.remove(scriptId); return false; } else { return errorCount.get() >= getMaxErrors(); @@ -130,11 +141,11 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { } } - private class BlackListInfo { + private class DisableListInfo { private final AtomicInteger counter; private long expirationTime; - private BlackListInfo() { + private DisableListInfo() { this.counter = new AtomicInteger(0); } diff --git a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java index 17be0602f8..db8bb68b4e 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java @@ -24,10 +24,10 @@ import delight.nashornsandbox.NashornSandboxes; import jdk.nashorn.api.scripting.NashornScriptEngineFactory; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; -import org.thingsboard.server.queue.usagestats.TbUsageStatsClient; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -65,8 +65,8 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer @Value("${js.local.stats.enabled:false}") private boolean statsEnabled; - public AbstractNashornJsInvokeService(TbUsageStatsClient apiUsageStatsClient, JsExecutorService jsExecutor) { - super(apiUsageStatsClient); + public AbstractNashornJsInvokeService(TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient, JsExecutorService jsExecutor) { + super(apiUsageStateService, apiUsageClient); this.jsExecutor = jsExecutor; } diff --git a/application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java index c20f3e8c08..0dd45d340c 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java @@ -19,7 +19,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; -import org.thingsboard.server.queue.usagestats.TbUsageStatsClient; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import java.util.concurrent.TimeUnit; @@ -43,8 +44,8 @@ public class NashornJsInvokeService extends AbstractNashornJsInvokeService { @Value("${js.local.max_black_list_duration_sec:60}") private int maxBlackListDurationSec; - public NashornJsInvokeService(TbUsageStatsClient apiUsageStatsClient, JsExecutorService jsExecutor) { - super(apiUsageStatsClient, jsExecutor); + public NashornJsInvokeService(TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient, JsExecutorService jsExecutor) { + super(apiUsageStateService, apiUsageClient, jsExecutor); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java index 2ada577287..7b4114dce6 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java @@ -30,7 +30,8 @@ import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.queue.TbQueueRequestTemplate; import org.thingsboard.server.queue.common.TbProtoJsQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.queue.usagestats.TbUsageStatsClient; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import javax.annotation.Nullable; import javax.annotation.PostConstruct; @@ -69,8 +70,8 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { private final AtomicInteger queueFailedMsgs = new AtomicInteger(0); private final AtomicInteger queueTimeoutMsgs = new AtomicInteger(0); - public RemoteJsInvokeService(TbUsageStatsClient apiUsageStatsClient) { - super(apiUsageStatsClient); + public RemoteJsInvokeService(TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient) { + super(apiUsageStateService, apiUsageClient); } @Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms}") diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java index 0048e325f3..b1ff230e71 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java @@ -15,12 +15,24 @@ */ package org.thingsboard.server.common.data; +import lombok.Getter; + public enum ApiUsageRecordKey { - TRANSPORT_MSG_COUNT, - TRANSPORT_DP_COUNT, - STORAGE_DP_COUNT, - RE_EXEC_COUNT, - JS_EXEC_COUNT + TRANSPORT_MSG_COUNT("transportMsgCount", "transportMsgLimit"), + TRANSPORT_DP_COUNT("transportDataPointsCount", "transportDataPointsLimit"), + STORAGE_DP_COUNT("storageDataPointsCount", "storageDataPointsLimit"), + RE_EXEC_COUNT("ruleEngineExecutionCount", "ruleEngineExecutionLimit"), + JS_EXEC_COUNT("jsExecutionCount", "jsExecutionLimit"); + + @Getter + private final String apiCountKey; + @Getter + private final String apiLimitKey; + + ApiUsageRecordKey(String apiCountKey, String apiLimitKey) { + this.apiCountKey = apiCountKey; + this.apiLimitKey = apiLimitKey; + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java index f361ec901c..65940916b3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.data.tenant.profile; import lombok.Data; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.TenantProfileType; @Data @@ -38,6 +39,24 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura private long maxDPStorageDays; private int maxRuleNodeExecutionsPerMessage; + @Override + public long getProfileThreshold(ApiUsageRecordKey key) { + switch (key) { + case TRANSPORT_MSG_COUNT: + return maxTransportMessages; + case TRANSPORT_DP_COUNT: + return maxTransportDataPoints; + case JS_EXEC_COUNT: + return maxJSExecutions; + case RE_EXEC_COUNT: + return maxREExecutions; + case STORAGE_DP_COUNT: + return maxDPStorageDays; + } + return 0L; + } + + @Override public TenantProfileType getType() { return TenantProfileType.DEFAULT; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java index 07b184b389..d058d11cfc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.TenantProfileType; @JsonIgnoreProperties(ignoreUnknown = true) @@ -33,4 +34,7 @@ public interface TenantProfileConfiguration { @JsonIgnore TenantProfileType getType(); + @JsonIgnore + long getProfileThreshold(ApiUsageRecordKey key); + } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java similarity index 93% rename from common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java rename to common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java index 5a94cd505e..0702322838 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java @@ -40,7 +40,7 @@ import java.util.concurrent.atomic.AtomicLong; @Component @Slf4j -public class DefaultTbUsageStatsClient implements TbUsageStatsClient { +public class DefaultTbApiUsageClient implements TbApiUsageClient { @Value("${usage.stats.report.enabled:true}") private boolean enabled; @@ -53,7 +53,7 @@ public class DefaultTbUsageStatsClient implements TbUsageStatsClient { private final TbQueueProducerProvider producerProvider; private TbQueueProducer> msgProducer; - public DefaultTbUsageStatsClient(PartitionService partitionService, SchedulerComponent scheduler, TbQueueProducerProvider producerProvider) { + public DefaultTbApiUsageClient(PartitionService partitionService, SchedulerComponent scheduler, TbQueueProducerProvider producerProvider) { this.partitionService = partitionService; this.scheduler = scheduler; this.producerProvider = producerProvider; @@ -109,4 +109,8 @@ public class DefaultTbUsageStatsClient implements TbUsageStatsClient { } } + @Override + public void report(TenantId tenantId, ApiUsageRecordKey key) { + report(tenantId, key, 1L); + } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbApiUsageClient.java similarity index 90% rename from common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java rename to common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbApiUsageClient.java index 4ed850a018..31d85b27c7 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbApiUsageClient.java @@ -18,9 +18,10 @@ package org.thingsboard.server.queue.usagestats; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.id.TenantId; -public interface TbUsageStatsClient { +public interface TbApiUsageClient { void report(TenantId tenantId, ApiUsageRecordKey key, long value); + void report(TenantId tenantId, ApiUsageRecordKey key); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index f77a3ce44b..29bc1b04a7 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -78,7 +78,7 @@ import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbTransportQueueFactory; import org.thingsboard.server.queue.scheduler.SchedulerComponent; -import org.thingsboard.server.queue.usagestats.TbUsageStatsClient; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.queue.util.TbTransportComponent; import javax.annotation.PostConstruct; @@ -123,7 +123,7 @@ public class DefaultTransportService implements TransportService { private final StatsFactory statsFactory; private final TransportDeviceProfileCache deviceProfileCache; private final TransportTenantProfileCache tenantProfileCache; - private final TbUsageStatsClient apiUsageStatsClient; + private final TbApiUsageClient apiUsageClient; private final TransportRateLimitService rateLimitService; private final DataDecodingEncodingService dataDecodingEncodingService; private final SchedulerComponent scheduler; @@ -152,7 +152,7 @@ public class DefaultTransportService implements TransportService { StatsFactory statsFactory, TransportDeviceProfileCache deviceProfileCache, TransportTenantProfileCache tenantProfileCache, - TbUsageStatsClient apiUsageStatsClient, TransportRateLimitService rateLimitService, + TbApiUsageClient apiUsageClient, TransportRateLimitService rateLimitService, DataDecodingEncodingService dataDecodingEncodingService, SchedulerComponent scheduler) { this.serviceInfoProvider = serviceInfoProvider; this.queueProvider = queueProvider; @@ -161,7 +161,7 @@ public class DefaultTransportService implements TransportService { this.statsFactory = statsFactory; this.deviceProfileCache = deviceProfileCache; this.tenantProfileCache = tenantProfileCache; - this.apiUsageStatsClient = apiUsageStatsClient; + this.apiUsageClient = apiUsageClient; this.rateLimitService = rateLimitService; this.dataDecodingEncodingService = dataDecodingEncodingService; this.scheduler = scheduler; @@ -647,6 +647,7 @@ public class DefaultTransportService implements TransportService { if (stateOpt.isPresent()) { ApiUsageState apiUsageState = stateOpt.get(); rateLimitService.update(apiUsageState.getTenantId(), apiUsageState.isTransportEnabled()); + //TODO: if transport is disabled, we should close all sessions and not to check credentials. } } } else if (toSessionMsg.hasEntityDeleteMsg()) { @@ -821,8 +822,8 @@ public class DefaultTransportService implements TransportService { @Override public void onSuccess(T msg) { try { - apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.TRANSPORT_MSG_COUNT, 1); - apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.TRANSPORT_DP_COUNT, dataPoints); + apiUsageClient.report(tenantId, ApiUsageRecordKey.TRANSPORT_MSG_COUNT, 1); + apiUsageClient.report(tenantId, ApiUsageRecordKey.TRANSPORT_DP_COUNT, dataPoints); } finally { callback.onSuccess(msg); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantProfileRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantProfileRepository.java index 9c0687ab64..0deae8f729 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantProfileRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantProfileRepository.java @@ -51,4 +51,5 @@ public interface TenantProfileRepository extends PagingAndSortingRepository { TenantProfile findDefaultTenantProfile(TenantId tenantId); EntityInfo findDefaultTenantProfileInfo(TenantId tenantId); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index 15be73a0b8..e1b494e2e8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -17,15 +17,26 @@ package org.thingsboard.server.dao.usagerecord; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.tenant.TenantDao; +import org.thingsboard.server.dao.tenant.TenantProfileDao; +import org.thingsboard.server.dao.timeseries.TimeseriesService; + +import java.util.ArrayList; +import java.util.List; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -35,11 +46,15 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; private final ApiUsageStateDao apiUsageStateDao; + private final TenantProfileDao tenantProfileDao; private final TenantDao tenantDao; + private final TimeseriesService tsService; - public ApiUsageStateServiceImpl(TenantDao tenantDao, ApiUsageStateDao apiUsageStateDao) { + public ApiUsageStateServiceImpl(TenantDao tenantDao, ApiUsageStateDao apiUsageStateDao, TenantProfileDao tenantProfileDao, TimeseriesService tsService) { this.tenantDao = tenantDao; this.apiUsageStateDao = apiUsageStateDao; + this.tenantProfileDao = tenantProfileDao; + this.tsService = tsService; } @Override @@ -57,7 +72,18 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A apiUsageState.setTenantId(tenantId); apiUsageState.setEntityId(tenantId); apiUsageStateValidator.validate(apiUsageState, ApiUsageState::getTenantId); - return apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); + + ApiUsageState saved = apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); + + Tenant tenant = tenantDao.findById(tenantId, tenantId.getId()); + TenantProfile tenantProfile = tenantProfileDao.findById(tenantId, tenant.getTenantProfileId().getId()); + TenantProfileConfiguration configuration = tenantProfile.getProfileData().getConfiguration(); + List profileThresholds = new ArrayList<>(); + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { + profileThresholds.add(new BasicTsKvEntry(saved.getCreatedTime(), new LongDataEntry(key.getApiLimitKey(), configuration.getProfileThreshold(key)))); + } + tsService.save(tenantId, saved.getId(), profileThresholds, 0L); + return saved; } @Override @@ -80,7 +106,8 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A log.trace("Executing findApiUsageStateById, tenantId [{}], apiUsageStateId [{}]", tenantId, id); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(id, "Incorrect apiUsageStateId " + id); - return apiUsageStateDao.findById(tenantId, id.getId()); } + return apiUsageStateDao.findById(tenantId, id.getId()); + } private DataValidator apiUsageStateValidator = new DataValidator() { From e9592174c2293b842ef6f1946dbd11d2e27056e4 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 27 Oct 2020 19:39:14 +0200 Subject: [PATCH 263/463] Api Tenant States --- .../DefaultTbApiUsageStateService.java | 47 ++++++++++++++----- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 1b1a2853c7..17563bcbbd 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -23,6 +23,7 @@ import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.TenantId; @@ -31,11 +32,13 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.tools.SchedulerUtils; +import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; @@ -69,17 +72,14 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { public static final String HOURLY = "Hourly"; public static final FutureCallback VOID_CALLBACK = new FutureCallback() { @Override - public void onSuccess(@Nullable Void result) { - - } + public void onSuccess(@Nullable Void result) {} @Override - public void onFailure(Throwable t) { - - } + public void onFailure(Throwable t) {} }; private final TbClusterService clusterService; private final PartitionService partitionService; + private final TenantService tenantService; private final ApiUsageStateService apiUsageStateService; private final TimeseriesService tsService; private final TelemetrySubscriptionService tsWsService; @@ -101,12 +101,13 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { public DefaultTbApiUsageStateService(TbClusterService clusterService, PartitionService partitionService, - ApiUsageStateService apiUsageStateService, + TenantService tenantService, ApiUsageStateService apiUsageStateService, TimeseriesService tsService, TelemetrySubscriptionService tsWsService, SchedulerComponent scheduler, TbTenantProfileCache tenantProfileCache) { this.clusterService = clusterService; this.partitionService = partitionService; + this.tenantService = tenantService; this.apiUsageStateService = apiUsageStateService; this.tsService = tsService; this.tsWsService = tsWsService; @@ -117,6 +118,8 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @PostConstruct public void init() { if (enabled) { + log.info("Starting api usage service."); + initStatesFromDataBase(); scheduler.scheduleAtFixedRate(this::checkStartOfNextCycle, nextCycleCheckInterval, nextCycleCheckInterval, TimeUnit.MILLISECONDS); } } @@ -164,6 +167,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { if (partitionChangeEvent.getServiceType().equals(ServiceType.TB_CORE)) { myTenantStates.entrySet().removeIf(entry -> !partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition()); otherTenantStates.entrySet().removeIf(entry -> partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition()); + initStatesFromDataBase(); } } @@ -299,12 +303,10 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { for (TsKvEntry tsKvEntry : dbValues) { if (tsKvEntry.getKey().equals(key.getApiCountKey())) { cycleEntryFound = true; - tenantState.put(key, tsKvEntry.getLongValue().get()); + tenantState.put(key, tsKvEntry.getTs() == tenantState.getCurrentCycleTs() ? tsKvEntry.getLongValue().get() : 0L); } else if (tsKvEntry.getKey().equals(key.getApiCountKey() + HOURLY)) { hourlyEntryFound = true; - if (tsKvEntry.getTs() == tenantState.getCurrentHourTs()) { - tenantState.putHourly(key, tsKvEntry.getLongValue().get()); - } + tenantState.putHourly(key, tsKvEntry.getTs() == tenantState.getCurrentHourTs() ? tsKvEntry.getLongValue().get() : 0L); } if (cycleEntryFound && hourlyEntryFound) { break; @@ -319,4 +321,25 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { return tenantState; } + private void initStatesFromDataBase() { + try { + PageDataIterable tenantIterator = new PageDataIterable<>(tenantService::findTenants, 1024); + for (Tenant tenant : tenantIterator) { + if (!myTenantStates.containsKey(tenant.getId()) && partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), tenant.getId()).isMyPartition()) { + updateLock.lock(); + try { + updateTenantState(getOrFetchState(tenant.getId()), tenantProfileCache.get(tenant.getTenantProfileId())); + } catch (Exception e) { + log.warn("[{}] Failed to initialize tenant API state", tenant.getId(), e); + } finally { + updateLock.unlock(); + } + } + } + log.info("Api usage service started."); + } catch (Exception e) { + log.warn("Unknown failure", e); + } + } + } From d4ae02f9a6dd94c1cad7adb5773fd94ea7ad374f Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 27 Oct 2020 19:39:45 +0200 Subject: [PATCH 264/463] Update State of the API on startup --- .../server/service/apiusage/DefaultTbApiUsageStateService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 17563bcbbd..3f9fab11ec 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, From c2a2b8cc4fdf1b5fa2e914cce2f5ec2ccb161035 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 28 Oct 2020 10:53:50 +0200 Subject: [PATCH 265/463] base url improvements --- .../oauth2/Oauth2AuthenticationFailureHandler.java | 10 ++-------- .../oauth2/Oauth2AuthenticationSuccessHandler.java | 12 +++--------- .../server/controller/AbstractControllerTest.java | 9 --------- 3 files changed, 5 insertions(+), 26 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java index 10fe5fe3fd..984936874f 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java @@ -19,10 +19,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.service.security.system.SystemSecurityService; import org.thingsboard.server.utils.MiscUtils; import javax.servlet.ServletException; @@ -34,15 +30,13 @@ import java.nio.charset.StandardCharsets; @Component(value = "oauth2AuthenticationFailureHandler") @ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true") -public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { - - private SystemSecurityService systemSecurityService; +public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { - String baseUrl = systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); + String baseUrl = MiscUtils.constructBaseUrl(request); getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" + URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8.toString())); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index ece8708fd3..6641ab0ac4 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -21,16 +21,13 @@ import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.oauth2.OAuth2Client; import org.thingsboard.server.dao.oauth2.OAuth2Configuration; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.token.JwtToken; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; -import org.thingsboard.server.service.security.system.SystemSecurityService; +import org.thingsboard.server.utils.MiscUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -46,26 +43,23 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS private final RefreshTokenRepository refreshTokenRepository; private final OAuth2ClientMapperProvider oauth2ClientMapperProvider; private final OAuth2Configuration oauth2Configuration; - private final SystemSecurityService systemSecurityService; @Autowired public Oauth2AuthenticationSuccessHandler(final JwtTokenFactory tokenFactory, final RefreshTokenRepository refreshTokenRepository, final OAuth2ClientMapperProvider oauth2ClientMapperProvider, - final OAuth2Configuration oauth2Configuration, SystemSecurityService systemSecurityService) { + final OAuth2Configuration oauth2Configuration) { this.tokenFactory = tokenFactory; this.refreshTokenRepository = refreshTokenRepository; this.oauth2ClientMapperProvider = oauth2ClientMapperProvider; this.oauth2Configuration = oauth2Configuration; - this.systemSecurityService = systemSecurityService; } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { - - String baseUrl = systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); + String baseUrl = MiscUtils.constructBaseUrl(request); try { OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java index c0d79204d0..b819d70a7a 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java @@ -18,7 +18,6 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Header; import io.jsonwebtoken.Jwt; @@ -59,7 +58,6 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilde import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.context.WebApplicationContext; -import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Tenant; @@ -70,7 +68,6 @@ import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.config.ThingsboardSecurityConfiguration; -import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.service.mail.TestMailService; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest; import org.thingsboard.server.service.security.auth.rest.LoginRequest; @@ -176,12 +173,6 @@ public abstract class AbstractControllerTest { .apply(springSecurity()).build(); } loginSysAdmin(); - - ObjectNode generalSettings = JacksonUtil.OBJECT_MAPPER.createObjectNode(); - AdminSettings adminSettings = new AdminSettings(); - adminSettings.setKey("general"); - adminSettings.setJsonValue(generalSettings); - Tenant tenant = new Tenant(); tenant.setTitle(TEST_TENANT_NAME); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); From 7724495cb12530a45e64761c5cb66b908686237f Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 28 Oct 2020 14:48:00 +0200 Subject: [PATCH 266/463] added default queue for device profile --- .../src/main/data/upgrade/3.1.1/schema_update_before.sql | 1 + .../server/service/queue/DefaultTbClusterService.java | 4 ++++ .../org/thingsboard/server/common/data/DeviceProfile.java | 2 ++ .../main/java/org/thingsboard/server/common/msg/TbMsg.java | 5 +++++ .../org/thingsboard/server/dao/model/ModelConstants.java | 1 + .../server/dao/model/sql/DeviceProfileEntity.java | 5 +++++ dao/src/main/resources/sql/schema-entities-hsql.sql | 1 + dao/src/main/resources/sql/schema-entities.sql | 1 + .../profile/add-device-profile-dialog.component.html | 5 +++++ .../profile/add-device-profile-dialog.component.ts | 4 ++++ .../home/components/profile/device-profile.component.html | 5 +++++ .../home/components/profile/device-profile.component.ts | 5 +++++ .../components/wizard/device-wizard-dialog.component.html | 7 +++++++ .../components/wizard/device-wizard-dialog.component.ts | 7 +++++++ ui-ngx/src/app/shared/models/device.models.ts | 1 + ui-ngx/src/assets/locale/locale.constant-en_US.json | 1 + 16 files changed, 55 insertions(+) diff --git a/application/src/main/data/upgrade/3.1.1/schema_update_before.sql b/application/src/main/data/upgrade/3.1.1/schema_update_before.sql index 216940b8f0..5f30f135e1 100644 --- a/application/src/main/data/upgrade/3.1.1/schema_update_before.sql +++ b/application/src/main/data/upgrade/3.1.1/schema_update_before.sql @@ -96,6 +96,7 @@ CREATE TABLE IF NOT EXISTS device_profile ( is_default boolean, tenant_id uuid, default_rule_chain_id uuid, + default_queue_name varchar(255), provision_device_key varchar, CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index 9451b58e9f..5d59955e2b 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -159,6 +159,10 @@ public class DefaultTbClusterService implements TbClusterService { if (targetRuleChainId != null && !targetRuleChainId.equals(tbMsg.getRuleChainId())) { tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId); } + String targetQueueName = deviceProfile.getDefaultQueueName(); + if (targetQueueName != null && !targetQueueName.equals(tbMsg.getQueueName())) { + tbMsg = TbMsg.transformMsg(tbMsg, targetQueueName); + } } return tbMsg; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index 10990bc436..b9ecb862eb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -43,6 +43,7 @@ public class DeviceProfile extends SearchTextBased implements H private DeviceTransportType transportType; private DeviceProfileProvisionType provisionType; private RuleChainId defaultRuleChainId; + private String defaultQueueName; private transient DeviceProfileData profileData; @JsonIgnore private byte[] profileDataBytes; @@ -63,6 +64,7 @@ public class DeviceProfile extends SearchTextBased implements H this.description = deviceProfile.getDescription(); this.isDefault = deviceProfile.isDefault(); this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId(); + this.defaultQueueName = deviceProfile.getDefaultQueueName(); this.setProfileData(deviceProfile.getProfileData()); this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey(); } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index f0af529be9..5ff8527327 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -90,6 +90,11 @@ public final class TbMsg implements Serializable { origMsg.data, ruleChainId, null, origMsg.getCallback()); } + public static TbMsg transformMsg(TbMsg origMsg, String queueName) { + return new TbMsg(queueName, origMsg.id, origMsg.ts, origMsg.type, origMsg.originator, origMsg.metaData, origMsg.dataType, + origMsg.data, origMsg.getRuleChainId(), null, origMsg.getCallback()); + } + public static TbMsg newMsg(TbMsg tbMsg, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { return new TbMsg(tbMsg.getQueueName(), UUID.randomUUID(), tbMsg.getTs(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.getMetaData().copy(), tbMsg.getDataType(), tbMsg.getData(), ruleChainId, ruleNodeId, TbMsgCallback.EMPTY); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 14ab9ed9e0..f7e8711dcb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -174,6 +174,7 @@ public class ModelConstants { public static final String DEVICE_PROFILE_DESCRIPTION_PROPERTY = "description"; public static final String DEVICE_PROFILE_IS_DEFAULT_PROPERTY = "is_default"; public static final String DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY = "default_rule_chain_id"; + public static final String DEVICE_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY = "default_queue_name"; public static final String DEVICE_PROFILE_PROVISION_DEVICE_KEY = "provision_device_key"; /** diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java index 264d0725ae..d70268c349 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java @@ -79,6 +79,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl @Column(name = ModelConstants.DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY, columnDefinition = "uuid") private UUID defaultRuleChainId; + @Column(name = ModelConstants.DEVICE_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY) + private String defaultQueueName; + @Type(type = "jsonb") @Column(name = ModelConstants.DEVICE_PROFILE_PROFILE_DATA_PROPERTY, columnDefinition = "jsonb") private JsonNode profileData; @@ -108,6 +111,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl if (deviceProfile.getDefaultRuleChainId() != null) { this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId().getId(); } + this.defaultQueueName = deviceProfile.getDefaultQueueName(); this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey(); } @@ -142,6 +146,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl if (defaultRuleChainId != null) { deviceProfile.setDefaultRuleChainId(new RuleChainId(defaultRuleChainId)); } + deviceProfile.setDefaultQueueName(defaultQueueName); deviceProfile.setProvisionDeviceKey(provisionDeviceKey); return deviceProfile; } diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index 41d9e3ac81..28abe32786 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -170,6 +170,7 @@ CREATE TABLE IF NOT EXISTS device_profile ( is_default boolean, tenant_id uuid, default_rule_chain_id uuid, + default_queue_name varchar(255), provision_device_key varchar, CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index e1ffba98eb..233cd509da 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -188,6 +188,7 @@ CREATE TABLE IF NOT EXISTS device_profile ( is_default boolean, tenant_id uuid, default_rule_chain_id uuid, + default_queue_name varchar(255), provision_device_key varchar, CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html index fd5b5b17c2..b53ca8f0b0 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html @@ -45,6 +45,11 @@ labelText="device-profile.default-rule-chain" formControlName="defaultRuleChainId"> + + +
device-profile.select-queue-hint
device-profile.type diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts index fee4f91c66..07ce80aca6 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts @@ -48,6 +48,7 @@ import { MatHorizontalStepper } from '@angular/material/stepper'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; import { StepperSelectionEvent } from '@angular/cdk/stepper'; import { deepTrim } from '@core/utils'; +import {ServiceType} from "@shared/models/queue.models"; export interface AddDeviceProfileDialogData { deviceProfileName: string; @@ -89,6 +90,8 @@ export class AddDeviceProfileDialogComponent extends provisionConfigFormGroup: FormGroup; + serviceType = ServiceType.TB_RULE_ENGINE; + constructor(protected store: Store, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: AddDeviceProfileDialogData, @@ -104,6 +107,7 @@ export class AddDeviceProfileDialogComponent extends name: [data.deviceProfileName, [Validators.required]], type: [DeviceProfileType.DEFAULT, [Validators.required]], defaultRuleChainId: [null, []], + defaultQueueName: ['', []], description: ['', []] } ); diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html index f74b19891e..0eb4c07367 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html @@ -53,6 +53,11 @@ labelText="device-profile.default-rule-chain" formControlName="defaultRuleChainId"> + + +
device-profile.select-queue-hint
device-profile.type diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts index e837caf660..d30c8b47b9 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts @@ -38,6 +38,7 @@ import { } from '@shared/models/device.models'; import { EntityType } from '@shared/models/entity-type.models'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; +import {ServiceType} from "@shared/models/queue.models"; @Component({ selector: 'tb-device-profile', @@ -63,6 +64,8 @@ export class DeviceProfileComponent extends EntityComponent { displayTransportConfiguration: boolean; + serviceType = ServiceType.TB_RULE_ENGINE; + constructor(protected store: Store, protected translate: TranslateService, @Optional() @Inject('entity') protected entityValue: DeviceProfile, @@ -101,6 +104,7 @@ export class DeviceProfileComponent extends EntityComponent { provisionConfiguration: [deviceProvisionConfiguration, Validators.required] }), defaultRuleChainId: [entity && entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null, []], + defaultQueueName: [entity ? entity.defaultQueueName : '', []], description: [entity ? entity.description : '', []], } ); @@ -174,6 +178,7 @@ export class DeviceProfileComponent extends EntityComponent { provisionConfiguration: deviceProvisionConfiguration }}); this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}); + this.entityForm.patchValue({defaultQueueName: entity.defaultQueueName}); this.entityForm.patchValue({description: entity.description}); } diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index 0d224122d6..fe7849d3e1 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -97,6 +97,13 @@ formControlName="defaultRuleChainId"> +
+ + +
device-profile.select-queue-hint
+
{{ 'device.is-gateway' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts index c4f100b30f..a539b48917 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts @@ -43,6 +43,7 @@ import { StepperSelectionEvent } from '@angular/cdk/stepper'; import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; +import {ServiceType} from "@shared/models/queue.models"; @Component({ selector: 'tb-device-wizard', @@ -84,6 +85,8 @@ export class DeviceWizardDialogComponent extends labelPosition = 'end'; + serviceType = ServiceType.TB_RULE_ENGINE; + private subscriptions: Subscription[] = []; constructor(protected store: Store, @@ -105,6 +108,7 @@ export class DeviceWizardDialogComponent extends deviceProfileId: [null, Validators.required], newDeviceProfileTitle: [{value: null, disabled: true}], defaultRuleChainId: [{value: null, disabled: true}], + defaultQueueName: [''], description: [''] } ); @@ -117,6 +121,7 @@ export class DeviceWizardDialogComponent extends this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators(null); this.deviceWizardFormGroup.get('newDeviceProfileTitle').disable(); this.deviceWizardFormGroup.get('defaultRuleChainId').disable(); + this.deviceWizardFormGroup.get('defaultQueueName').disable(); this.deviceWizardFormGroup.updateValueAndValidity(); this.createProfile = false; this.createTransportConfiguration = false; @@ -126,6 +131,8 @@ export class DeviceWizardDialogComponent extends this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]); this.deviceWizardFormGroup.get('newDeviceProfileTitle').enable(); this.deviceWizardFormGroup.get('defaultRuleChainId').enable(); + this.deviceWizardFormGroup.get('defaultQueueName').enable(); + this.deviceWizardFormGroup.updateValueAndValidity(); this.createProfile = true; this.createTransportConfiguration = this.deviceWizardFormGroup.get('transportType').value && diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index f6850dbc57..9fbe289543 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -369,6 +369,7 @@ export interface DeviceProfile extends BaseData { provisionType: DeviceProvisionType; provisionDeviceKey?: string; defaultRuleChainId?: RuleChainId; + defaultQueueName?: string; profileData: DeviceProfileData; } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 453d5d2abd..22f4b9527d 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -872,6 +872,7 @@ "profile-configuration": "Profile configuration", "transport-configuration": "Transport configuration", "default-rule-chain": "Default rule chain", + "select-queue-hint": "The queue name can be selected from a drop-down list or add a custom name.", "delete-device-profile-title": "Are you sure you want to delete the device profile '{{deviceProfileName}}'?", "delete-device-profile-text": "Be careful, after the confirmation the device profile and all related data will become unrecoverable.", "delete-device-profiles-title": "Are you sure you want to delete { count, plural, 1 {1 device profile} other {# device profiles} }?", From df133e48fffee8d66c8df9338471bc5d22a9693f Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 28 Oct 2020 16:28:04 +0200 Subject: [PATCH 267/463] DefaultTransportService refactored --- .../queue/DefaultTbClusterService.java | 12 +- .../thingsboard/server/common/msg/TbMsg.java | 5 + .../service/DefaultTransportService.java | 161 ++++++++++-------- 3 files changed, 106 insertions(+), 72 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index 5d59955e2b..9a331bb208 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -156,11 +156,15 @@ public class DefaultTbClusterService implements TbClusterService { private TbMsg transformMsg(TbMsg tbMsg, DeviceProfile deviceProfile) { if (deviceProfile != null) { RuleChainId targetRuleChainId = deviceProfile.getDefaultRuleChainId(); - if (targetRuleChainId != null && !targetRuleChainId.equals(tbMsg.getRuleChainId())) { - tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId); - } String targetQueueName = deviceProfile.getDefaultQueueName(); - if (targetQueueName != null && !targetQueueName.equals(tbMsg.getQueueName())) { + boolean isRuleChainTransform = targetRuleChainId != null && !targetRuleChainId.equals(tbMsg.getRuleChainId()); + boolean isQueueTransform = targetQueueName != null && !targetQueueName.equals(tbMsg.getQueueName()); + + if (isRuleChainTransform && isQueueTransform) { + tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId, targetQueueName); + } else if (isRuleChainTransform) { + tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId); + } else if (isQueueTransform) { tbMsg = TbMsg.transformMsg(tbMsg, targetQueueName); } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index 5ff8527327..844a4d0216 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -95,6 +95,11 @@ public final class TbMsg implements Serializable { origMsg.data, origMsg.getRuleChainId(), null, origMsg.getCallback()); } + public static TbMsg transformMsg(TbMsg origMsg, RuleChainId ruleChainId, String queueName) { + return new TbMsg(queueName, origMsg.id, origMsg.ts, origMsg.type, origMsg.originator, origMsg.metaData, origMsg.dataType, + origMsg.data, ruleChainId, null, origMsg.getCallback()); + } + public static TbMsg newMsg(TbMsg tbMsg, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { return new TbMsg(tbMsg.getQueueName(), UUID.randomUUID(), tbMsg.getTs(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.getMetaData().copy(), tbMsg.getDataType(), tbMsg.getData(), ruleChainId, ruleNodeId, TbMsgCallback.EMPTY); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 3f37685383..aabb8c927e 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -23,7 +23,6 @@ import com.google.gson.JsonObject; import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.DeviceProfile; @@ -53,21 +52,46 @@ import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.common.transport.limits.TransportRateLimit; import org.thingsboard.server.common.transport.limits.TransportRateLimitService; import org.thingsboard.server.common.transport.limits.TransportRateLimitType; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.common.transport.util.JsonUtils; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; +import org.thingsboard.server.gen.transport.TransportProtos.EntityDeleteMsg; +import org.thingsboard.server.gen.transport.TransportProtos.EntityUpdateMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; +import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto; +import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; +import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; +import org.thingsboard.server.gen.transport.TransportProtos.SessionType; +import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueMsgMetadata; @@ -237,14 +261,14 @@ public class DefaultTransportService implements TransportService { } @Override - public void registerAsyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener) { - sessions.putIfAbsent(toSessionId(sessionInfo), new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listener)); + public void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener) { + sessions.putIfAbsent(toSessionId(sessionInfo), new SessionMetaData(sessionInfo, SessionType.ASYNC, listener)); } @Override - public TransportProtos.GetEntityProfileResponseMsg getRoutingInfo(TransportProtos.GetEntityProfileRequestMsg msg) { - TbProtoQueueMsg protoMsg = - new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setEntityProfileRequestMsg(msg).build()); + public GetEntityProfileResponseMsg getRoutingInfo(GetEntityProfileRequestMsg msg) { + TbProtoQueueMsg protoMsg = + new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setEntityProfileRequestMsg(msg).build()); try { TbProtoQueueMsg response = transportApiRequestTemplate.send(protoMsg).get(); return response.getValue().getEntityProfileResponseMsg(); @@ -254,7 +278,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceTokenRequestMsg msg, + public void process(DeviceTransportType transportType, ValidateDeviceTokenRequestMsg msg, TransportServiceCallback callback) { log.trace("Processing msg: {}", msg); TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), @@ -263,7 +287,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(DeviceTransportType transportType, TransportProtos.ValidateBasicMqttCredRequestMsg msg, + public void process(DeviceTransportType transportType, ValidateBasicMqttCredRequestMsg msg, TransportServiceCallback callback) { log.trace("Processing msg: {}", msg); TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), @@ -272,7 +296,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback callback) { + public void process(DeviceTransportType transportType, ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback callback) { log.trace("Processing msg: {}", msg); TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateX509CertRequestMsg(msg).build()); doProcess(transportType, protoMsg, callback); @@ -281,7 +305,7 @@ public class DefaultTransportService implements TransportService { private void doProcess(DeviceTransportType transportType, TbProtoQueueMsg protoMsg, TransportServiceCallback callback) { ListenableFuture response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> { - TransportProtos.ValidateDeviceCredentialsResponseMsg msg = tmp.getValue().getValidateCredResponseMsg(); + ValidateDeviceCredentialsResponseMsg msg = tmp.getValue().getValidateCredResponseMsg(); ValidateDeviceCredentialsResponse.ValidateDeviceCredentialsResponseBuilder result = ValidateDeviceCredentialsResponse.builder(); if (msg.hasDeviceInfo()) { result.credentials(msg.getCredentialsBody()); @@ -304,11 +328,11 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg requestMsg, TransportServiceCallback callback) { + public void process(GetOrCreateDeviceFromGatewayRequestMsg requestMsg, TransportServiceCallback callback) { TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setGetOrCreateDeviceRequestMsg(requestMsg).build()); log.trace("Processing msg: {}", requestMsg); ListenableFuture response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> { - TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg msg = tmp.getValue().getGetOrCreateDeviceResponseMsg(); + GetOrCreateDeviceFromGatewayResponseMsg msg = tmp.getValue().getGetOrCreateDeviceResponseMsg(); GetOrCreateDeviceFromGatewayResponse.GetOrCreateDeviceFromGatewayResponseBuilder result = GetOrCreateDeviceFromGatewayResponse.builder(); if (msg.hasDeviceInfo()) { TransportDeviceInfo tdi = getTransportDeviceInfo(msg.getDeviceInfo()); @@ -323,7 +347,7 @@ public class DefaultTransportService implements TransportService { AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor); } - private TransportDeviceInfo getTransportDeviceInfo(TransportProtos.DeviceInfoProto di) { + private TransportDeviceInfo getTransportDeviceInfo(DeviceInfoProto di) { TransportDeviceInfo tdi = new TransportDeviceInfo(); tdi.setTenantId(new TenantId(new UUID(di.getTenantIdMSB(), di.getTenantIdLSB()))); tdi.setDeviceId(new DeviceId(new UUID(di.getDeviceIdMSB(), di.getDeviceIdLSB()))); @@ -345,7 +369,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscriptionInfoProto msg, TransportServiceCallback callback) { + public void process(SessionInfoProto sessionInfo, SubscriptionInfoProto msg, TransportServiceCallback callback) { if (log.isTraceEnabled()) { log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg); } @@ -354,7 +378,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SessionEventMsg msg, TransportServiceCallback callback) { + public void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) @@ -363,9 +387,9 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback callback) { + public void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback callback) { int dataPoints = 0; - for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { + for (TsKvListProto tsKv : msg.getTsKvListList()) { dataPoints += tsKv.getKvCount(); } if (checkLimits(sessionInfo, msg, callback, dataPoints, TELEMETRY)) { @@ -373,22 +397,19 @@ public class DefaultTransportService implements TransportService { TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); MsgPackCallback packCallback = new MsgPackCallback(msg.getTsKvListCount(), callback); - for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { + for (TsKvListProto tsKv : msg.getTsKvListList()) { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("deviceName", sessionInfo.getDeviceName()); metaData.putValue("deviceType", sessionInfo.getDeviceType()); metaData.putValue("ts", tsKv.getTs() + ""); JsonObject json = JsonUtils.getJsonObject(tsKv.getKvList()); - RuleChainId ruleChainId = resolveRuleChainId(sessionInfo); - TbMsg tbMsg = TbMsg.newMsg(ServiceQueue.MAIN, SessionMsgType.POST_TELEMETRY_REQUEST.name(), - deviceId, metaData, gson.toJson(json), ruleChainId, null); - sendToRuleEngine(tenantId, tbMsg, packCallback); + sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, SessionMsgType.POST_TELEMETRY_REQUEST, packCallback); } } } @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback callback) { + public void process(SessionInfoProto sessionInfo, PostAttributeMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback, msg.getKvCount(), TELEMETRY)) { reportActivityInternal(sessionInfo); TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); @@ -398,15 +419,12 @@ public class DefaultTransportService implements TransportService { metaData.putValue("deviceName", sessionInfo.getDeviceName()); metaData.putValue("deviceType", sessionInfo.getDeviceType()); metaData.putValue("notifyDevice", "false"); - RuleChainId ruleChainId = resolveRuleChainId(sessionInfo); - TbMsg tbMsg = TbMsg.newMsg(ServiceQueue.MAIN, SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), - deviceId, metaData, gson.toJson(json), ruleChainId, null); - sendToRuleEngine(tenantId, tbMsg, new TransportTbQueueCallback(callback)); + sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, SessionMsgType.POST_ATTRIBUTES_REQUEST, new TransportTbQueueCallback(callback)); } } @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.GetAttributeRequestMsg msg, TransportServiceCallback callback) { + public void process(SessionInfoProto sessionInfo, GetAttributeRequestMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) @@ -415,7 +433,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback callback) { + public void process(SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); sessionMetaData.setSubscribedToAttributes(!msg.getUnsubscribe()); @@ -425,7 +443,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscribeToRPCMsg msg, TransportServiceCallback callback) { + public void process(SessionInfoProto sessionInfo, SubscribeToRPCMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); sessionMetaData.setSubscribedToRPC(!msg.getUnsubscribe()); @@ -435,7 +453,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDeviceRpcResponseMsg msg, TransportServiceCallback callback) { + public void process(SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) @@ -450,13 +468,13 @@ public class DefaultTransportService implements TransportService { if (md != null) { SessionMsgListener listener = md.getListener(); transportCallbackExecutor.submit(() -> { - TransportProtos.ToServerRpcResponseMsg responseMsg = - TransportProtos.ToServerRpcResponseMsg.newBuilder() + ToServerRpcResponseMsg responseMsg = + ToServerRpcResponseMsg.newBuilder() .setRequestId(data.getRequestId()) .setError("timeout").build(); listener.onToServerRpcResponse(responseMsg); }); - if (md.getSessionType() == TransportProtos.SessionType.SYNC) { + if (md.getSessionType() == SessionType.SYNC) { deregisterSession(md.getSessionInfo()); } } else { @@ -466,7 +484,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToServerRpcRequestMsg msg, TransportServiceCallback callback) { + public void process(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); UUID sessionId = toSessionId(sessionInfo); @@ -482,10 +500,9 @@ public class DefaultTransportService implements TransportService { metaData.putValue("requestId", Integer.toString(msg.getRequestId())); metaData.putValue("serviceId", serviceInfoProvider.getServiceId()); metaData.putValue("sessionId", sessionId.toString()); - RuleChainId ruleChainId = resolveRuleChainId(sessionInfo); - TbMsg tbMsg = TbMsg.newMsg(ServiceQueue.MAIN, SessionMsgType.TO_SERVER_RPC_REQUEST.name(), - deviceId, metaData, gson.toJson(json), ruleChainId, null); - sendToRuleEngine(tenantId, tbMsg, new TransportTbQueueCallback(callback)); + + sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, + SessionMsgType.TO_SERVER_RPC_REQUEST, new TransportTbQueueCallback(callback)); String requestId = sessionId + "-" + msg.getRequestId(); toServerRpcPendingMap.put(requestId, new RpcRequestMetadata(sessionId, msg.getRequestId())); schedulerExecutor.schedule(() -> processTimeout(requestId), clientSideRpcTimeout, TimeUnit.MILLISECONDS); @@ -493,7 +510,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ClaimDeviceMsg msg, TransportServiceCallback callback) { + public void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) @@ -502,11 +519,11 @@ public class DefaultTransportService implements TransportService { } @Override - public void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { + public void reportActivity(SessionInfoProto sessionInfo) { reportActivityInternal(sessionInfo); } - private SessionMetaData reportActivityInternal(TransportProtos.SessionInfoProto sessionInfo) { + private SessionMetaData reportActivityInternal(SessionInfoProto sessionInfo) { UUID sessionId = toSessionId(sessionInfo); SessionMetaData sessionMetaData = sessions.get(sessionId); if (sessionMetaData != null) { @@ -519,7 +536,7 @@ public class DefaultTransportService implements TransportService { long expTime = System.currentTimeMillis() - sessionInactivityTimeout; sessions.forEach((uuid, sessionMD) -> { long lastActivityTime = sessionMD.getLastActivityTime(); - TransportProtos.SessionInfoProto sessionInfo = sessionMD.getSessionInfo(); + SessionInfoProto sessionInfo = sessionMD.getSessionInfo(); if (sessionInfo.getGwSessionIdMSB() > 0 && sessionInfo.getGwSessionIdLSB() > 0) { SessionMetaData gwMetaData = sessions.get(new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB())); @@ -531,13 +548,13 @@ public class DefaultTransportService implements TransportService { if (log.isDebugEnabled()) { log.debug("[{}] Session has expired due to last activity time: {}", toSessionId(sessionInfo), lastActivityTime); } - process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null); + process(sessionInfo, getSessionEventMsg(SessionEvent.CLOSED), null); sessions.remove(uuid); - sessionMD.getListener().onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto.getDefaultInstance()); + sessionMD.getListener().onRemoteSessionCloseCommand(SessionCloseNotificationProto.getDefaultInstance()); } else { if (lastActivityTime > sessionMD.getLastReportedActivityTime()) { final long lastActivityTimeFinal = lastActivityTime; - process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() + process(sessionInfo, SubscriptionInfoProto.newBuilder() .setAttributeSubscription(sessionMD.isSubscribedToAttributes()) .setRpcSubscription(sessionMD.isSubscribedToRPC()) .setLastActivityTime(lastActivityTime).build(), new TransportServiceCallback() { @@ -557,12 +574,12 @@ public class DefaultTransportService implements TransportService { } @Override - public void registerSyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout) { - SessionMetaData currentSession = new SessionMetaData(sessionInfo, TransportProtos.SessionType.SYNC, listener); + public void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout) { + SessionMetaData currentSession = new SessionMetaData(sessionInfo, SessionType.SYNC, listener); sessions.putIfAbsent(toSessionId(sessionInfo), currentSession); ScheduledFuture executorFuture = schedulerExecutor.schedule(() -> { - listener.onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto.getDefaultInstance()); + listener.onRemoteSessionCloseCommand(SessionCloseNotificationProto.getDefaultInstance()); deregisterSession(sessionInfo); }, timeout, TimeUnit.MILLISECONDS); @@ -570,7 +587,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void deregisterSession(TransportProtos.SessionInfoProto sessionInfo) { + public void deregisterSession(SessionInfoProto sessionInfo) { SessionMetaData currentSession = sessions.get(toSessionId(sessionInfo)); if (currentSession != null && currentSession.hasScheduledFuture()) { log.debug("Stopping scheduler to avoid resending response if request has been ack."); @@ -583,12 +600,12 @@ public class DefaultTransportService implements TransportService { private TransportRateLimitType[] TELEMETRY = TransportRateLimitType.values(); @Override - public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback) { + public boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback) { return checkLimits(sessionInfo, msg, callback, 0, DEFAULT); } @Override - public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints, TransportRateLimitType... limits) { + public boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints, TransportRateLimitType... limits) { if (log.isTraceEnabled()) { log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg); } @@ -609,7 +626,7 @@ public class DefaultTransportService implements TransportService { } } - protected void processToTransportMsg(TransportProtos.ToTransportMsg toSessionMsg) { + protected void processToTransportMsg(ToTransportMsg toSessionMsg) { UUID sessionId = new UUID(toSessionMsg.getSessionIdMSB(), toSessionMsg.getSessionIdLSB()); SessionMetaData md = sessions.get(sessionId); if (md != null) { @@ -633,12 +650,12 @@ public class DefaultTransportService implements TransportService { listener.onToServerRpcResponse(toSessionMsg.getToServerResponse()); } }); - if (md.getSessionType() == TransportProtos.SessionType.SYNC) { + if (md.getSessionType() == SessionType.SYNC) { deregisterSession(md.getSessionInfo()); } } else { if (toSessionMsg.hasEntityUpdateMsg()) { - TransportProtos.EntityUpdateMsg msg = toSessionMsg.getEntityUpdateMsg(); + EntityUpdateMsg msg = toSessionMsg.getEntityUpdateMsg(); EntityType entityType = EntityType.valueOf(msg.getEntityType()); if (EntityType.DEVICE_PROFILE.equals(entityType)) { DeviceProfile deviceProfile = deviceProfileCache.put(msg.getData()); @@ -659,7 +676,7 @@ public class DefaultTransportService implements TransportService { } } } else if (toSessionMsg.hasEntityDeleteMsg()) { - TransportProtos.EntityDeleteMsg msg = toSessionMsg.getEntityDeleteMsg(); + EntityDeleteMsg msg = toSessionMsg.getEntityDeleteMsg(); EntityType entityType = EntityType.valueOf(msg.getEntityType()); UUID entityUuid = new UUID(msg.getEntityIdMSB(), msg.getEntityIdLSB()); if (EntityType.DEVICE_PROFILE.equals(entityType)) { @@ -690,29 +707,29 @@ public class DefaultTransportService implements TransportService { }); } - protected UUID toSessionId(TransportProtos.SessionInfoProto sessionInfo) { + protected UUID toSessionId(SessionInfoProto sessionInfo) { return new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()); } - protected UUID getRoutingKey(TransportProtos.SessionInfoProto sessionInfo) { + protected UUID getRoutingKey(SessionInfoProto sessionInfo) { return new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()); } - protected TenantId getTenantId(TransportProtos.SessionInfoProto sessionInfo) { + protected TenantId getTenantId(SessionInfoProto sessionInfo) { return new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); } - protected DeviceId getDeviceId(TransportProtos.SessionInfoProto sessionInfo) { + protected DeviceId getDeviceId(SessionInfoProto sessionInfo) { return new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); } - public static TransportProtos.SessionEventMsg getSessionEventMsg(TransportProtos.SessionEvent event) { - return TransportProtos.SessionEventMsg.newBuilder() - .setSessionType(TransportProtos.SessionType.ASYNC) + public static SessionEventMsg getSessionEventMsg(SessionEvent event) { + return SessionEventMsg.newBuilder() + .setSessionType(SessionType.ASYNC) .setEvent(event).build(); } - protected void sendToDeviceActor(TransportProtos.SessionInfoProto sessionInfo, TransportToDeviceActorMsg toDeviceActorMsg, TransportServiceCallback callback) { + protected void sendToDeviceActor(SessionInfoProto sessionInfo, TransportToDeviceActorMsg toDeviceActorMsg, TransportServiceCallback callback) { TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, getTenantId(sessionInfo), getDeviceId(sessionInfo)); if (log.isTraceEnabled()) { log.trace("[{}][{}] Pushing to topic {} message {}", getTenantId(sessionInfo), getDeviceId(sessionInfo), tpi.getFullTopicName(), toDeviceActorMsg); @@ -740,17 +757,25 @@ public class DefaultTransportService implements TransportService { ruleEngineMsgProducer.send(tpi, new TbProtoQueueMsg<>(tbMsg.getId(), msg), wrappedCallback); } - private RuleChainId resolveRuleChainId(TransportProtos.SessionInfoProto sessionInfo) { + protected void sendToRuleEngine(TenantId tenantId, DeviceId deviceId, SessionInfoProto sessionInfo, JsonObject json, + TbMsgMetaData metaData, SessionMsgType sessionMsgType, TbQueueCallback callback) { DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); DeviceProfile deviceProfile = deviceProfileCache.get(deviceProfileId); RuleChainId ruleChainId; + String queueName; + if (deviceProfile == null) { log.warn("[{}] Device profile is null!", deviceProfileId); ruleChainId = null; + queueName = ServiceQueue.MAIN; } else { ruleChainId = deviceProfile.getDefaultRuleChainId(); + String defaultQueueName = deviceProfile.getDefaultQueueName(); + queueName = defaultQueueName != null ? defaultQueueName : ServiceQueue.MAIN; } - return ruleChainId; + + TbMsg tbMsg = TbMsg.newMsg(queueName, sessionMsgType.name(), deviceId, metaData, gson.toJson(json), ruleChainId, null); + sendToRuleEngine(tenantId, tbMsg, callback); } private class TransportTbQueueCallback implements TbQueueCallback { From a0d39a3e46c968642b7e99f8fcf3ee19683b9564 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 28 Oct 2020 16:56:13 +0200 Subject: [PATCH 268/463] revert import changes from DefaultTransportService --- .../common/transport/service/DefaultTransportService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index aabb8c927e..b1ae211ed7 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -757,8 +757,8 @@ public class DefaultTransportService implements TransportService { ruleEngineMsgProducer.send(tpi, new TbProtoQueueMsg<>(tbMsg.getId(), msg), wrappedCallback); } - protected void sendToRuleEngine(TenantId tenantId, DeviceId deviceId, SessionInfoProto sessionInfo, JsonObject json, - TbMsgMetaData metaData, SessionMsgType sessionMsgType, TbQueueCallback callback) { + protected void sendToRuleEngine(TenantId tenantId, DeviceId deviceId, TransportProtos.SessionInfoProto sessionInfo, JsonObject json, + TbMsgMetaData metaData, SessionMsgType sessionMsgType, TbQueueCallback callback) { DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); DeviceProfile deviceProfile = deviceProfileCache.get(deviceProfileId); RuleChainId ruleChainId; From 2211cff1e9e35e3a98a5d4fa4cf52e1a0c6193c4 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 28 Oct 2020 17:00:31 +0200 Subject: [PATCH 269/463] revert DefaultTransportService --- .../service/DefaultTransportService.java | 132 +++++++----------- 1 file changed, 53 insertions(+), 79 deletions(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index b1ae211ed7..089b8aaf53 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -57,41 +57,15 @@ import org.thingsboard.server.common.transport.limits.TransportRateLimitType; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.common.transport.util.JsonUtils; -import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; -import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; -import org.thingsboard.server.gen.transport.TransportProtos.EntityDeleteMsg; -import org.thingsboard.server.gen.transport.TransportProtos.EntityUpdateMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; -import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto; -import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; -import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg; -import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; -import org.thingsboard.server.gen.transport.TransportProtos.SessionType; -import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg; -import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg; -import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; -import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueMsgMetadata; @@ -261,14 +235,14 @@ public class DefaultTransportService implements TransportService { } @Override - public void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener) { - sessions.putIfAbsent(toSessionId(sessionInfo), new SessionMetaData(sessionInfo, SessionType.ASYNC, listener)); + public void registerAsyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener) { + sessions.putIfAbsent(toSessionId(sessionInfo), new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listener)); } @Override - public GetEntityProfileResponseMsg getRoutingInfo(GetEntityProfileRequestMsg msg) { - TbProtoQueueMsg protoMsg = - new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setEntityProfileRequestMsg(msg).build()); + public TransportProtos.GetEntityProfileResponseMsg getRoutingInfo(TransportProtos.GetEntityProfileRequestMsg msg) { + TbProtoQueueMsg protoMsg = + new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setEntityProfileRequestMsg(msg).build()); try { TbProtoQueueMsg response = transportApiRequestTemplate.send(protoMsg).get(); return response.getValue().getEntityProfileResponseMsg(); @@ -278,7 +252,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(DeviceTransportType transportType, ValidateDeviceTokenRequestMsg msg, + public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceTokenRequestMsg msg, TransportServiceCallback callback) { log.trace("Processing msg: {}", msg); TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), @@ -287,7 +261,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(DeviceTransportType transportType, ValidateBasicMqttCredRequestMsg msg, + public void process(DeviceTransportType transportType, TransportProtos.ValidateBasicMqttCredRequestMsg msg, TransportServiceCallback callback) { log.trace("Processing msg: {}", msg); TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), @@ -296,7 +270,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(DeviceTransportType transportType, ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback callback) { + public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback callback) { log.trace("Processing msg: {}", msg); TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateX509CertRequestMsg(msg).build()); doProcess(transportType, protoMsg, callback); @@ -305,7 +279,7 @@ public class DefaultTransportService implements TransportService { private void doProcess(DeviceTransportType transportType, TbProtoQueueMsg protoMsg, TransportServiceCallback callback) { ListenableFuture response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> { - ValidateDeviceCredentialsResponseMsg msg = tmp.getValue().getValidateCredResponseMsg(); + TransportProtos.ValidateDeviceCredentialsResponseMsg msg = tmp.getValue().getValidateCredResponseMsg(); ValidateDeviceCredentialsResponse.ValidateDeviceCredentialsResponseBuilder result = ValidateDeviceCredentialsResponse.builder(); if (msg.hasDeviceInfo()) { result.credentials(msg.getCredentialsBody()); @@ -328,11 +302,11 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(GetOrCreateDeviceFromGatewayRequestMsg requestMsg, TransportServiceCallback callback) { + public void process(TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg requestMsg, TransportServiceCallback callback) { TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setGetOrCreateDeviceRequestMsg(requestMsg).build()); log.trace("Processing msg: {}", requestMsg); ListenableFuture response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> { - GetOrCreateDeviceFromGatewayResponseMsg msg = tmp.getValue().getGetOrCreateDeviceResponseMsg(); + TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg msg = tmp.getValue().getGetOrCreateDeviceResponseMsg(); GetOrCreateDeviceFromGatewayResponse.GetOrCreateDeviceFromGatewayResponseBuilder result = GetOrCreateDeviceFromGatewayResponse.builder(); if (msg.hasDeviceInfo()) { TransportDeviceInfo tdi = getTransportDeviceInfo(msg.getDeviceInfo()); @@ -347,7 +321,7 @@ public class DefaultTransportService implements TransportService { AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor); } - private TransportDeviceInfo getTransportDeviceInfo(DeviceInfoProto di) { + private TransportDeviceInfo getTransportDeviceInfo(TransportProtos.DeviceInfoProto di) { TransportDeviceInfo tdi = new TransportDeviceInfo(); tdi.setTenantId(new TenantId(new UUID(di.getTenantIdMSB(), di.getTenantIdLSB()))); tdi.setDeviceId(new DeviceId(new UUID(di.getDeviceIdMSB(), di.getDeviceIdLSB()))); @@ -369,7 +343,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(SessionInfoProto sessionInfo, SubscriptionInfoProto msg, TransportServiceCallback callback) { + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscriptionInfoProto msg, TransportServiceCallback callback) { if (log.isTraceEnabled()) { log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg); } @@ -378,7 +352,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback callback) { + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SessionEventMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) @@ -387,9 +361,9 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback callback) { + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback callback) { int dataPoints = 0; - for (TsKvListProto tsKv : msg.getTsKvListList()) { + for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { dataPoints += tsKv.getKvCount(); } if (checkLimits(sessionInfo, msg, callback, dataPoints, TELEMETRY)) { @@ -397,19 +371,20 @@ public class DefaultTransportService implements TransportService { TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); MsgPackCallback packCallback = new MsgPackCallback(msg.getTsKvListCount(), callback); - for (TsKvListProto tsKv : msg.getTsKvListList()) { + for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("deviceName", sessionInfo.getDeviceName()); metaData.putValue("deviceType", sessionInfo.getDeviceType()); metaData.putValue("ts", tsKv.getTs() + ""); JsonObject json = JsonUtils.getJsonObject(tsKv.getKvList()); sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, SessionMsgType.POST_TELEMETRY_REQUEST, packCallback); + } } } @Override - public void process(SessionInfoProto sessionInfo, PostAttributeMsg msg, TransportServiceCallback callback) { + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback, msg.getKvCount(), TELEMETRY)) { reportActivityInternal(sessionInfo); TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); @@ -424,7 +399,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(SessionInfoProto sessionInfo, GetAttributeRequestMsg msg, TransportServiceCallback callback) { + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.GetAttributeRequestMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) @@ -433,7 +408,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback callback) { + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); sessionMetaData.setSubscribedToAttributes(!msg.getUnsubscribe()); @@ -443,7 +418,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(SessionInfoProto sessionInfo, SubscribeToRPCMsg msg, TransportServiceCallback callback) { + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscribeToRPCMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); sessionMetaData.setSubscribedToRPC(!msg.getUnsubscribe()); @@ -453,7 +428,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg msg, TransportServiceCallback callback) { + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDeviceRpcResponseMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) @@ -468,13 +443,13 @@ public class DefaultTransportService implements TransportService { if (md != null) { SessionMsgListener listener = md.getListener(); transportCallbackExecutor.submit(() -> { - ToServerRpcResponseMsg responseMsg = - ToServerRpcResponseMsg.newBuilder() + TransportProtos.ToServerRpcResponseMsg responseMsg = + TransportProtos.ToServerRpcResponseMsg.newBuilder() .setRequestId(data.getRequestId()) .setError("timeout").build(); listener.onToServerRpcResponse(responseMsg); }); - if (md.getSessionType() == SessionType.SYNC) { + if (md.getSessionType() == TransportProtos.SessionType.SYNC) { deregisterSession(md.getSessionInfo()); } } else { @@ -484,7 +459,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback callback) { + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToServerRpcRequestMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); UUID sessionId = toSessionId(sessionInfo); @@ -500,7 +475,6 @@ public class DefaultTransportService implements TransportService { metaData.putValue("requestId", Integer.toString(msg.getRequestId())); metaData.putValue("serviceId", serviceInfoProvider.getServiceId()); metaData.putValue("sessionId", sessionId.toString()); - sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, SessionMsgType.TO_SERVER_RPC_REQUEST, new TransportTbQueueCallback(callback)); String requestId = sessionId + "-" + msg.getRequestId(); @@ -510,7 +484,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback callback) { + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ClaimDeviceMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) @@ -519,11 +493,11 @@ public class DefaultTransportService implements TransportService { } @Override - public void reportActivity(SessionInfoProto sessionInfo) { + public void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { reportActivityInternal(sessionInfo); } - private SessionMetaData reportActivityInternal(SessionInfoProto sessionInfo) { + private SessionMetaData reportActivityInternal(TransportProtos.SessionInfoProto sessionInfo) { UUID sessionId = toSessionId(sessionInfo); SessionMetaData sessionMetaData = sessions.get(sessionId); if (sessionMetaData != null) { @@ -536,7 +510,7 @@ public class DefaultTransportService implements TransportService { long expTime = System.currentTimeMillis() - sessionInactivityTimeout; sessions.forEach((uuid, sessionMD) -> { long lastActivityTime = sessionMD.getLastActivityTime(); - SessionInfoProto sessionInfo = sessionMD.getSessionInfo(); + TransportProtos.SessionInfoProto sessionInfo = sessionMD.getSessionInfo(); if (sessionInfo.getGwSessionIdMSB() > 0 && sessionInfo.getGwSessionIdLSB() > 0) { SessionMetaData gwMetaData = sessions.get(new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB())); @@ -548,13 +522,13 @@ public class DefaultTransportService implements TransportService { if (log.isDebugEnabled()) { log.debug("[{}] Session has expired due to last activity time: {}", toSessionId(sessionInfo), lastActivityTime); } - process(sessionInfo, getSessionEventMsg(SessionEvent.CLOSED), null); + process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null); sessions.remove(uuid); - sessionMD.getListener().onRemoteSessionCloseCommand(SessionCloseNotificationProto.getDefaultInstance()); + sessionMD.getListener().onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto.getDefaultInstance()); } else { if (lastActivityTime > sessionMD.getLastReportedActivityTime()) { final long lastActivityTimeFinal = lastActivityTime; - process(sessionInfo, SubscriptionInfoProto.newBuilder() + process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() .setAttributeSubscription(sessionMD.isSubscribedToAttributes()) .setRpcSubscription(sessionMD.isSubscribedToRPC()) .setLastActivityTime(lastActivityTime).build(), new TransportServiceCallback() { @@ -574,12 +548,12 @@ public class DefaultTransportService implements TransportService { } @Override - public void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout) { - SessionMetaData currentSession = new SessionMetaData(sessionInfo, SessionType.SYNC, listener); + public void registerSyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout) { + SessionMetaData currentSession = new SessionMetaData(sessionInfo, TransportProtos.SessionType.SYNC, listener); sessions.putIfAbsent(toSessionId(sessionInfo), currentSession); ScheduledFuture executorFuture = schedulerExecutor.schedule(() -> { - listener.onRemoteSessionCloseCommand(SessionCloseNotificationProto.getDefaultInstance()); + listener.onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto.getDefaultInstance()); deregisterSession(sessionInfo); }, timeout, TimeUnit.MILLISECONDS); @@ -587,7 +561,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void deregisterSession(SessionInfoProto sessionInfo) { + public void deregisterSession(TransportProtos.SessionInfoProto sessionInfo) { SessionMetaData currentSession = sessions.get(toSessionId(sessionInfo)); if (currentSession != null && currentSession.hasScheduledFuture()) { log.debug("Stopping scheduler to avoid resending response if request has been ack."); @@ -600,12 +574,12 @@ public class DefaultTransportService implements TransportService { private TransportRateLimitType[] TELEMETRY = TransportRateLimitType.values(); @Override - public boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback) { + public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback) { return checkLimits(sessionInfo, msg, callback, 0, DEFAULT); } @Override - public boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints, TransportRateLimitType... limits) { + public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints, TransportRateLimitType... limits) { if (log.isTraceEnabled()) { log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg); } @@ -626,7 +600,7 @@ public class DefaultTransportService implements TransportService { } } - protected void processToTransportMsg(ToTransportMsg toSessionMsg) { + protected void processToTransportMsg(TransportProtos.ToTransportMsg toSessionMsg) { UUID sessionId = new UUID(toSessionMsg.getSessionIdMSB(), toSessionMsg.getSessionIdLSB()); SessionMetaData md = sessions.get(sessionId); if (md != null) { @@ -650,12 +624,12 @@ public class DefaultTransportService implements TransportService { listener.onToServerRpcResponse(toSessionMsg.getToServerResponse()); } }); - if (md.getSessionType() == SessionType.SYNC) { + if (md.getSessionType() == TransportProtos.SessionType.SYNC) { deregisterSession(md.getSessionInfo()); } } else { if (toSessionMsg.hasEntityUpdateMsg()) { - EntityUpdateMsg msg = toSessionMsg.getEntityUpdateMsg(); + TransportProtos.EntityUpdateMsg msg = toSessionMsg.getEntityUpdateMsg(); EntityType entityType = EntityType.valueOf(msg.getEntityType()); if (EntityType.DEVICE_PROFILE.equals(entityType)) { DeviceProfile deviceProfile = deviceProfileCache.put(msg.getData()); @@ -676,7 +650,7 @@ public class DefaultTransportService implements TransportService { } } } else if (toSessionMsg.hasEntityDeleteMsg()) { - EntityDeleteMsg msg = toSessionMsg.getEntityDeleteMsg(); + TransportProtos.EntityDeleteMsg msg = toSessionMsg.getEntityDeleteMsg(); EntityType entityType = EntityType.valueOf(msg.getEntityType()); UUID entityUuid = new UUID(msg.getEntityIdMSB(), msg.getEntityIdLSB()); if (EntityType.DEVICE_PROFILE.equals(entityType)) { @@ -707,29 +681,29 @@ public class DefaultTransportService implements TransportService { }); } - protected UUID toSessionId(SessionInfoProto sessionInfo) { + protected UUID toSessionId(TransportProtos.SessionInfoProto sessionInfo) { return new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()); } - protected UUID getRoutingKey(SessionInfoProto sessionInfo) { + protected UUID getRoutingKey(TransportProtos.SessionInfoProto sessionInfo) { return new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()); } - protected TenantId getTenantId(SessionInfoProto sessionInfo) { + protected TenantId getTenantId(TransportProtos.SessionInfoProto sessionInfo) { return new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); } - protected DeviceId getDeviceId(SessionInfoProto sessionInfo) { + protected DeviceId getDeviceId(TransportProtos.SessionInfoProto sessionInfo) { return new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); } - public static SessionEventMsg getSessionEventMsg(SessionEvent event) { - return SessionEventMsg.newBuilder() - .setSessionType(SessionType.ASYNC) + public static TransportProtos.SessionEventMsg getSessionEventMsg(TransportProtos.SessionEvent event) { + return TransportProtos.SessionEventMsg.newBuilder() + .setSessionType(TransportProtos.SessionType.ASYNC) .setEvent(event).build(); } - protected void sendToDeviceActor(SessionInfoProto sessionInfo, TransportToDeviceActorMsg toDeviceActorMsg, TransportServiceCallback callback) { + protected void sendToDeviceActor(TransportProtos.SessionInfoProto sessionInfo, TransportToDeviceActorMsg toDeviceActorMsg, TransportServiceCallback callback) { TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, getTenantId(sessionInfo), getDeviceId(sessionInfo)); if (log.isTraceEnabled()) { log.trace("[{}][{}] Pushing to topic {} message {}", getTenantId(sessionInfo), getDeviceId(sessionInfo), tpi.getFullTopicName(), toDeviceActorMsg); From 6cc8eada320b9fb716da67f51939d3e94c024852 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 28 Oct 2020 11:20:33 +0200 Subject: [PATCH 270/463] base url improvements --- .../modules/home/pages/admin/general-settings.component.html | 4 ++++ ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 ++ 2 files changed, 6 insertions(+) diff --git a/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html index 1bd8c60cc2..1a5c4da887 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html @@ -35,6 +35,10 @@ {{ 'admin.base-url-required' | translate }}
+ + {{ 'admin.prohibit-different-url' | translate }} + +
admin.prohibit-different-url-hint
\n
\n\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),I=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.assignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),k=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.clearAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],alarmType:[e?e.alarmType:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-clear-alarm-config",template:'
\n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),N=function(e){function r(t,r,n,o){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.nodeScriptTestService=n,i.translate=o,i.alarmSeverities=Object.keys(a.AlarmSeverity),i.alarmSeverityTranslationMap=a.alarmSeverityTranslations,i.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],i}return y(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]})},r.prototype.validatorTriggers=function(){return["useMessageAlarmData"]},r.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([i.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([i.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},r.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.removeKey=function(e,t){var r=this.createAlarmConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.createAlarmConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),V=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.createRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[i.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["entityType"]},r.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([i.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-create-relation-config",template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),E=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgDelayConfigForm},r.prototype.onConfigurationSet=function(e){this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(1e5)]]})},r.prototype.validatorTriggers=function(){return["useMetadataPeriodInSecondsPatterns"]},r.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([i.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([i.Validators.required,i.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-delay-config",template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n \n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),A=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.deleteRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["deleteForSingleEntity","entityType"]},r.prototype.updateValidators=function(e){var t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-delete-relation-config",template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),L=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.generatorConfigForm},r.prototype.onConfigurationSet=function(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[i.Validators.required,i.Validators.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[i.Validators.required,i.Validators.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.prepareInputConfig=function(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e},r.prototype.prepareOutputConfig=function(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e},r.prototype.testScript=function(){var e=this,t=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId).subscribe((function(t){t&&e.generatorConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-generator-config",template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent);!function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR"}(v||(v={}));var M,P=new Map([[v.CUSTOMER,"tb.rulenode.originator-customer"],[v.TENANT,"tb.rulenode.originator-tenant"],[v.RELATED,"tb.rulenode.originator-related"],[v.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(M||(M={}));var R,w=new Map([[M.CIRCLE,"tb.rulenode.perimeter-circle"],[M.POLYGON,"tb.rulenode.perimeter-polygon"]]);!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(R||(R={}));var O,D=new Map([[R.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[R.SECONDS,"tb.rulenode.time-unit-seconds"],[R.MINUTES,"tb.rulenode.time-unit-minutes"],[R.HOURS,"tb.rulenode.time-unit-hours"],[R.DAYS,"tb.rulenode.time-unit-days"]]);!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(O||(O={}));var K,B=new Map([[O.METER,"tb.rulenode.range-unit-meter"],[O.KILOMETER,"tb.rulenode.range-unit-kilometer"],[O.FOOT,"tb.rulenode.range-unit-foot"],[O.MILE,"tb.rulenode.range-unit-mile"],[O.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);!function(e){e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(K||(K={}));var U,j,H,G=new Map([[K.TITLE,"tb.rulenode.entity-details-title"],[K.COUNTRY,"tb.rulenode.entity-details-country"],[K.STATE,"tb.rulenode.entity-details-state"],[K.ZIP,"tb.rulenode.entity-details-zip"],[K.ADDRESS,"tb.rulenode.entity-details-address"],[K.ADDRESS2,"tb.rulenode.entity-details-address2"],[K.PHONE,"tb.rulenode.entity-details-phone"],[K.EMAIL,"tb.rulenode.entity-details-email"],[K.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(U||(U={})),function(e){e.ASC="ASC",e.DESC="DESC"}(j||(j={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(H||(H={}));var z,$=new Map([[H.STANDARD,"tb.rulenode.sqs-queue-standard"],[H.FIFO,"tb.rulenode.sqs-queue-fifo"]]),Q=["anonymous","basic","cert.PEM"],_=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),W=["sas","cert.PEM"],J=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(z||(z={}));var Y=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],Z=new Map([["US-ASCII","tb.rulenode.charset-us-ascii"],["ISO-8859-1","tb.rulenode.charset-iso-8859-1"],["UTF-8","tb.rulenode.charset-utf-8"],["UTF-16BE","tb.rulenode.charset-utf-16be"],["UTF-16LE","tb.rulenode.charset-utf-16le"],["UTF-16","tb.rulenode.charset-utf-16"]]),X=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.geoActionConfigForm},r.prototype.onConfigurationSet=function(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]],minInsideDuration:[e?e.minInsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[i.Validators.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterType").setValidators([]):this.geoActionConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([])):(this.geoActionConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoActionConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoActionConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoActionConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgCountConfigForm},r.prototype.onConfigurationSet=function(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[i.Validators.required,i.Validators.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-count-config",template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcReplyConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.saveToCustomTableConfigForm},r.prototype.onConfigurationSet=function(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[i.Validators.required]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-custom-table-config",template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ne=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.translate=r,o.injector=n,o.fb=a,o.propagateChange=null,o.valueChangeSubscription=null,o}var a;return y(r,e),a=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){this.ngControl=this.injector.get(i.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},r.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t,r,n=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var o=C(Object.keys(e)),l=o.next();!l.done;l=o.next()){var s=l.value;Object.prototype.hasOwnProperty.call(e,s)&&a.push(this.fb.group({key:[s,[i.Validators.required]],value:[e[s],[i.Validators.required]]}))}}catch(e){t={error:e}}finally{try{l&&!l.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){n.updateModel()}))},r.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},r.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[i.Validators.required]],value:["",[i.Validators.required]]}))},r.prototype.validate=function(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}},r.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:t.Injector},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",String)],r.prototype,"requiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyRequiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valRequiredText",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=a=b([t.Component({selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return a})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return a})),multi:!0}],styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}),h("design:paramtypes",[o.Store,n.TranslateService,t.Injector,i.FormBuilder])],r)}(a.PageComponent),ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[i.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-device-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),ie=function(e){function r(t,r,n,o){var i,l,m=e.call(this,t)||this;m.store=t,m.translate=r,m.truncate=n,m.fb=o,m.placeholder="tb.rulenode.message-type",m.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],m.messageTypes=[],m.messageTypesList=[],m.searchText="",m.propagateChange=function(e){},m.messageTypeConfigForm=m.fb.group({messageType:[null]});try{for(var u=C(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;m.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){i={error:e}}finally{try{d&&!d.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}return m}var l;return y(r,e),l=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.ngOnInit=function(){var e=this;this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(t){return e.fetchMessageTypes(t)})),f.share())},r.prototype.ngAfterViewInit=function(){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.forEach((function(e){var r=t.messageTypesList.find((function(t){return t.value===e}));r?t.messageTypes.push({name:r.name,value:r.value}):t.messageTypes.push({name:e,value:e})}))},r.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},r.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},r.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},r.prototype.add=function(e){this.transformMessageType(e.value)},r.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return c.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return c.of(this.messageTypesList)},r.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,r=e.trim(),n=this.messageTypesList.find((function(e){return e.name===r}));(t=n?{name:n.name,value:n.value}:{name:r,value:r})&&this.addMessageType(t)}this.clear("")},r.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},r.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},r.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},r.prototype.onFocus=function(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},r.prototype.updateModel=function(){var e=this.messageTypes.map((function(e){return e.value}));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:a.TruncatePipe},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),b([t.Input(),h("design:type",String)],r.prototype,"label",void 0),b([t.Input(),h("design:type",Object)],r.prototype,"placeholder",void 0),b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.ViewChild("chipList",{static:!1}),h("design:type",d.MatChipList)],r.prototype,"chipList",void 0),b([t.ViewChild("messageTypeAutocomplete",{static:!1}),h("design:type",p.MatAutocomplete)],r.prototype,"matAutocomplete",void 0),b([t.ViewChild("messageTypeInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"messageTypeInput",void 0),r=l=b([t.Component({selector:"tb-message-types-config",template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n \n {{ \'tb.rulenode.message-types-required\' | translate }}\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return l})),multi:!0}]}),h("design:paramtypes",[o.Store,n.TranslateService,a.TruncatePipe,i.FormBuilder])],r)}(a.PageComponent),le=function(){function e(){}return e=b([t.NgModule({declarations:[ne,ae,oe,ie],imports:[r.CommonModule,a.SharedModule,m.HomeComponentsModule],exports:[ne,ae,oe,ie]})],e)}(),se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.unassignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-un-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.snsConfigForm},r.prototype.onConfigurationSet=function(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[i.Validators.required]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sns-config",template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.sqsQueueType=H,n.sqsQueueTypes=Object.keys(H),n.sqsQueueTypeTranslationsMap=$,n}return y(r,e),r.prototype.configForm=function(){return this.sqsConfigForm},r.prototype.onConfigurationSet=function(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[i.Validators.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[i.Validators.required]],delaySeconds:[e?e.delaySeconds:null,[i.Validators.min(0),i.Validators.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sqs-config",template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.pubSubConfigForm},r.prototype.onConfigurationSet=function(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[i.Validators.required]],topicName:[e?e.topicName:null,[i.Validators.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[i.Validators.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[i.Validators.required]],messageAttributes:[e?e.messageAttributes:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-pub-sub-config",template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.ackValues=["all","-1","0","1"],n.ToByteStandartCharsetTypesValues=Y,n.ToByteStandartCharsetTypeTranslationMap=Z,n}return y(r,e),r.prototype.configForm=function(){return this.kafkaConfigForm},r.prototype.onConfigurationSet=function(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],bootstrapServers:[e?e.bootstrapServers:null,[i.Validators.required]],retries:[e?e.retries:null,[i.Validators.min(0)]],batchSize:[e?e.batchSize:null,[i.Validators.min(0)]],linger:[e?e.linger:null,[i.Validators.min(0)]],bufferMemory:[e?e.bufferMemory:null,[i.Validators.min(0)]],acks:[e?e.acks:null,[i.Validators.required]],keySerializer:[e?e.keySerializer:null,[i.Validators.required]],valueSerializer:[e?e.valueSerializer:null,[i.Validators.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})},r.prototype.validatorTriggers=function(){return["addMetadataKeyValuesAsKafkaHeaders"]},r.prototype.updateValidators=function(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([i.Validators.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allMqttCredentialsTypes=Q,n.mqttCredentialsTypeTranslationsMap=_,n}return y(r,e),r.prototype.configForm=function(){return this.mqttConfigForm},r.prototype.onConfigurationSet=function(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],username:[e&&e.credentials?e.credentials.username:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;switch(t){case"anonymous":e.credentials={type:t};break;case"basic":e.credentials={type:t,username:e.credentials.username,password:e.credentials.password};break;case"cert.PEM":delete e.credentials.username}return e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.mqttConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("username").setValidators([]),t.get("password").setValidators([]),t.get("caCert").setValidators([]),t.get("caCertFileName").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"anonymous":break;case"basic":t.get("username").setValidators([i.Validators.required]),t.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("caCert").setValidators([i.Validators.required]),t.get("caCertFileName").setValidators([i.Validators.required]),t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("username").updateValueAndValidity({emitEvent:e}),t.get("password").updateValueAndValidity({emitEvent:e}),t.get("caCert").updateValueAndValidity({emitEvent:e}),t.get("caCertFileName").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-mqtt-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n \n tb.rulenode.credentials\n \n {{ mqttCredentialsTypeTranslationsMap.get(mqttConfigForm.get(\'credentials\').get(\'type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ mqttCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return y(r,e),r.prototype.configForm=function(){return this.rabbitMqConfigForm},r.prototype.onConfigurationSet=function(e){this.rabbitMqConfigForm=this.fb.group({exchangeNamePattern:[e?e.exchangeNamePattern:null,[]],routingKeyPattern:[e?e.routingKeyPattern:null,[]],messageProperties:[e?e.messageProperties:null,[]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],virtualHost:[e?e.virtualHost:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]],automaticRecoveryEnabled:[!!e&&e.automaticRecoveryEnabled,[]],connectionTimeout:[e?e.connectionTimeout:null,[i.Validators.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[i.Validators.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rabbit-mq-config",template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.proxySchemes=["http","https"],n.httpRequestTypes=Object.keys(z),n}return y(r,e),r.prototype.configForm=function(){return this.restApiCallConfigForm},r.prototype.onConfigurationSet=function(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[i.Validators.required]],requestMethod:[e?e.requestMethod:null,[i.Validators.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],enableProxy:[!!e&&e.enableProxy,[]],useSystemProxyProperties:[!!e&&e.enableProxy,[]],proxyScheme:[e?e.proxyHost:null,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[i.Validators.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]]})},r.prototype.validatorTriggers=function(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]},r.prototype.updateValidators=function(e){var t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,r=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,n=this.restApiCallConfigForm.get("enableProxy").value,a=this.restApiCallConfigForm.get("useSystemProxyProperties").value;n&&!a?(this.restApiCallConfigForm.get("proxyHost").setValidators(n?[i.Validators.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(n?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([i.Validators.min(0)])),r?this.restApiCallConfigForm.get("maxQueueSize").setValidators([i.Validators.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rest-api-call-config",template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n \n \n \n tb.rulenode.max-parallel-requests-count\n \n \n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ye=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.smtpProtocols=["smtp","smtps"],n.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"],n}return y(r,e),r.prototype.configForm=function(){return this.sendEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.sendEmailConfigForm=this.fb.group({useSystemSmtpSettings:[!!e&&e.useSystemSmtpSettings,[]],smtpProtocol:[e?e.smtpProtocol:null,[]],smtpHost:[e?e.smtpHost:null,[]],smtpPort:[e?e.smtpPort:null,[]],timeout:[e?e.timeout:null,[]],enableTls:[!!e&&e.enableTls,[]],tlsVersion:[e?e.tlsVersion:null,[]],enableProxy:[!!e&&e.enableProxy,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmtpSettings","enableProxy"]},r.prototype.updateValidators=function(e){var t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,r=this.sendEmailConfigForm.get("enableProxy").value;t?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([]),this.sendEmailConfigForm.get("proxyHost").setValidators([]),this.sendEmailConfigForm.get("proxyPort").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([i.Validators.required,i.Validators.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(r?[i.Validators.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(r?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])),this.sendEmailConfigForm.get("smtpProtocol").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpPort").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("timeout").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-send-email-config",template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.serviceType=a.ServiceType.TB_RULE_ENGINE,n}return y(r,e),r.prototype.configForm=function(){return this.checkPointConfigForm},r.prototype.onConfigurationSet=function(e){this.checkPointConfigForm=this.fb.group({queueName:[e?e.queueName:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-check-point-config",template:'
\n \n \n
tb.rulenode.select-queue-hint
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),he=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allAzureIotHubCredentialsTypes=W,n.azureIotHubCredentialsTypeTranslationsMap=J,n}return y(r,e),r.prototype.configForm=function(){return this.azureIotHubConfigForm},r.prototype.onConfigurationSet=function(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[i.Validators.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],sasKey:[e&&e.credentials?e.credentials.sasKey:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.azureIotHubConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"sas":t.get("sasKey").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("sasKey").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-azure-iot-hub-config",template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.deviceProfile},r.prototype.onConfigurationSet=function(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState,i.Validators.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,i.Validators.required]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-device-profile-config",template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ve=function(){function e(){}return e=b([t.NgModule({declarations:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce],imports:[r.CommonModule,a.SharedModule,le],exports:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce]})],e)}(),Fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.checkMessageConfigForm},r.prototype.onConfigurationSet=function(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})},r.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},r.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},r.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},r.prototype.addMessageName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("messageNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("messageNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.prototype.addMetadataName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("metadataNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("metadataNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),xe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.checkRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],relationType:[e?e.relationType:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["checkForSingleEntity"]},r.prototype.updateValidators=function(e){var t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-relation-config",template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n}return y(r,e),r.prototype.configForm=function(){return this.geoFilterConfigForm},r.prototype.onConfigurationSet=function(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([])):(this.geoFilterConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoFilterConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoFilterConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),qe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.messageTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],n}return y(r,e),r.prototype.configForm=function(){return this.originatorTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ie=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),ke=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.switchConfigForm},r.prototype.onConfigurationSet=function(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ne=function(e){function r(t,r,n){var o,l,s=e.call(this,t)||this;s.store=t,s.translate=r,s.fb=n,s.alarmStatusTranslationsMap=a.alarmStatusTranslations,s.alarmStatusList=[],s.searchText="",s.displayStatusFn=s.displayStatus.bind(s);try{for(var m=C(Object.keys(a.AlarmStatus)),u=m.next();!u.done;u=m.next()){var d=u.value;s.alarmStatusList.push(a.AlarmStatus[d])}}catch(e){o={error:e}}finally{try{u&&!u.done&&(l=m.return)&&l.call(m)}finally{if(o)throw o.error}}return s.statusFormControl=new i.FormControl(""),s.filteredAlarmStatus=s.statusFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return s.fetchAlarmStatus(e)})),f.share()),s}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.alarmStatusConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.statusFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e?e.alarmStatusList:null,[i.Validators.required]]})},r.prototype.displayStatus=function(e){return e?this.translate.instant(a.alarmStatusTranslations.get(e)):void 0},r.prototype.fetchAlarmStatus=function(e){var t=this,r=this.getAlarmStatusList();if(this.searchText=e,this.searchText&&this.searchText.length){var n=this.searchText.toUpperCase();return c.of(r.filter((function(e){return t.translate.instant(a.alarmStatusTranslations.get(a.AlarmStatus[e])).toUpperCase().includes(n)})))}return c.of(r)},r.prototype.alarmStatusSelected=function(e){this.addAlarmStatus(e.option.value),this.clear("")},r.prototype.removeAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}},r.prototype.addAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))},r.prototype.getAlarmStatusList=function(){var e=this;return this.alarmStatusList.filter((function(t){return-1===e.alarmStatusConfigForm.get("alarmStatusList").value.indexOf(t)}))},r.prototype.onAlarmStatusInputFocus=function(){this.statusFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.alarmStatusInput.nativeElement.value=e,this.statusFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.alarmStatusInput.nativeElement.blur(),t.alarmStatusInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("alarmStatusInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"alarmStatusInput",void 0),r=b([t.Component({selector:"tb-filter-node-check-alarm-status-config",template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\n
\n
\n
\n \n
\n\n\n\n'}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ve=function(){function e(){}return e=b([t.NgModule({declarations:[Fe,xe,Te,qe,Se,Ie,ke,Ne],imports:[r.CommonModule,a.SharedModule,le],exports:[Fe,xe,Te,qe,Se,Ie,ke,Ne]})],e)}(),Ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.customerAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ae=function(e){function r(t,r,n){var a,o,l=e.call(this,t)||this;l.store=t,l.translate=r,l.fb=n,l.entityDetailsTranslationsMap=G,l.entityDetailsList=[],l.searchText="",l.displayDetailsFn=l.displayDetails.bind(l);try{for(var s=C(Object.keys(K)),m=s.next();!m.done;m=s.next()){var u=m.value;l.entityDetailsList.push(K[u])}}catch(e){a={error:e}}finally{try{m&&!m.done&&(o=s.return)&&o.call(s)}finally{if(a)throw a.error}}return l.detailsFormControl=new i.FormControl(""),l.filteredEntityDetails=l.detailsFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return l.fetchEntityDetails(e)})),f.share()),l}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.entityDetailsConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[i.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})},r.prototype.displayDetails=function(e){return e?this.translate.instant(G.get(e)):void 0},r.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var r=this.searchText.toUpperCase();return c.of(this.entityDetailsList.filter((function(e){return t.translate.instant(G.get(K[e])).toUpperCase().includes(r)})))}return c.of(this.entityDetailsList)},r.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},r.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},r.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},r.prototype.onEntityDetailsInputFocus=function(){this.detailsFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("detailsInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"detailsInput",void 0),r=b([t.Component({selector:"tb-enrichment-node-entity-details-config",template:'
\n \n tb.rulenode.entity-details\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Le=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.deviceAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[i.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.deviceAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.deviceAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.originatorAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.originatorAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.originatorAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.originatorFieldsConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n.fetchMode=U,n.fetchModes=Object.keys(U),n.samplingOrders=Object.keys(j),n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.getTelemetryFromDatabaseConfigForm},r.prototype.onConfigurationSet=function(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[i.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchMode","useMetadataIntervalPatterns"]},r.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,r=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===U.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([i.Validators.required,i.Validators.min(2),i.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),r?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([i.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},r.prototype.removeKey=function(e,t){var r=this.getTelemetryFromDatabaseConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),we=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.relatedAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[i.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.tenantAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),De=function(){function e(){}return e=b([t.NgModule({declarations:[Ee,Ae,Le,Me,Pe,Re,we,Oe],imports:[r.CommonModule,a.SharedModule,le],exports:[Ee,Ae,Le,Me,Pe,Re,we,Oe]})],e)}(),Ke=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=v,n.originatorSources=Object.keys(v),n.originatorSourceTranslationMap=P,n}return y(r,e),r.prototype.configForm=function(){return this.changeOriginatorConfigForm},r.prototype.onConfigurationSet=function(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[i.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]})},r.prototype.validatorTriggers=function(){return["originatorSource"]},r.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===v.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([i.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-change-originator-config",template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n \n \n
\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Be=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.toEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[i.Validators.required]],toTemplate:[e?e.toTemplate:null,[i.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[i.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-to-email-config",template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),je=function(){function e(){}return e=b([t.NgModule({declarations:[Ke,Be,Ue],imports:[r.CommonModule,a.SharedModule,le],exports:[Ke,Be,Ue]})],e)}(),He=function(){function e(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","client-attributes-hint":"Client attributes, use ${metaKeyName} to substitute variables from metadata","shared-attributes":"Shared attributes","shared-attributes-hint":"Shared attributes, use ${metaKeyName} to substitute variables from metadata","server-attributes":"Server attributes","server-attributes-hint":"Server attributes, use ${metaKeyName} to substitute variables from metadata","notify-device":"Notify Device","notify-device-hint":"If the message arrives from the device, we will push it back to the device by default.","latest-timeseries":"Latest timeseries","latest-timeseries-hint":"Latest timeseries, use ${metaKeyName} to substitute variables from metadata","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","alarm-status-filter":"Alarm status filter","alarm-status-list-empty":"Alarm status list is empty","no-alarm-status-matching":"No alarm status matching were found.",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata",topic:"Topic","topic-required":"Topic is required","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","device-id":"Device ID","device-id-required":"Device ID is required.","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required.","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","tls-version":"TLS version","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"Proxy port is required.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.ctorParameters=function(){return[{type:n.TranslateService}]},e=b([t.NgModule({declarations:[F],imports:[r.CommonModule,a.SharedModule],exports:[ve,Ve,De,je,F]}),h("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=He,e.ɵa=F,e.ɵb=ve,e.ɵba=be,e.ɵbb=he,e.ɵbc=Ce,e.ɵbd=le,e.ɵbe=ne,e.ɵbf=ae,e.ɵbg=oe,e.ɵbh=ie,e.ɵbi=Ve,e.ɵbj=Fe,e.ɵbk=xe,e.ɵbl=Te,e.ɵbm=qe,e.ɵbn=Se,e.ɵbo=Ie,e.ɵbp=ke,e.ɵbq=Ne,e.ɵbr=De,e.ɵbs=Ee,e.ɵbt=Ae,e.ɵbu=Le,e.ɵbv=Me,e.ɵbw=Pe,e.ɵbx=Re,e.ɵby=we,e.ɵbz=Oe,e.ɵc=x,e.ɵca=je,e.ɵcb=Ke,e.ɵcc=Be,e.ɵcd=Ue,e.ɵd=T,e.ɵe=q,e.ɵf=S,e.ɵg=I,e.ɵh=k,e.ɵi=N,e.ɵj=V,e.ɵk=E,e.ɵl=A,e.ɵm=L,e.ɵn=X,e.ɵo=ee,e.ɵp=te,e.ɵq=re,e.ɵr=se,e.ɵs=me,e.ɵt=ue,e.ɵu=de,e.ɵv=pe,e.ɵw=ce,e.ɵx=fe,e.ɵy=ge,e.ɵz=ye,Object.defineProperty(e,"__esModule",{value:!0})})); + ***************************************************************************** */var g=function(e,t){return(g=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])})(e,t)};function y(e,t){function r(){this.constructor=e}g(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function b(e,t,r,n){var a,o=arguments.length,i=o<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,r):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,r,n);else for(var l=e.length-1;l>=0;l--)(a=e[l])&&(i=(o<3?a(i):o>3?a(t,r,i):a(t,r))||i);return o>3&&i&&Object.defineProperty(t,r,i),i}function h(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}Object.create;function C(e){var t="function"==typeof Symbol&&Symbol.iterator,r=t&&e[t],n=0;if(r)return r.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}Object.create;var v,F=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.emptyConfigForm},r.prototype.onConfigurationSet=function(e){this.emptyConfigForm=this.fb.group({})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-node-empty-config",template:"
"}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),x=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.attributesConfigForm},r.prototype.onConfigurationSet=function(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[i.Validators.required]],notifyDevice:[!e||e.scope,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-attributes-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-hint
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),T=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.timeseriesConfigForm},r.prototype.onConfigurationSet=function(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-timeseries-config",template:'
\n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),q=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcRequestConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-request-config",template:'
\n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),S=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.logConfigForm},r.prototype.onConfigurationSet=function(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),I=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.assignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),k=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.clearAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],alarmType:[e?e.alarmType:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-clear-alarm-config",template:'
\n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),N=function(e){function r(t,r,n,o){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.nodeScriptTestService=n,i.translate=o,i.alarmSeverities=Object.keys(a.AlarmSeverity),i.alarmSeverityTranslationMap=a.alarmSeverityTranslations,i.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],i}return y(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]})},r.prototype.validatorTriggers=function(){return["useMessageAlarmData"]},r.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([i.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([i.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},r.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.removeKey=function(e,t){var r=this.createAlarmConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.createAlarmConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),V=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.createRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[i.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["entityType"]},r.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([i.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-create-relation-config",template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),E=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgDelayConfigForm},r.prototype.onConfigurationSet=function(e){this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(1e5)]]})},r.prototype.validatorTriggers=function(){return["useMetadataPeriodInSecondsPatterns"]},r.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([i.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([i.Validators.required,i.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-delay-config",template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n \n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),A=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.deleteRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["deleteForSingleEntity","entityType"]},r.prototype.updateValidators=function(e){var t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-delete-relation-config",template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),L=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.generatorConfigForm},r.prototype.onConfigurationSet=function(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[i.Validators.required,i.Validators.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[i.Validators.required,i.Validators.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.prepareInputConfig=function(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e},r.prototype.prepareOutputConfig=function(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e},r.prototype.testScript=function(){var e=this,t=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId).subscribe((function(t){t&&e.generatorConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-generator-config",template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent);!function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR"}(v||(v={}));var M,P=new Map([[v.CUSTOMER,"tb.rulenode.originator-customer"],[v.TENANT,"tb.rulenode.originator-tenant"],[v.RELATED,"tb.rulenode.originator-related"],[v.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(M||(M={}));var R,w=new Map([[M.CIRCLE,"tb.rulenode.perimeter-circle"],[M.POLYGON,"tb.rulenode.perimeter-polygon"]]);!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(R||(R={}));var O,D=new Map([[R.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[R.SECONDS,"tb.rulenode.time-unit-seconds"],[R.MINUTES,"tb.rulenode.time-unit-minutes"],[R.HOURS,"tb.rulenode.time-unit-hours"],[R.DAYS,"tb.rulenode.time-unit-days"]]);!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(O||(O={}));var K,B=new Map([[O.METER,"tb.rulenode.range-unit-meter"],[O.KILOMETER,"tb.rulenode.range-unit-kilometer"],[O.FOOT,"tb.rulenode.range-unit-foot"],[O.MILE,"tb.rulenode.range-unit-mile"],[O.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);!function(e){e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(K||(K={}));var U,j,H,G=new Map([[K.TITLE,"tb.rulenode.entity-details-title"],[K.COUNTRY,"tb.rulenode.entity-details-country"],[K.STATE,"tb.rulenode.entity-details-state"],[K.ZIP,"tb.rulenode.entity-details-zip"],[K.ADDRESS,"tb.rulenode.entity-details-address"],[K.ADDRESS2,"tb.rulenode.entity-details-address2"],[K.PHONE,"tb.rulenode.entity-details-phone"],[K.EMAIL,"tb.rulenode.entity-details-email"],[K.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(U||(U={})),function(e){e.ASC="ASC",e.DESC="DESC"}(j||(j={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(H||(H={}));var z,$=new Map([[H.STANDARD,"tb.rulenode.sqs-queue-standard"],[H.FIFO,"tb.rulenode.sqs-queue-fifo"]]),Q=["anonymous","basic","cert.PEM"],_=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),W=["sas","cert.PEM"],J=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(z||(z={}));var Y=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],Z=new Map([["US-ASCII","tb.rulenode.charset-us-ascii"],["ISO-8859-1","tb.rulenode.charset-iso-8859-1"],["UTF-8","tb.rulenode.charset-utf-8"],["UTF-16BE","tb.rulenode.charset-utf-16be"],["UTF-16LE","tb.rulenode.charset-utf-16le"],["UTF-16","tb.rulenode.charset-utf-16"]]),X=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.geoActionConfigForm},r.prototype.onConfigurationSet=function(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]],minInsideDuration:[e?e.minInsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[i.Validators.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterType").setValidators([]):this.geoActionConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([])):(this.geoActionConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoActionConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoActionConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoActionConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgCountConfigForm},r.prototype.onConfigurationSet=function(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[i.Validators.required,i.Validators.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-count-config",template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcReplyConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.saveToCustomTableConfigForm},r.prototype.onConfigurationSet=function(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[i.Validators.required]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-custom-table-config",template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ne=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.translate=r,o.injector=n,o.fb=a,o.propagateChange=null,o.valueChangeSubscription=null,o}var a;return y(r,e),a=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){this.ngControl=this.injector.get(i.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},r.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t,r,n=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var o=C(Object.keys(e)),l=o.next();!l.done;l=o.next()){var s=l.value;Object.prototype.hasOwnProperty.call(e,s)&&a.push(this.fb.group({key:[s,[i.Validators.required]],value:[e[s],[i.Validators.required]]}))}}catch(e){t={error:e}}finally{try{l&&!l.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){n.updateModel()}))},r.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},r.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[i.Validators.required]],value:["",[i.Validators.required]]}))},r.prototype.validate=function(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}},r.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:t.Injector},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",String)],r.prototype,"requiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyRequiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valRequiredText",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=a=b([t.Component({selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return a})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return a})),multi:!0}],styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}),h("design:paramtypes",[o.Store,n.TranslateService,t.Injector,i.FormBuilder])],r)}(a.PageComponent),ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[i.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-device-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),ie=function(e){function r(t,r,n,o){var i,l,m=e.call(this,t)||this;m.store=t,m.translate=r,m.truncate=n,m.fb=o,m.placeholder="tb.rulenode.message-type",m.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],m.messageTypes=[],m.messageTypesList=[],m.searchText="",m.propagateChange=function(e){},m.messageTypeConfigForm=m.fb.group({messageType:[null]});try{for(var u=C(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;m.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){i={error:e}}finally{try{d&&!d.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}return m}var l;return y(r,e),l=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.ngOnInit=function(){var e=this;this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(t){return e.fetchMessageTypes(t)})),f.share())},r.prototype.ngAfterViewInit=function(){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.forEach((function(e){var r=t.messageTypesList.find((function(t){return t.value===e}));r?t.messageTypes.push({name:r.name,value:r.value}):t.messageTypes.push({name:e,value:e})}))},r.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},r.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},r.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},r.prototype.add=function(e){this.transformMessageType(e.value)},r.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return c.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return c.of(this.messageTypesList)},r.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,r=e.trim(),n=this.messageTypesList.find((function(e){return e.name===r}));(t=n?{name:n.name,value:n.value}:{name:r,value:r})&&this.addMessageType(t)}this.clear("")},r.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},r.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},r.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},r.prototype.onFocus=function(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},r.prototype.updateModel=function(){var e=this.messageTypes.map((function(e){return e.value}));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:a.TruncatePipe},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),b([t.Input(),h("design:type",String)],r.prototype,"label",void 0),b([t.Input(),h("design:type",Object)],r.prototype,"placeholder",void 0),b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.ViewChild("chipList",{static:!1}),h("design:type",d.MatChipList)],r.prototype,"chipList",void 0),b([t.ViewChild("messageTypeAutocomplete",{static:!1}),h("design:type",p.MatAutocomplete)],r.prototype,"matAutocomplete",void 0),b([t.ViewChild("messageTypeInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"messageTypeInput",void 0),r=l=b([t.Component({selector:"tb-message-types-config",template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n \n {{ \'tb.rulenode.message-types-required\' | translate }}\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return l})),multi:!0}]}),h("design:paramtypes",[o.Store,n.TranslateService,a.TruncatePipe,i.FormBuilder])],r)}(a.PageComponent),le=function(){function e(){}return e=b([t.NgModule({declarations:[ne,ae,oe,ie],imports:[r.CommonModule,a.SharedModule,m.HomeComponentsModule],exports:[ne,ae,oe,ie]})],e)}(),se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.unassignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-un-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.snsConfigForm},r.prototype.onConfigurationSet=function(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[i.Validators.required]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sns-config",template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.sqsQueueType=H,n.sqsQueueTypes=Object.keys(H),n.sqsQueueTypeTranslationsMap=$,n}return y(r,e),r.prototype.configForm=function(){return this.sqsConfigForm},r.prototype.onConfigurationSet=function(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[i.Validators.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[i.Validators.required]],delaySeconds:[e?e.delaySeconds:null,[i.Validators.min(0),i.Validators.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sqs-config",template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.pubSubConfigForm},r.prototype.onConfigurationSet=function(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[i.Validators.required]],topicName:[e?e.topicName:null,[i.Validators.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[i.Validators.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[i.Validators.required]],messageAttributes:[e?e.messageAttributes:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-pub-sub-config",template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.ackValues=["all","-1","0","1"],n.ToByteStandartCharsetTypesValues=Y,n.ToByteStandartCharsetTypeTranslationMap=Z,n}return y(r,e),r.prototype.configForm=function(){return this.kafkaConfigForm},r.prototype.onConfigurationSet=function(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],bootstrapServers:[e?e.bootstrapServers:null,[i.Validators.required]],retries:[e?e.retries:null,[i.Validators.min(0)]],batchSize:[e?e.batchSize:null,[i.Validators.min(0)]],linger:[e?e.linger:null,[i.Validators.min(0)]],bufferMemory:[e?e.bufferMemory:null,[i.Validators.min(0)]],acks:[e?e.acks:null,[i.Validators.required]],keySerializer:[e?e.keySerializer:null,[i.Validators.required]],valueSerializer:[e?e.valueSerializer:null,[i.Validators.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})},r.prototype.validatorTriggers=function(){return["addMetadataKeyValuesAsKafkaHeaders"]},r.prototype.updateValidators=function(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([i.Validators.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allMqttCredentialsTypes=Q,n.mqttCredentialsTypeTranslationsMap=_,n}return y(r,e),r.prototype.configForm=function(){return this.mqttConfigForm},r.prototype.onConfigurationSet=function(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],username:[e&&e.credentials?e.credentials.username:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;switch(t){case"anonymous":e.credentials={type:t};break;case"basic":e.credentials={type:t,username:e.credentials.username,password:e.credentials.password};break;case"cert.PEM":delete e.credentials.username}return e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.mqttConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("username").setValidators([]),t.get("password").setValidators([]),t.get("caCert").setValidators([]),t.get("caCertFileName").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"anonymous":break;case"basic":t.get("username").setValidators([i.Validators.required]),t.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("caCert").setValidators([i.Validators.required]),t.get("caCertFileName").setValidators([i.Validators.required]),t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("username").updateValueAndValidity({emitEvent:e}),t.get("password").updateValueAndValidity({emitEvent:e}),t.get("caCert").updateValueAndValidity({emitEvent:e}),t.get("caCertFileName").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-mqtt-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n \n tb.rulenode.credentials\n \n {{ mqttCredentialsTypeTranslationsMap.get(mqttConfigForm.get(\'credentials\').get(\'type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ mqttCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return y(r,e),r.prototype.configForm=function(){return this.rabbitMqConfigForm},r.prototype.onConfigurationSet=function(e){this.rabbitMqConfigForm=this.fb.group({exchangeNamePattern:[e?e.exchangeNamePattern:null,[]],routingKeyPattern:[e?e.routingKeyPattern:null,[]],messageProperties:[e?e.messageProperties:null,[]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],virtualHost:[e?e.virtualHost:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]],automaticRecoveryEnabled:[!!e&&e.automaticRecoveryEnabled,[]],connectionTimeout:[e?e.connectionTimeout:null,[i.Validators.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[i.Validators.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rabbit-mq-config",template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.proxySchemes=["http","https"],n.httpRequestTypes=Object.keys(z),n}return y(r,e),r.prototype.configForm=function(){return this.restApiCallConfigForm},r.prototype.onConfigurationSet=function(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[i.Validators.required]],requestMethod:[e?e.requestMethod:null,[i.Validators.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],enableProxy:[!!e&&e.enableProxy,[]],useSystemProxyProperties:[!!e&&e.enableProxy,[]],proxyScheme:[e?e.proxyHost:null,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[i.Validators.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]]})},r.prototype.validatorTriggers=function(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]},r.prototype.updateValidators=function(e){var t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,r=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,n=this.restApiCallConfigForm.get("enableProxy").value,a=this.restApiCallConfigForm.get("useSystemProxyProperties").value;n&&!a?(this.restApiCallConfigForm.get("proxyHost").setValidators(n?[i.Validators.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(n?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([i.Validators.min(0)])),r?this.restApiCallConfigForm.get("maxQueueSize").setValidators([i.Validators.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rest-api-call-config",template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n \n \n \n tb.rulenode.max-parallel-requests-count\n \n \n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ye=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.smtpProtocols=["smtp","smtps"],n.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"],n}return y(r,e),r.prototype.configForm=function(){return this.sendEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.sendEmailConfigForm=this.fb.group({useSystemSmtpSettings:[!!e&&e.useSystemSmtpSettings,[]],smtpProtocol:[e?e.smtpProtocol:null,[]],smtpHost:[e?e.smtpHost:null,[]],smtpPort:[e?e.smtpPort:null,[]],timeout:[e?e.timeout:null,[]],enableTls:[!!e&&e.enableTls,[]],tlsVersion:[e?e.tlsVersion:null,[]],enableProxy:[!!e&&e.enableProxy,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmtpSettings","enableProxy"]},r.prototype.updateValidators=function(e){var t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,r=this.sendEmailConfigForm.get("enableProxy").value;t?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([]),this.sendEmailConfigForm.get("proxyHost").setValidators([]),this.sendEmailConfigForm.get("proxyPort").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([i.Validators.required,i.Validators.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(r?[i.Validators.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(r?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])),this.sendEmailConfigForm.get("smtpProtocol").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpPort").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("timeout").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-send-email-config",template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.serviceType=a.ServiceType.TB_RULE_ENGINE,n}return y(r,e),r.prototype.configForm=function(){return this.checkPointConfigForm},r.prototype.onConfigurationSet=function(e){this.checkPointConfigForm=this.fb.group({queueName:[e?e.queueName:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-check-point-config",template:'
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),he=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allAzureIotHubCredentialsTypes=W,n.azureIotHubCredentialsTypeTranslationsMap=J,n}return y(r,e),r.prototype.configForm=function(){return this.azureIotHubConfigForm},r.prototype.onConfigurationSet=function(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[i.Validators.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],sasKey:[e&&e.credentials?e.credentials.sasKey:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.azureIotHubConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"sas":t.get("sasKey").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("sasKey").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-azure-iot-hub-config",template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.deviceProfile},r.prototype.onConfigurationSet=function(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState,i.Validators.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,i.Validators.required]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-device-profile-config",template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ve=function(){function e(){}return e=b([t.NgModule({declarations:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce],imports:[r.CommonModule,a.SharedModule,le],exports:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce]})],e)}(),Fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.checkMessageConfigForm},r.prototype.onConfigurationSet=function(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})},r.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},r.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},r.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},r.prototype.addMessageName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("messageNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("messageNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.prototype.addMetadataName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("metadataNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("metadataNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),xe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.checkRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],relationType:[e?e.relationType:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["checkForSingleEntity"]},r.prototype.updateValidators=function(e){var t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-relation-config",template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n}return y(r,e),r.prototype.configForm=function(){return this.geoFilterConfigForm},r.prototype.onConfigurationSet=function(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([])):(this.geoFilterConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoFilterConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoFilterConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),qe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.messageTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],n}return y(r,e),r.prototype.configForm=function(){return this.originatorTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ie=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),ke=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.switchConfigForm},r.prototype.onConfigurationSet=function(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ne=function(e){function r(t,r,n){var o,l,s=e.call(this,t)||this;s.store=t,s.translate=r,s.fb=n,s.alarmStatusTranslationsMap=a.alarmStatusTranslations,s.alarmStatusList=[],s.searchText="",s.displayStatusFn=s.displayStatus.bind(s);try{for(var m=C(Object.keys(a.AlarmStatus)),u=m.next();!u.done;u=m.next()){var d=u.value;s.alarmStatusList.push(a.AlarmStatus[d])}}catch(e){o={error:e}}finally{try{u&&!u.done&&(l=m.return)&&l.call(m)}finally{if(o)throw o.error}}return s.statusFormControl=new i.FormControl(""),s.filteredAlarmStatus=s.statusFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return s.fetchAlarmStatus(e)})),f.share()),s}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.alarmStatusConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.statusFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e?e.alarmStatusList:null,[i.Validators.required]]})},r.prototype.displayStatus=function(e){return e?this.translate.instant(a.alarmStatusTranslations.get(e)):void 0},r.prototype.fetchAlarmStatus=function(e){var t=this,r=this.getAlarmStatusList();if(this.searchText=e,this.searchText&&this.searchText.length){var n=this.searchText.toUpperCase();return c.of(r.filter((function(e){return t.translate.instant(a.alarmStatusTranslations.get(a.AlarmStatus[e])).toUpperCase().includes(n)})))}return c.of(r)},r.prototype.alarmStatusSelected=function(e){this.addAlarmStatus(e.option.value),this.clear("")},r.prototype.removeAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}},r.prototype.addAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))},r.prototype.getAlarmStatusList=function(){var e=this;return this.alarmStatusList.filter((function(t){return-1===e.alarmStatusConfigForm.get("alarmStatusList").value.indexOf(t)}))},r.prototype.onAlarmStatusInputFocus=function(){this.statusFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.alarmStatusInput.nativeElement.value=e,this.statusFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.alarmStatusInput.nativeElement.blur(),t.alarmStatusInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("alarmStatusInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"alarmStatusInput",void 0),r=b([t.Component({selector:"tb-filter-node-check-alarm-status-config",template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\n
\n
\n
\n \n
\n\n\n\n'}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ve=function(){function e(){}return e=b([t.NgModule({declarations:[Fe,xe,Te,qe,Se,Ie,ke,Ne],imports:[r.CommonModule,a.SharedModule,le],exports:[Fe,xe,Te,qe,Se,Ie,ke,Ne]})],e)}(),Ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.customerAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ae=function(e){function r(t,r,n){var a,o,l=e.call(this,t)||this;l.store=t,l.translate=r,l.fb=n,l.entityDetailsTranslationsMap=G,l.entityDetailsList=[],l.searchText="",l.displayDetailsFn=l.displayDetails.bind(l);try{for(var s=C(Object.keys(K)),m=s.next();!m.done;m=s.next()){var u=m.value;l.entityDetailsList.push(K[u])}}catch(e){a={error:e}}finally{try{m&&!m.done&&(o=s.return)&&o.call(s)}finally{if(a)throw a.error}}return l.detailsFormControl=new i.FormControl(""),l.filteredEntityDetails=l.detailsFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return l.fetchEntityDetails(e)})),f.share()),l}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.entityDetailsConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[i.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})},r.prototype.displayDetails=function(e){return e?this.translate.instant(G.get(e)):void 0},r.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var r=this.searchText.toUpperCase();return c.of(this.entityDetailsList.filter((function(e){return t.translate.instant(G.get(K[e])).toUpperCase().includes(r)})))}return c.of(this.entityDetailsList)},r.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},r.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},r.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},r.prototype.onEntityDetailsInputFocus=function(){this.detailsFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("detailsInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"detailsInput",void 0),r=b([t.Component({selector:"tb-enrichment-node-entity-details-config",template:'
\n \n tb.rulenode.entity-details\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Le=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.deviceAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[i.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.deviceAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.deviceAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.originatorAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.originatorAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.originatorAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.originatorFieldsConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n.fetchMode=U,n.fetchModes=Object.keys(U),n.samplingOrders=Object.keys(j),n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.getTelemetryFromDatabaseConfigForm},r.prototype.onConfigurationSet=function(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[i.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchMode","useMetadataIntervalPatterns"]},r.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,r=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===U.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([i.Validators.required,i.Validators.min(2),i.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),r?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([i.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},r.prototype.removeKey=function(e,t){var r=this.getTelemetryFromDatabaseConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),we=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.relatedAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[i.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.tenantAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),De=function(){function e(){}return e=b([t.NgModule({declarations:[Ee,Ae,Le,Me,Pe,Re,we,Oe],imports:[r.CommonModule,a.SharedModule,le],exports:[Ee,Ae,Le,Me,Pe,Re,we,Oe]})],e)}(),Ke=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=v,n.originatorSources=Object.keys(v),n.originatorSourceTranslationMap=P,n}return y(r,e),r.prototype.configForm=function(){return this.changeOriginatorConfigForm},r.prototype.onConfigurationSet=function(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[i.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]})},r.prototype.validatorTriggers=function(){return["originatorSource"]},r.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===v.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([i.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-change-originator-config",template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n \n \n
\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Be=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.toEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[i.Validators.required]],toTemplate:[e?e.toTemplate:null,[i.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[i.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-to-email-config",template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),je=function(){function e(){}return e=b([t.NgModule({declarations:[Ke,Be,Ue],imports:[r.CommonModule,a.SharedModule,le],exports:[Ke,Be,Ue]})],e)}(),He=function(){function e(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","client-attributes-hint":"Client attributes, use ${metaKeyName} to substitute variables from metadata","shared-attributes":"Shared attributes","shared-attributes-hint":"Shared attributes, use ${metaKeyName} to substitute variables from metadata","server-attributes":"Server attributes","server-attributes-hint":"Server attributes, use ${metaKeyName} to substitute variables from metadata","notify-device":"Notify Device","notify-device-hint":"If the message arrives from the device, we will push it back to the device by default.","latest-timeseries":"Latest timeseries","latest-timeseries-hint":"Latest timeseries, use ${metaKeyName} to substitute variables from metadata","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","alarm-status-filter":"Alarm status filter","alarm-status-list-empty":"Alarm status list is empty","no-alarm-status-matching":"No alarm status matching were found.",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata",topic:"Topic","topic-required":"Topic is required","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","device-id":"Device ID","device-id-required":"Device ID is required.","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required.","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","tls-version":"TLS version","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"Proxy port is required.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.ctorParameters=function(){return[{type:n.TranslateService}]},e=b([t.NgModule({declarations:[F],imports:[r.CommonModule,a.SharedModule],exports:[ve,Ve,De,je,F]}),h("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=He,e.ɵa=F,e.ɵb=ve,e.ɵba=be,e.ɵbb=he,e.ɵbc=Ce,e.ɵbd=le,e.ɵbe=ne,e.ɵbf=ae,e.ɵbg=oe,e.ɵbh=ie,e.ɵbi=Ve,e.ɵbj=Fe,e.ɵbk=xe,e.ɵbl=Te,e.ɵbm=qe,e.ɵbn=Se,e.ɵbo=Ie,e.ɵbp=ke,e.ɵbq=Ne,e.ɵbr=De,e.ɵbs=Ee,e.ɵbt=Ae,e.ɵbu=Le,e.ɵbv=Me,e.ɵbw=Pe,e.ɵbx=Re,e.ɵby=we,e.ɵbz=Oe,e.ɵc=x,e.ɵca=je,e.ɵcb=Ke,e.ɵcc=Be,e.ɵcd=Ue,e.ɵd=T,e.ɵe=q,e.ɵf=S,e.ɵg=I,e.ɵh=k,e.ɵi=N,e.ɵj=V,e.ɵk=E,e.ɵl=A,e.ɵm=L,e.ɵn=X,e.ɵo=ee,e.ɵp=te,e.ɵq=re,e.ɵr=se,e.ɵs=me,e.ɵt=ue,e.ɵu=de,e.ɵv=pe,e.ɵw=ce,e.ɵx=fe,e.ɵy=ge,e.ɵz=ye,Object.defineProperty(e,"__esModule",{value:!0})})); //# sourceMappingURL=rulenode-core-config.umd.min.js.map \ No newline at end of file diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html index b53ca8f0b0..c696272d78 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html @@ -49,7 +49,6 @@ [queueType]="serviceType" formControlName="defaultQueueName"> -
device-profile.select-queue-hint
device-profile.type diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html index 0eb4c07367..468e946a74 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html @@ -57,7 +57,6 @@ [queueType]="serviceType" formControlName="defaultQueueName"> -
device-profile.select-queue-hint
device-profile.type diff --git a/ui-ngx/src/app/modules/home/components/rule-chain/rule-chain-autocomplete.component.ts b/ui-ngx/src/app/modules/home/components/rule-chain/rule-chain-autocomplete.component.ts index 53449aad10..de404f49e2 100644 --- a/ui-ngx/src/app/modules/home/components/rule-chain/rule-chain-autocomplete.component.ts +++ b/ui-ngx/src/app/modules/home/components/rule-chain/rule-chain-autocomplete.component.ts @@ -202,8 +202,8 @@ export class RuleChainAutocompleteComponent implements ControlValueAccessor, OnI createDefaultRuleChain($event: Event, ruleChainName: string) { $event.preventDefault(); this.ruleChainAutocomplete.closePanel(); - this.ruleChainService.createDefaultRuleChain(ruleChainName).subscribe((ruleChain) => { - this.updateView(ruleChain.id.id); + this.ruleChainService.createDefaultRuleChain(ruleChainName.trim()).subscribe((ruleChain) => { + this.selectRuleChainFormGroup.get('ruleChainId').patchValue(ruleChain); }); } } diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index fe7849d3e1..362b3c14ce 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -102,7 +102,6 @@ [queueType]="serviceType" formControlName="defaultQueueName"> -
device-profile.select-queue-hint
diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts index a539b48917..6a7c05e0ac 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts @@ -25,8 +25,12 @@ import { createDeviceProfileConfiguration, createDeviceProfileTransportConfiguration, DeviceProfile, - DeviceProfileType, DeviceProvisionConfiguration, DeviceProvisionType, - DeviceTransportType, deviceTransportTypeConfigurationInfoMap, deviceTransportTypeHintMap, + DeviceProfileType, + DeviceProvisionConfiguration, + DeviceProvisionType, + DeviceTransportType, + deviceTransportTypeConfigurationInfoMap, + deviceTransportTypeHintMap, deviceTransportTypeTranslationMap } from '@shared/models/device.models'; import { MatHorizontalStepper } from '@angular/material/stepper'; @@ -43,7 +47,8 @@ import { StepperSelectionEvent } from '@angular/cdk/stepper'; import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; -import {ServiceType} from "@shared/models/queue.models"; +import { ServiceType } from '@shared/models/queue.models'; +import { deepTrim } from '@core/utils'; @Component({ selector: 'tb-device-wizard', @@ -108,7 +113,7 @@ export class DeviceWizardDialogComponent extends deviceProfileId: [null, Validators.required], newDeviceProfileTitle: [{value: null, disabled: true}], defaultRuleChainId: [{value: null, disabled: true}], - defaultQueueName: [''], + defaultQueueName: [{value: null, disabled: true}], description: [''] } ); @@ -288,7 +293,7 @@ export class DeviceWizardDialogComponent extends if (this.deviceWizardFormGroup.get('defaultRuleChainId').value) { deviceProfile.defaultRuleChainId = new RuleChainId(this.deviceWizardFormGroup.get('defaultRuleChainId').value); } - return this.deviceProfileService.saveDeviceProfile(deviceProfile).pipe( + return this.deviceProfileService.saveDeviceProfile(deepTrim(deviceProfile)).pipe( map(profile => profile.id), tap((profileId) => { this.deviceWizardFormGroup.patchValue({ @@ -319,7 +324,7 @@ export class DeviceWizardDialogComponent extends id: this.customerFormGroup.get('customerId').value }; } - return this.data.entitiesTableConfig.saveEntity(device); + return this.data.entitiesTableConfig.saveEntity(deepTrim(device)); } private saveCredentials(device: BaseData): Observable { diff --git a/ui-ngx/src/app/shared/components/queue/queue-type-list.component.html b/ui-ngx/src/app/shared/components/queue/queue-type-list.component.html index ccdf4a76ec..1380e7e28b 100644 --- a/ui-ngx/src/app/shared/components/queue/queue-type-list.component.html +++ b/ui-ngx/src/app/shared/components/queue/queue-type-list.component.html @@ -40,4 +40,5 @@ {{ 'queue.name_required' | translate }} + device-profile.select-queue-hint
From 75686ac16757f6a32405eed3894f4c1c59e5c8de Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 29 Oct 2020 19:03:20 +0200 Subject: [PATCH 283/463] Added new language Brazilian Portuguese --- .../assets/locale/locale.constant-en_US.json | 3 +- .../assets/locale/locale.constant-pl_BR.json | 2048 +++++++++++++++++ 2 files changed, 2050 insertions(+), 1 deletion(-) create mode 100644 ui-ngx/src/assets/locale/locale.constant-pl_BR.json diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 571d4ed5dc..e3b7b4b81d 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2342,7 +2342,8 @@ "el_GR": "Ελληνικά", "ro_RO": "Română", "lv_LV": "Latviešu", - "ka_GE": "ქართული" + "ka_GE": "ქართული", + "pt_BR": "Português do Brasil" } } } diff --git a/ui-ngx/src/assets/locale/locale.constant-pl_BR.json b/ui-ngx/src/assets/locale/locale.constant-pl_BR.json new file mode 100644 index 0000000000..f08a38e9d9 --- /dev/null +++ b/ui-ngx/src/assets/locale/locale.constant-pl_BR.json @@ -0,0 +1,2048 @@ +{ + "access": { + "unauthorized": "Não autorizado", + "unauthorized-access": "Acesso não autorizado", + "unauthorized-access-text": "Para acessar este recurso, você precisa se inscrever!", + "access-forbidden": "Acesso proibido", + "access-forbidden-text": "Você não tem direito de acesso a esta localização!
Se ainda deseja acessar esta localização, tente inscrever-se com um nome de usuário diferente.", + "refresh-token-expired": "A sessão expirou", + "refresh-token-failed": "Impossível atualizar a sessão", + "permission-denied": "Permissão negada", + "permission-denied-text": "Você não tem permissão para executar esta operação!" + }, + "action": { + "activate": "Ativar", + "suspend": "Suspender", + "save": "Salvar", + "saveAs": "Salvar como", + "cancel": "Cancelar", + "ok": "OK", + "delete": "Excluir", + "add": "Adicionar", + "yes": "Sim", + "no": "Não", + "update": "Atualizar", + "remove": "Remover", + "select": "Selecionar", + "search": "Pesquisar", + "clear-search": "Limpar pesquisa", + "assign": "Atribuir", + "unassign": "Cancelar atribuição", + "share": "Compartilhar", + "make-private": "Tornar privado", + "apply": "Aplicar", + "apply-changes": "Aplicar alterações", + "edit-mode": "Modo de edição", + "enter-edit-mode": "Entrar no modo de edição", + "decline-changes": "Rejeitar alterações", + "close": "Fechar", + "back": "Voltar", + "run": "Executar", + "sign-in": "Inscreva-se!", + "edit": "Editar", + "view": "Visualizar", + "create": "Criar", + "drag": "Arrastar", + "refresh": "Atualizar", + "undo": "Desfazer", + "copy": "Copiar", + "paste": "Colar", + "copy-reference": "Copiar referência", + "paste-reference": "Colar referência", + "import": "Importar", + "export": "Exportar", + "share-via": "Compartilhar via {{provider}}", + "continue": "Continuar", + "discard-changes": "Descartar alterações", + "download": "Download" + }, + "aggregation": { + "aggregation": "Agregação", + "function": "Função de agregação de dados", + "limit": "Valores máx.", + "group-interval": "Intervalo de agrupamento", + "min": "Mín.", + "max": "Máx.", + "avg": "Média", + "sum": "Soma", + "count": "Contagem", + "none": "Nenhum" + }, + "admin": { + "general": "Geral", + "general-settings": "Configuração geral", + "outgoing-mail": "Servidor de e-mail", + "outgoing-mail-settings": "Configuração do servidor de saída de e-mail", + "system-settings": "Configuração do sistema", + "test-mail-sent": "O e-mail de teste foi enviado corretamente!", + "base-url": "URL de base", + "base-url-required": "O URL de base é obrigatório.", + "mail-from": "E-mail de", + "mail-from-required": "'E-mail de' é obrigatório.", + "smtp-protocol": "Protocolo SMTP", + "smtp-host": "Host SMTP", + "smtp-host-required": "O host SMTP é obrigatório.", + "smtp-port": "Porta SMTP", + "smtp-port-required": "É necessário informar a porta SMPT.", + "smtp-port-invalid": "Esta não parece ser uma porta SMTP válida.", + "timeout-msec": "Tempo limite (ms)", + "timeout-required": "O tempo limite é obrigatório.", + "timeout-invalid": "Este não parece ser um tempo limite válido.", + "enable-tls": "Habilitar TLS", + "tls-version": "Versão de TLS", + "enable-proxy": "Habilitar proxy", + "proxy-host": "Host de proxy", + "proxy-host-required": "O host de proxy é obrigatório.", + "proxy-port": "Porta do proxy", + "proxy-port-required": "A porta do proxy é obrigatória.", + "proxy-port-range": "A porta do proxy deve estar em um intervalo de 1 a 65535.", + "proxy-user": "Usuário do proxy", + "proxy-password": "Senha do proxy", + "send-test-mail": "Enviar e-mail de teste", + "security-settings": "Configuração de segurança", + "password-policy": "Política de senhas", + "minimum-password-length": "Comprimento mínimo da senha", + "minimum-password-length-required": "O comprimento mínimo da senha é obrigatório", + "minimum-password-length-range": "O comprimento mínimo da senha deve estar em um intervalo de 5 a 50.", + "minimum-uppercase-letters": "Número mínimo de letras maiúsculas", + "minimum-uppercase-letters-range": "O número mínimo de letras maiúsculas não pode ser negativo", + "minimum-lowercase-letters": "Número mínimo de letras minúsculas", + "minimum-lowercase-letters-range": "O número mínimo de letras minúsculas não pode ser negativo", + "minimum-digits": "Número mínimo de dígitos", + "minimum-digits-range": "O número mínimo de dígitos não pode ser negativo", + "minimum-special-characters": "Número mínimo de caracteres especiais", + "minimum-special-characters-range": "O número mínimo de caracteres especiais não pode ser negativo", + "password-expiration-period-days": "Período de expiração de senha em dias", + "password-expiration-period-days-range": "O período de expiração de senha não pode ser negativo", + "password-reuse-frequency-days": "Frequência de reutilização de senha em dias", + "password-reuse-frequency-days-range": "A frequência em dias de reutilização da senha não pode ser negativa", + "general-policy": "Política geral", + "max-failed-login-attempts": "Número máximo de tentativas de login com falha antes de bloquear a conta", + "minimum-max-failed-login-attempts-range": "O número máximo de tentativas de login com falha não pode ser negativo", + "user-lockout-notification-email": "Em caso de bloqueio de conta do usuário, enviar notificação para o endereço de e-mail" + }, + "alarm": { + "alarm": "Alarme", + "alarms": "Alarmes", + "select-alarm": "Selecionar alarme", + "no-alarms-matching": "Nenhum alarme encontrado que coincida com '{{entity}}'.", + "alarm-required": "O alarme é obrigatório", + "alarm-status": "Status do alarme", + "alarm-status-list": "Lista de status de alarmes", + "any-status": "Qualquer status", + "search-status": { + "ANY": "Qualquer", + "ACTIVE": "Ativo", + "CLEARED": "Limpo", + "ACK": "Confirmado", + "UNACK": "Não confirmado" + }, + "display-status": { + "ACTIVE_UNACK": "Não confirmado ativo", + "ACTIVE_ACK": "Confirmado ativo", + "CLEARED_UNACK": "Não confirmado limpo", + "CLEARED_ACK": "Confirmado limpo" + }, + "no-alarms-prompt": "Nenhum alarme encontrado", + "created-time": "Hora de criação", + "type": "Tipo", + "severity": "Severidade", + "originator": "Originador", + "originator-type": "Tipo de originador", + "details": "Detalhes", + "status": "Status", + "alarm-details": "Detalhes do alarme", + "start-time": "Hora de início", + "end-time": "Hora de término", + "ack-time": "Horário de confirmação", + "clear-time": "Horário de limpeza", + "alarm-severity-list": "Lista de severidade de alarmes", + "any-severity": "Qualquer severidade", + "severity-critical": "Crítica", + "severity-major": "Principal", + "severity-minor": "Menor", + "severity-warning": "Aviso", + "severity-indeterminate": "Indeterminado", + "acknowledge": "Confirmar", + "clear": "Limpar", + "search": "Pesquisar alarmes", + "selected-alarms": "{ count, plural, 1 {1 alarm} other {# alarms} } selecionado(s)", + "no-data": "Nenhum dado para exibição", + "polling-interval": "Intervalo seletivo de alarmes (s)", + "polling-interval-required": "O intervalo de chamada seletiva de alarmes é obrigatório.", + "min-polling-interval-message": "É permitido no mínimo 1 segundo de intervalo de chamada seletiva.", + "aknowledge-alarms-title": "Confirmar { count, plural, 1 {1 alarm} other {# alarms} }", + "aknowledge-alarms-text": "Tem certeza de que deseja confirmar { count, plural, 1 {1 alarm} other {# alarms} }?", + "aknowledge-alarm-title": "Confirmar alarme", + "aknowledge-alarm-text": "Tem certeza de que deseja confirmar o alarme?", + "clear-alarms-title": "Limpar { count, plural, 1 {1 alarm} other {# alarms} }", + "clear-alarms-text": "Tem certeza de que deseja limpar { count, plural, 1 {1 alarm} other {# alarms} }?", + "clear-alarm-title": "Limpar alarme", + "clear-alarm-text": "Tem certeza de que deseja limpar alarme?", + "alarm-status-filter": "Filtro de status de alarmes", + "alarm-filter": "Filtro de alarmes", + "max-count-load": "Número máximo de alarmes a serem carregados (0 - ilimitado)", + "max-count-load-required": "O número máximo de alarmes é obrigatório.", + "max-count-load-error-min": "O valor mínimo é 0.", + "fetch-size": "Tamanho da busca", + "fetch-size-required": "O tamanho da busca é obrigatório.", + "fetch-size-error-min": "O valor mínimo é 10.", + "alarm-type-list": "Lista de tipos de alarmes", + "any-type": "Qualquer tipo", + "search-propagated-alarms": "Pesquisar alarmes propagados" + }, + "alias": { + "add": "Adicionar alias", + "edit": "Editar alias", + "name": "Nome do alias", + "name-required": "O nome do alias é obrigatório", + "duplicate-alias": "Já existe um alias com o mesmo nome.", + "filter-type-single-entity": "Entidade individual", + "filter-type-entity-list": "Lista de entidades", + "filter-type-entity-name": "Nome da entidade", + "filter-type-state-entity": "Entidade do estado do dashboard", + "filter-type-state-entity-description": "Entidade obtida dos parâmetros de estado do dashboard", + "filter-type-asset-type": "Tipo de ativo", + "filter-type-asset-type-description": "Ativos do tipo '{{assetType}}'", + "filter-type-asset-type-and-name-description": "Ativos do tipo '{{assetType}}' cujo nome começa com '{{prefix}}'", + "filter-type-device-type": "Tipo de dispositivo", + "filter-type-device-type-description": "Dispositivos do tipo '{{deviceType}}'", + "filter-type-device-type-and-name-description": "Dispositivos do tipo '{{deviceType}}' cujo nome começa com '{{prefix}}'", + "filter-type-entity-view-type": "Tipo de exibições de entidades", + "filter-type-entity-view-type-description": "Exibições de entidades do tipo '{{entityView}}'", + "filter-type-entity-view-type-and-name-description": "Exibições de entidades do tipo '{{entityView}}' cujo nome começa com '{{prefix}}'", + "filter-type-relations-query": "Consulta de relações", + "filter-type-relations-query-description": "{{entities}} que tem relação de {{relationType}} {{direction}} {{rootEntity}}", + "filter-type-asset-search-query": "Consulta de pesquisa de ativo", + "filter-type-asset-search-query-description": "Ativos dos tipos {{assetTypes}} que têm relação de {{relationType}} {{direction}} {{rootEntity}}", + "filter-type-device-search-query": "Consulta de pesquisa de dispositivo", + "filter-type-device-search-query-description": "Dispositivos dos tipos {{deviceTypes}} que têm relação de {{relationType}} {{direction}} {{rootEntity}}", + "filter-type-entity-view-search-query": "Consulta de pesquisa de exibição de entidade", + "filter-type-entity-view-search-query-description": "Exibições de entidades dos tipos {{entityViewTypes}} que têm relação de {{relationType}} {{direction}} {{rootEntity}}", + "entity-filter": "Filtro de entidades", + "resolve-multiple": "Resolver como entidades múltiplas", + "filter-type": "Tipo de filtro", + "filter-type-required": "O tipo de filtro é obrigatório.", + "entity-filter-no-entity-matched": "Nenhuma entidade encontrada que coincida com o filtro especificado.", + "no-entity-filter-specified": "Nenhum filtro de entidade especificado", + "root-state-entity": "Usar entidade de estado do dashboard como raiz", + "last-level-relation": "Procurar apenas último nível de relação", + "root-entity": "Entidade raiz", + "state-entity-parameter-name": "Nome do parâmetro de entidade de estado", + "default-state-entity": "Entidade de estado padrão", + "default-entity-parameter-name": "Predefinido", + "max-relation-level": "Nível máx. de relação", + "unlimited-level": "Nível ilimitado", + "state-entity": "Entidade de estado do dashboard", + "all-entities": "Todas as entidades", + "any-relation": "qualquer" + }, + "asset": { + "asset": "Ativo", + "assets": "Ativos", + "management": "Gerenciamento de ativos", + "view-assets": "Visualizar ativos", + "add": "Adicionar ativo", + "assign-to-customer": "Atribuir a cliente", + "assign-asset-to-customer": "Atribuir ativo(s) a cliente", + "assign-asset-to-customer-text": "Selecione os ativos a serem atribuídos ao cliente", + "no-assets-text": "Nenhum ativo encontrado", + "assign-to-customer-text": "Selecione o cliente para atribuir o(s) ativo(s)", + "public": "Público", + "assignedToCustomer": "Atribuído ao cliente", + "make-public": "Tornar ativo público", + "make-private": "Tornar ativo privado", + "unassign-from-customer": "Remover atribuição a cliente", + "delete": "Excluir ativo", + "asset-public": "Ativo é público", + "asset-type": "Tipo de ativo", + "asset-type-required": "O tipo de ativo é obrigatório.", + "select-asset-type": "Selecionar tipo de ativo", + "enter-asset-type": "Inserir tipo de ativo", + "any-asset": "Qualquer ativo", + "no-asset-types-matching": "Nenhum tipo de ativo encontrado que coincida com '{{entitySubtype}}'.", + "asset-type-list-empty": "Nenhum tipo de ativo selecionado.", + "asset-types": "Tipos de ativos", + "name": "Nome", + "name-required": "O nome é obrigatório.", + "description": "Descrição", + "type": "Tipo", + "type-required": "O tipo é obrigatório.", + "details": "Detalhes", + "events": "Eventos", + "add-asset-text": "Adicionar novo ativo", + "asset-details": "Detalhes do ativo", + "assign-assets": "Atribuir ativos", + "assign-assets-text": "Atribuir { count, plural, 1 {1 asset} other {# assets} } a cliente", + "delete-assets": "Excluir ativos", + "unassign-assets": "Remover atribuição de ativos", + "unassign-assets-action-title": "Remover atribuição de { count, plural, 1 {1 asset} other {# assets} } a cliente", + "assign-new-asset": "Atribuir novo ativo", + "delete-asset-title": "Tem certeza de que deseja excluir o ativo '{{assetName}}'?", + "delete-asset-text": "Cuidado, após confirmar, não será possível recuperar o ativo e nenhum dado associado.", + "delete-assets-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 asset} other {# assets} }?", + "delete-assets-action-title": "Excluir { count, plural, 1 {1 asset} other {# assets} }", + "delete-assets-text": "Cuidado, após confirmar, todos os ativos selecionados serão removidos e não será possível recuperar nenhum dado associado.", + "make-public-asset-title": "Tem certeza de que deseja tornar o ativo '{{assetName}}' público?", + "make-public-asset-text": "Após confirmar, o ativo e todos os dados associados a ele se tornarão públicos e poderão ser acessados por outros.", + "make-private-asset-title": "Tem certeza de que deseja tornar o ativo '{{assetName}}' privado?", + "make-private-asset-text": "Após confirmar, o ativo e todos os dados associados a ele se tornarão privados e não poderão ser acessados por outros.", + "unassign-asset-title": "Tem certeza de que deseja remover a atribuição do ativo '{{assetName}}'?", + "unassign-asset-text": "Após confirmar, a atribuição do ativo será removida e ele não poderá ser acessado pelo cliente.", + "unassign-asset": "Remover atribuição de ativo", + "unassign-assets-title": "Tem certeza de que deseja remover a atribuição de { count, plural, 1 {1 asset} other {# assets} }?", + "unassign-assets-text": "Após confirmar, a atribuição de todos os ativos selecionados será removida e eles não poderão ser acessados pelo cliente.", + "copyId": "Copiar ID do ativo", + "idCopiedMessage": "O ID do ativo foi copiado para a área de transferência", + "select-asset": "Selecionar ativo", + "no-assets-matching": "Nenhum ativo encontrado que coincida com '{{entity}}'.", + "asset-required": "O ativo é obrigatório", + "name-starts-with": "O nome do ativo começa com", + "import": "Importar ativos", + "asset-file": "Arquivo de ativos", + "search": "Pesquisar ativo", + "selected-assets": "{ count, plural, 1 {1 asset} other {# assets} } selecionado(s)", + "label": "Etiqueta" + }, + "attribute": { + "attributes": "Atributos", + "latest-telemetry": "Última telemetria", + "attributes-scope": "Escopo de atributos de entidade", + "scope-latest-telemetry": "Última telemetria", + "scope-client": "Atributos do cliente", + "scope-server": "Atributos do servidor", + "scope-shared": "Atributos compartilhados", + "add": "Adicionar atributo", + "key": "Chave", + "last-update-time": "Horário da última atualização", + "key-required": "A chave de atributo é obrigatória.", + "value": "Valor", + "value-required": "O valor do atributo é obrigatório.", + "delete-attributes-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 attribute} other {# attributes} }?", + "delete-attributes-text": "Cuidado, após confirmar, todos os atributos selecionados serão removidos.", + "delete-attributes": "Excluir atributos", + "enter-attribute-value": "Inserir valor do atributo", + "show-on-widget": "Mostrar no widget", + "widget-mode": "Modo de widget", + "next-widget": "Próximo widget", + "prev-widget": "Widget anterior", + "add-to-dashboard": "Adicionar ao dashboard", + "add-widget-to-dashboard": "Adicionar widget ao dashboard", + "selected-attributes": "{ count, plural, 1 {1 attribute} other {# attributes} } selecionado(s)", + "selected-telemetry": "{ count, plural, 1 {1 telemetry unit} other {# telemetry units} } selecionado(s)", + "no-attributes-text": "Nenhum atributo encontrado", + "no-telemetry-text": "Nenhuma telemetria encontrada" + }, + "audit-log": { + "audit": "Auditoria", + "audit-logs": "Logs de auditoria", + "timestamp": "Carimbo de data/hora", + "entity-type": "Tipo de entidade", + "entity-name": "Nome da entidade", + "user": "Usuário", + "type": "Tipo", + "status": "Status", + "details": "Detalhes", + "type-added": "Adicionado", + "type-deleted": "Excluído", + "type-updated": "Atualizado", + "type-attributes-updated": "Atributos atualizados", + "type-attributes-deleted": "Atributos excluídos", + "type-rpc-call": "Chamada RPC", + "type-credentials-updated": "Credenciais atualizadas", + "type-assigned-to-customer": "Atribuído ao cliente", + "type-unassigned-from-customer": "Atribuição a cliente removida", + "type-activated": "Ativado", + "type-suspended": "Suspenso", + "type-credentials-read": "Credenciais lidas", + "type-attributes-read": "Atributos lidos", + "type-relation-add-or-update": "Relação atualizada", + "type-relation-delete": "Relação excluída", + "type-relations-delete": "Todas as relações excluídas", + "type-alarm-ack": "Confirmado", + "type-alarm-clear": "Limpo", + "type-login": "Login", + "type-logout": "Logout", + "type-lockout": "Bloqueio", + "status-success": "Êxito", + "status-failure": "Falha", + "audit-log-details": "Detalhes da trilha de auditoria", + "no-audit-logs-prompt": "Nenhum log encontrado", + "action-data": "Dados da ação", + "failure-details": "Detalhes da falha", + "search": "Pesquisar logs de auditoria", + "clear-search": "Limpar pesquisa", + "type-assigned-from-tenant": "Atribuído do locatário", + "type-assigned-to-tenant": "Atribuído a locatário" + }, + "confirm-on-exit": { + "message": "Existem alterações sem salvar. Tem certeza de que deseja sair desta página?", + "html-message": "Existem alterações sem salvar.
Tem certeza de que deseja sair desta página?", + "title": "Alterações sem salvar" + }, + "contact": { + "country": "País", + "city": "Cidade", + "state": "Estado", + "postal-code": "CEP / Código postal", + "postal-code-invalid": "Formato de código postal / CEP inválido.", + "address": "Endereço", + "address2": "Endereço 2", + "phone": "Telefone", + "email": "E-mail", + "no-address": "Em endereço" + }, + "common": { + "username": "Nome de usuário", + "password": "Senha", + "enter-username": "Inserir nome de usuário", + "enter-password": "Inserir senha", + "enter-search": "Inserir pesquisa", + "created-time": "Hora de criação", + "loading": "Carregando..." + }, + "content-type": { + "json": "Json", + "text": "Texto", + "binary": "Binário (Base64)" + }, + "customer": { + "customer": "Cliente", + "customers": "Clientes", + "management": "Gerenciamento de clientes", + "dashboard": "Dashboard de clientes", + "dashboards": "Dashboards de clientes", + "devices": "Dispositivos do cliente", + "entity-views": "Exibições de entidades do cliente", + "assets": "Ativos do cliente", + "public-dashboards": "Dashboards públicos", + "public-devices": "Dispositivos públicos", + "public-assets": "Ativos públicos", + "public-entity-views": "Exibições de entidades públicas", + "add": "Adicionar cliente", + "delete": "Excluir cliente", + "manage-customer-users": "Gerenciar usuários do cliente", + "manage-customer-devices": "Gerenciar dispositivos do cliente", + "manage-customer-dashboards": "Gerenciar dashboards do cliente", + "manage-public-devices": "Gerenciar dispositivos públicos", + "manage-public-dashboards": "Gerenciar dashboards públicos", + "manage-customer-assets": "Gerenciar ativos do cliente", + "manage-public-assets": "Gerenciar ativos públicos", + "add-customer-text": "Adicionar novo cliente", + "no-customers-text": "Nenhum cliente encontrado", + "customer-details": "Detalhes do cliente", + "delete-customer-title": "Tem certeza de que deseja excluir o ativo cliente '{{customerTitle}}'?", + "delete-customer-text": "Cuidado, após confirmar, não será possível recuperar o cliente e nenhum dado associado.", + "delete-customers-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 customer} other {# customers} }?", + "delete-customers-action-title": "Excluir { count, plural, 1 {1 customer} other {# customers} }", + "delete-customers-text": "Cuidado, após confirmar, todos os clientes selecionados serão removidos e não será possível recuperar nenhum dado associado.", + "manage-users": "Gerenciar usuários", + "manage-assets": "Gerenciar ativos", + "manage-devices": "Gerenciar dispositivos", + "manage-dashboards": "Gerenciar dashboards", + "title": "Título", + "title-required": "O título é obrigatório.", + "description": "Descrição", + "details": "Detalhes", + "events": "Eventos", + "copyId": "Copiar ID de cliente", + "idCopiedMessage": "O ID do cliente foi copiado para a área de transferência", + "select-customer": "Selecionar cliente", + "no-customers-matching": "Nenhum cliente encontrado que coincida com '{{entity}}'.", + "customer-required": "O cliente é obrigatório", + "select-default-customer": "Selecionar cliente predefinido", + "default-customer": "Cliente predefinido", + "default-customer-required": "O cliente predefinido é necessário para depurar o dashboard no nível de locatário", + "search": "Pesquisar clientes", + "selected-customers": "{ count, plural, 1 {1 customer} other {# customers} } selecionado(s)" + }, + "datetime": { + "date-from": "Data de", + "time-from": "Hora de", + "date-to": "Data até", + "time-to": "Hora até" + }, + "dashboard": { + "dashboard": "Dashboard", + "dashboards": "Dashboards", + "management": "Gerenciamento de dashboards", + "view-dashboards": "Visualizar dashboards", + "add": "Adicionar dashboard", + "assign-dashboard-to-customer": "Atribuir dashboard(s) a cliente", + "assign-dashboard-to-customer-text": "Selecione os dashboards a serem atribuídos ao cliente", + "assign-to-customer-text": "Selecione o cliente para atribuir o(s) dashboard(s)", + "assign-to-customer": "Atribuir a cliente", + "unassign-from-customer": "Remover atribuição a cliente", + "make-public": "Tornar dashboards público", + "make-private": "Tornar dashboards privado", + "manage-assigned-customers": "Gerenciar clientes atribuídos", + "assigned-customers": "Clientes atribuídos", + "assign-to-customers": "Atribuir Dashboard(s) a Clientes", + "assign-to-customers-text": "Selecione os clientes para atribuir o(s) dashboard(s)", + "unassign-from-customers": "Remover Atribuição de Dashboard(s) a Clientes", + "unassign-from-customers-text": "Selecione os clientes para remover a atribuição do(s) dashboard(s)", + "no-dashboards-text": "Nenhum dashboard encontrado", + "no-widgets": "Nenhum widgets configurado", + "add-widget": "Adicionar novo widget", + "title": "Título", + "select-widget-title": "Selecionar widget", + "select-widget-subtitle": "Lista de tipos de widget disponíveis", + "delete": "Excluir dashboard", + "title-required": "O título é obrigatório.", + "description": "Descrição", + "details": "Detalhes", + "dashboard-details": "Detalhes do dashboard", + "add-dashboard-text": "Adicionar novo dashboard", + "assign-dashboards": "Atribuir dashboards", + "assign-new-dashboard": "Atribuir novo dashboard", + "assign-dashboards-text": "Atribuir { count, plural, 1 {1 dashboard} other {# dashboards} } a clientes", + "unassign-dashboards-action-text": "Remover atribuição de { count, plural, 1 {1 dashboard} other {# dashboards} } a clientes", + "delete-dashboards": "Excluir dashboards", + "unassign-dashboards": "Remover atribuição de dashboards", + "unassign-dashboards-action-title": "Remover atribuição de { count, plural, 1 {1 dashboard} other {# dashboards} } a cliente", + "delete-dashboard-title": "Tem certeza de que deseja excluir o dashboard '{{dashboardTitle}}'?", + "delete-dashboard-text": "Cuidado, após confirmar, não será possível recuperar o dashboard e nenhum dado associado.", + "delete-dashboards-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 dashboard} other {# dashboards} }?", + "delete-dashboards-action-title": "Excluir { count, plural, 1 {1 dashboard} other {# dashboards} }", + "delete-dashboards-text": "Cuidado, após confirmar, todos os dashboards selecionados serão removidos e não será possível recuperar nenhum dado associado.", + "unassign-dashboard-title": "Tem certeza de que deseja remover a atribuição do dashboard '{{dashboardTitle}}'?", + "unassign-dashboard-text": "Após confirmar, a atribuição do dashboard será removida e ele não poderá ser acessado pelo cliente.", + "unassign-dashboard": "Remover atribuição de dashboard", + "unassign-dashboards-title": "Tem certeza de que deseja remover a atribuição de { count, plural, 1 {1 dashboard} other {# dashboards} }?", + "unassign-dashboards-text": "Após confirmar, a atribuição de todos os dashboards selecionados será removida e eles não poderão ser acessados pelo cliente.", + "public-dashboard-title": "Agora o dashboard é público", + "public-dashboard-text": "Agora o dashboard {{dashboardTitle}} é público e pode ser acessado pelo próximo link público:", + "public-dashboard-notice": "Observação: Não se esqueça de tornar dispositivos relacionados públicos para poder acessar seus dados.", + "make-private-dashboard-title": "Tem certeza de que deseja tornar o dashboard '{{dashboardTitle}}' privado?", + "make-private-dashboard-text": "Após confirmar, o dashboard e todos os dados associados a ele se tornarão privados e não poderão ser acessados por outros.", + "make-private-dashboard": "Tornar dashboards privado", + "socialshare-text": "'{{dashboardTitle}}' da plataforma ThingsBoard", + "socialshare-title": "'{{dashboardTitle}}' da plataforma ThingsBoard", + "select-dashboard": "Selecionar dashboard", + "no-dashboards-matching": "Nenhum dashboard encontrado que coincida com '{{entity}}'.", + "dashboard-required": "O dashboard é obrigatório.", + "select-existing": "Selecionar dashboard existente", + "create-new": "Criar novo dashboard", + "new-dashboard-title": "Novo título de dashboard", + "open-dashboard": "Abrir dashboard", + "set-background": "Definir plano de fundo", + "background-color": "Cor de fundo", + "background-image": "Imagem de fundo", + "background-size-mode": "Modo de tamanho de fundo", + "no-image": "Nenhuma imagem selecionada", + "drop-image": "Solte uma imagem ou clique para selecionar um arquivo para carregar.", + "settings": "Configurações", + "columns-count": "Contagem de colunas", + "columns-count-required": "A contagem de colunas é obrigatória.", + "min-columns-count-message": "Somente é permitida uma contagem de no mínimo 10 colunas.", + "max-columns-count-message": "Somente é permitida uma contagem de no máximo 1000 colunas.", + "widgets-margins": "Margem entre widgets", + "margin-required": "O valor da margem é obrigatório.", + "min-margin-message": "Somente 0 é permitido como valor mínimo de margem.", + "max-margin-message": "Somente 50 é permitido como valor máximo de margem.", + "horizontal-margin": "Margem horizontal", + "horizontal-margin-required": "O valor da margem horizontal é obrigatório.", + "min-horizontal-margin-message": "Somente 0 é permitido como valor mínimo de margem horizontal.", + "max-horizontal-margin-message": "Somente 50 é permitido como valor máximo de margem horizontal.", + "vertical-margin": "Margem vertical", + "vertical-margin-required": "O valor da margem vertical é obrigatório.", + "min-vertical-margin-message": "Somente 0 é permitido como valor mínimo de margem vertical.", + "max-vertical-margin-message": "Somente 50 é permitido como valor máximo de margem vertical.", + "autofill-height": "Altura do layout de preenchimento automático", + "mobile-layout": "Configuração de layout móvel", + "mobile-row-height": "Altura de linha móvel, px", + "mobile-row-height-required": "O valor da altura de linha móvel é obrigatório.", + "min-mobile-row-height-message": "Somente 5 pixels é permitido como valor mínimo da altura de linha móvel.", + "max-mobile-row-height-message": "Somente 200 pixels é permitido como valor máximo da altura de linha móvel.", + "display-title": "Exibir título de dashboard", + "toolbar-always-open": "Manter barra de ferramentas aberto", + "title-color": "Cor do título", + "display-dashboards-selection": "Exibir seleção de dashboards", + "display-entities-selection": "Exibir seleção de entidades", + "display-filters": "Exibir filtros", + "display-dashboard-timewindow": "Exibir timewindow", + "display-dashboard-export": "Exibir exportação", + "import": "Importar dashboard", + "export": "Exportar dashboard", + "export-failed-error": "Impossível exportar dashboard: {{error}}", + "create-new-dashboard": "Criar novo dashboard", + "dashboard-file": "Arquivo de dashboard", + "invalid-dashboard-file-error": "Impossível importar dashboard: Estrutura de dados do dashboard inválida", + "dashboard-import-missing-aliases-title": "Configurar aliases usados por dashboard importado", + "create-new-widget": "Criar novo widget", + "import-widget": "Importar widget", + "widget-file": "Arquivo de widget", + "invalid-widget-file-error": "Impossível importar widget: Estrutura de dados do widget inválida.", + "widget-import-missing-aliases-title": "Configurar aliases usados por widget importado", + "open-toolbar": "Abrir barra de ferramentas de dashboard", + "close-toolbar": "Fechar barra de ferramentas", + "configuration-error": "Erro de configuração", + "alias-resolution-error-title": "Erro de configuração de aliases de dashboard", + "invalid-aliases-config": "Impossível encontrar dispositivos que coincidam com algum dos filtros de aliases.
Para resolver esta questão, encontre em contato com seu administrador.", + "select-devices": "Selecionar dispositivos", + "assignedToCustomer": "Atribuído ao cliente", + "assignedToCustomers": "Atribuído aos clientes", + "public": "Público", + "public-link": "Link público", + "copy-public-link": "Copiar link público", + "public-link-copied-message": "O link público da dashboard foi copiado para a área de transferência", + "manage-states": "Gerenciar estados de dashboards", + "states": "Estados de dashboards", + "search-states": "Pesquisar estados de dashboards", + "selected-states": "{ count, plural, 1 {1 dashboard state} other {# dashboard states} } selecionado(s)", + "edit-state": "Editar estado de dashboard", + "delete-state": "Excluir estado de dashboard", + "add-state": "Adicionar estado de dashboard", + "no-states-text": "Nenhum estado encontrado", + "state": "Estado do dashboard", + "state-name": "Nome", + "state-name-required": "O nome do estado do dashboard é obrigatório.", + "state-id": "ID do estado", + "state-id-required": "O ID do estado do dashboard é obrigatório.", + "state-id-exists": "Já existe um estado de dashboard com o mesmo ID.", + "is-root-state": "Estado raiz", + "delete-state-title": "Excluir estado de dashboard", + "delete-state-text": "Tem certeza de que deseja excluir o estado de dashboard com o nome '{{stateName}}'?", + "show-details": "Mostrar detalhes", + "hide-details": "Ocultar detalhes", + "select-state": "Selecionar estado alvo", + "state-controller": "Controlador de estado", + "search": "Selecionar dashboards", + "selected-dashboards": "{ count, plural, 1 {1 dashboard} other {# dashboards} } selecionado(s)" + }, + "datakey": { + "settings": "Configurações", + "advanced": "Avançado", + "label": "Etiqueta", + "color": "Cor", + "units": "Símbolo especial a ser exibido ao lado do valor", + "decimals": "Número de dígitos após ponto de flutuação", + "data-generation-func": "Função de geração de dados", + "use-data-post-processing-func": "Usar função de pós-processamento de dados", + "configuration": "Configuração de dados-chave", + "timeseries": "Intervalos de tempo", + "attributes": "Atributos", + "entity-field": "Campo de entidade", + "alarm": "Campos de alarmes", + "timeseries-required": "Os intervalos de tempo de entidade são obrigatórios.", + "timeseries-or-attributes-required": "Os intervalos de tempo/atributos de entidade são obrigatórios.", + "alarm-fields-timeseries-or-attributes-required": "Os campos de alarmes ou intervalos de tempo/atributos de entidade são obrigatórios.", + "maximum-timeseries-or-attributes": "No máximo { count, plural, 1 {1 timeseries/attribute is allowed.} other {# timeseries/attributes are allowed} }", + "alarm-fields-required": "Os campos de alarmes são obrigatórios.", + "function-types": "Tipos de função", + "function-types-required": "Os tipos de função são obrigatórios.", + "maximum-function-types": "No máximo { count, plural, 1 {1 function type is allowed.} other {# function types are allowed} }", + "time-description": "carimbo de data/horário do valor atual;", + "value-description": "o valor atual;", + "prev-value-description": "resultado da chamada de função anterior;", + "time-prev-description": "carimbo de data/horário do valor anterior;", + "prev-orig-value-description": "valor original anterior;" + }, + "datasource": { + "type": "Tipo de fonte de dados", + "name": "Nome", + "add-datasource-prompt": "Adicione a fonte de dados" + }, + "details": { + "details": "Detalhes", + "edit-mode": "Modo de edição", + "edit-json": "Editar JSON", + "toggle-edit-mode": "Alternar o modo de edição" + }, + "device": { + "device": "Dispositivo", + "device-required": "O nome do dispositivo é obrigatório.", + "devices": "Dispositivos", + "management": "Gerenciamento de dispositivos", + "view-devices": "Exibir Dispositivos", + "device-alias": "Alias do dispositivo", + "aliases": "Aliases de dispositivos", + "no-alias-matching": "'{{alias}}' não encontrado.", + "no-aliases-found": "Nenhum alias encontrado.", + "no-key-matching": "'{{key}}' não encontrado.", + "no-keys-found": "Nenhuma chave encontrado.", + "create-new-alias": "Criar um novo!", + "create-new-key": "Criar um novo!", + "duplicate-alias-error": "Alias '{{alias}}' duplicado encontrado.
Os aliases de dispositivos devem ser exclusivos no dashboard.", + "configure-alias": "Configurar alias de '{{alias}}'", + "no-devices-matching": "Nenhum dispositivo encontrado que coincida com '{{entity}}'.", + "alias": "Alias", + "alias-required": "O alias do dispositivo é obrigatório.", + "remove-alias": "Remover alias do dispositivo", + "add-alias": "Adicionar alias do dispositivo", + "name-starts-with": "O nome do dispositivo começa com", + "device-list": "Lista de dispositivos", + "use-device-name-filter": "Usar filtro", + "device-list-empty": "Nenhum dispositivo selecionado.", + "device-name-filter-required": "O filtro do nome do dispositivo é obrigatório.", + "device-name-filter-no-device-matched": "Nenhum dispositivo encontrado que comece com '{{device}}'.", + "add": "Adicionar Dispositivo", + "assign-to-customer": "Atribuir a cliente", + "assign-device-to-customer": "Atribuir Dispositivo(s) a Cliente", + "assign-device-to-customer-text": "Selecione os dispositivos a serem atribuídos ao cliente", + "make-public": "Tornar dispositivo público", + "make-private": "Tornar dispositivo privado", + "no-devices-text": "Nenhum dispositivo encontrado", + "assign-to-customer-text": "Selecione cliente para atribuir o(s) dispositivo(s)", + "device-details": "Detalhes do dispositivo", + "add-device-text": "Adicionar novo dispositivo", + "credentials": "Credenciais", + "manage-credentials": "Gerenciar credenciais", + "delete": "Excluir dispositivo", + "assign-devices": "Atribuir dispositivos", + "assign-devices-text": "Atribuir { count, plural, 1 {1 device} other {# devices} } a cliente", + "delete-devices": "Excluir dispositivos", + "unassign-from-customer": "Remover atribuição a cliente", + "unassign-devices": "Remover atribuição de dispositivos", + "unassign-devices-action-title": "Remover atribuição de { count, plural, 1 {1 device} other {# devices} } a cliente", + "assign-new-device": "Atribuir novo dispositivo", + "make-public-device-title": "Tem certeza de que deseja tornar o dispositivo '{{deviceName}}' público?", + "make-public-device-text": "Após confirmar, o dispositivo e todos os dados associados a ele se tornarão públicos e poderão ser acessados por outros.", + "make-private-device-title": "Tem certeza de que deseja tornar o dispositivo '{{deviceName}}' privado?", + "make-private-device-text": "Após confirmar, o dispositivo e todos os dados associados a ele se tornarão privados e não poderão ser acessados por outros.", + "view-credentials": "Exibir credenciais", + "delete-device-title": "Tem certeza de que deseja excluir o dispositivo '{{deviceName}}'?", + "delete-device-text": "Cuidado, após confirmar, não será possível recuperar o dispositivo e nenhum dado associado.", + "delete-devices-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 device} other {# devices} }?", + "delete-devices-action-title": "Excluir { count, plural, 1 {1 device} other {# devices} }", + "delete-devices-text": "Cuidado, após confirmar, todos os dispositivos selecionados serão removidos e não será possível recuperar nenhum dado associado.", + "unassign-device-title": "Tem certeza de que deseja remover a atribuição do dispositivo '{{deviceName}}'?", + "unassign-device-text": "Após confirmar, a atribuição do dispositivo será removida e ele não poderá ser acessado pelo cliente.", + "unassign-device": "Remover atribuição de dispositivo", + "unassign-devices-title": "Tem certeza de que deseja remover a atribuição de { count, plural, 1 {1 device} other {# devices} }?", + "unassign-devices-text": "Após confirmar, a atribuição de todos os dispositivos selecionados será removida e eles não poderão ser acessados pelo cliente.", + "device-credentials": "Credenciais do dispositivo", + "credentials-type": "Tipo de credencial", + "access-token": "Token de acesso", + "access-token-required": "O token de acesso é obrigatório.", + "access-token-invalid": "O token de acesso deve ter de 1 a 20 caracteres.", + "rsa-key": "Chave pública RSA", + "rsa-key-required": "A chave pública RSA é obrigatória.", + "secret": "Segredo", + "secret-required": "O segredo é obrigatório.", + "device-type": "Tipo de dispositivo", + "device-type-required": "O tipo de dispositivo é obrigatório.", + "select-device-type": "Selecionar tipo de dispositivo", + "enter-device-type": "Inserir tipo de dispositivo", + "any-device": "Qualquer dispositivo", + "no-device-types-matching": "Nenhum tipo de dispositivo encontrado que coincida com '{{entitySubtype}}'.", + "device-type-list-empty": "Nenhum tipo de dispositivo selecionado.", + "device-types": "Tipos de dispositivos", + "name": "Nome", + "name-required": "O nome é obrigatório.", + "description": "Descrição", + "label": "Etiqueta", + "events": "Eventos", + "details": "Detalhes", + "copyId": "Copiar ID de dispositivo", + "copyAccessToken": "Copiar token de acesso", + "idCopiedMessage": "O ID do dispositivo foi copiado para a área de transferência", + "accessTokenCopiedMessage": "O token de acesso foi copiado para a área de transferência", + "assignedToCustomer": "Atribuído ao cliente", + "unable-delete-device-alias-title": "Impossível excluir alias do dispositivo", + "unable-delete-device-alias-text": "Não é possível excluir o alias do dispositivo '{{deviceAlias}}' porque ele é usado pelo(s) seguinte(s) widget(s):
{{widgetsList}}", + "is-gateway": "É gateway", + "public": "Público", + "device-public": "O dispositivo é público", + "select-device": "Selecionar dispositivo", + "import": "Importar dispositivo", + "device-file": "Arquivo de dispositivo", + "search": "Pesquisar dispositivos", + "selected-devices": "{ count, plural, 1 {1 device} other {# devices} } selecionado(s)" + }, + "dialog": { + "close": "Fechar diálogo" + }, + "direction": { + "column": "Coluna", + "row": "Linha" + }, + "error": { + "unable-to-connect": "Impossível conectar ao servidor! Verifique sua conexão à Internet.", + "unhandled-error-code": "Código de erro sem tratamento: {{errorCode}}", + "unknown-error": "Erro desconhecido" + }, + "entity": { + "entity": "Entidade", + "entities": "Entidades", + "aliases": "Aliases de entidades", + "entity-alias": "Alias de entidade", + "unable-delete-entity-alias-title": "Impossível excluir alias de entidade", + "unable-delete-entity-alias-text": "Não é possível excluir o alias de entidade '{{entityAlias}}' porque ele é usado pelo(s) seguinte(s) widget(s):
{{widgetsList}}", + "duplicate-alias-error": "Alias '{{alias}}' duplicado encontrado.
Os aliases de entidades devem ser exclusivos no dashboard.", + "missing-entity-filter-error": "Filtro ausente para o alias '{{alias}}'.", + "configure-alias": "Configurar alias de '{{alias}}'", + "alias": "Alias", + "alias-required": "O alias de entidade é obrigatório.", + "remove-alias": "Remover alias de entidade", + "add-alias": "Adicionar alias de entidade", + "entity-list": "Lista de entidades", + "entity-type": "Tipo de entidade", + "entity-types": "Tipos de entidades", + "entity-type-list": "Lista de tipos de entidades", + "any-entity": "Qualquer entidade", + "enter-entity-type": "Inserir tipo de entidade", + "no-entities-matching": "Nenhuma entidade encontrada que coincida com '{{entity}}'.", + "no-entity-types-matching": "Nenhum tipo de entidade encontrado que coincida com '{{entityType}}'.", + "name-starts-with": "O nome começa com", + "use-entity-name-filter": "Usar filtro", + "entity-list-empty": "Nenhuma entidade selecionada.", + "entity-type-list-empty": "Nenhum tipo de entidade selecionado.", + "entity-name-filter-required": "O filtro do nome de entidade é obrigatório.", + "entity-name-filter-no-entity-matched": "Nenhuma entidade encontrada que comece com '{{entity}}'.", + "all-subtypes": "Tudo", + "select-entities": "Selecionar entidades", + "no-aliases-found": "Nenhum alias encontrado.", + "no-alias-matching": "'{{alias}}' não encontrado.", + "create-new-alias": "Criar um novo!", + "key": "Chave", + "key-name": "Nome da chave", + "no-keys-found": "Nenhuma chave encontrado.", + "no-key-matching": "'{{key}}' não encontrado.", + "create-new-key": "Criar um novo!", + "type": "Tipo", + "type-required": "O tipo de entidade é obrigatório.", + "type-device": "Dispositivo", + "type-devices": "Dispositivos", + "list-of-devices": "{ count, plural, 1 {One device} other {List of # devices} }", + "device-name-starts-with": "Dispositivos cujos nomes começam com '{{prefix}}'", + "type-asset": "Ativo", + "type-assets": "Ativos", + "list-of-assets": "{ count, plural, 1 {One asset} other {List of # assets} }", + "asset-name-starts-with": "Ativos cujos nomes começam com '{{prefix}}'", + "type-entity-view": "Exibição de entidade", + "type-entity-views": "Exibições de entidades", + "list-of-entity-views": "{ count, plural, 1 {One entity view} other {List of # entity views} }", + "entity-view-name-starts-with": "Exibições de entidades cujos nomes começam com '{{prefix}}'", + "type-rule": "Regra", + "type-rules": "Regras", + "list-of-rules": "{ count, plural, 1 {One rule} other {List of # rules} }", + "rule-name-starts-with": "Regras cujos nomes começam com '{{prefix}}'", + "type-plugin": "Plug-in", + "type-plugins": "Plug-ins", + "list-of-plugins": "{ count, plural, 1 {One plugin} other {List of # plugins} }", + "plugin-name-starts-with": "Plug-ins cujos nomes começam com '{{prefix}}'", + "type-tenant": "Locatário", + "type-tenants": "Locatários", + "list-of-tenants": "{ count, plural, 1 {One tenant} other {List of # tenants} }", + "tenant-name-starts-with": "Locatários cujos nomes começam com '{{prefix}}'", + "type-customer": "Cliente", + "type-customers": "Clientes", + "list-of-customers": "{ count, plural, 1 {One customer} other {List of # customers} }", + "customer-name-starts-with": "Clientes cujos nomes começam com '{{prefix}}'", + "type-user": "Usuário", + "type-users": "Usuários", + "list-of-users": "{ count, plural, 1 {One user} other {List of # users} }", + "user-name-starts-with": "Usuários cujos nomes começam com '{{prefix}}'", + "type-dashboard": "Dashboard", + "type-dashboards": "Dashboards", + "list-of-dashboards": "{ count, plural, 1 {One dashboard} other {List of # dashboards} }", + "dashboard-name-starts-with": "Dashboards cujos nomes começam com '{{prefix}}'", + "type-alarm": "Alarme", + "type-alarms": "Alarmes", + "list-of-alarms": "{ count, plural, 1 {One alarms} other {List of # alarms} }", + "alarm-name-starts-with": "Alarmes cujos nomes começam com '{{prefix}}'", + "type-rulechain": "Cadeia de regras", + "type-rulechains": "Cadeias de regras", + "list-of-rulechains": "{ count, plural, 1 {One rule chain} other {List of # rule chains} }", + "rulechain-name-starts-with": "Cadeias de regras cujos nomes começam com '{{prefix}}'", + "type-rulenode": "Nó de regra", + "type-rulenodes": "Nós de regras", + "list-of-rulenodes": "{ count, plural, 1 {One rule node} other {List of # rule nodes} }", + "rulenode-name-starts-with": "Nós de regras cujos nomes começam com '{{prefix}}'", + "type-current-customer": "Cliente atual", + "type-current-tenant": "Locatário atual", + "type-current-user": "Usuário atual", + "type-current-user-owner": "Proprietário de usuário atual", + "search": "Pesquisar entidades", + "selected-entities": "{ count, plural, 1 {1 entity} other {# entities} } selecionado(s)", + "entity-name": "Nome da entidade", + "entity-label": "Etiqueta de entidade", + "details": "Detalhes de entidades", + "no-entities-prompt": "Nenhuma entidade encontrada", + "no-data": "Nenhum dado para exibição", + "columns-to-display": "Colunas para Exibição" + }, + "entity-field": { + "created-time": "Hora de criação", + "name": "Nome", + "type": "Tipo", + "first-name": "Nome", + "last-name": "Sobrenome", + "email": "E-mail", + "title": "Título", + "country": "País", + "state": "Estado", + "city": "Cidade", + "address": "Endereço", + "address2": "Endereço 2", + "zip": "CEP", + "phone": "Telefone", + "label": "Etiqueta" + }, + "entity-view": { + "entity-view": "Exibição de entidade", + "entity-view-required": "A exibição de entidade é obrigatória.", + "entity-views": "Exibições de entidades", + "management": "Habilitar gerenciamento de exibições", + "view-entity-views": "Visualizar exibições de entidades", + "entity-view-alias": "Alias de exibição de entidade", + "aliases": "Aliases de exibições de entidades", + "no-alias-matching": "'{{alias}}' não encontrado.", + "no-aliases-found": "Nenhum alias encontrado.", + "no-key-matching": "'{{key}}' não encontrado.", + "no-keys-found": "Nenhuma chave encontrado.", + "create-new-alias": "Criar um novo!", + "create-new-key": "Criar um novo!", + "duplicate-alias-error": "Alias '{{alias}}' duplicado encontrado.
Os aliases de exibições de entidades devem ser exclusivos no dashboard.", + "configure-alias": "Configurar alias de '{{alias}}'", + "no-entity-views-matching": "Nenhuma exibição de entidade encontrada que coincida com '{{entity}}'.", + "public": "Público", + "alias": "Alias", + "alias-required": "O alias de exibição de entidade é obrigatório.", + "remove-alias": "Remover alias de exibição de entidade", + "add-alias": "Adicionar alias de exibição de entidade", + "name-starts-with": "O nome da exibição de entidade começa com", + "entity-view-list": "Lista de exibições de entidades", + "use-entity-view-name-filter": "Usar filtro", + "entity-view-list-empty": "Nenhuma exibição de entidade selecionada.", + "entity-view-name-filter-required": "O filtro de nomes de exibições de entidades é obrigatório.", + "entity-view-name-filter-no-entity-view-matched": "Nenhuma exibição de entidade encontrada que comece com '{{entityView}}'.", + "add": "Adicionar exibição de entidade", + "entity-view-public": "A exibição de entidade é pública", + "assign-to-customer": "Atribuir a cliente", + "assign-entity-view-to-customer": "Atribuir Exibição(ões) de entidade(s) a Cliente", + "assign-entity-view-to-customer-text": "Selecione as exibições de entidades a serem atribuídas ao cliente", + "no-entity-views-text": "Nenhuma exibição de entidade encontrada", + "assign-to-customer-text": "Selecione o cliente para atribuir a(s) exibição(ões) de entidade(s)", + "entity-view-details": "Detalhes de exibições de entidades", + "add-entity-view-text": "Adicionar nova exibição de entidade", + "delete": "Excluir exibição de entidade", + "assign-entity-views": "Atribuir exibições de entidades", + "assign-entity-views-text": "Atribuir { count, plural, 1 {1 entity view} other {# entity views} } a cliente", + "delete-entity-views": "Excluir exibições de entidades", + "unassign-from-customer": "Remover atribuição a cliente", + "unassign-entity-views": "Remover atribuição de exibições de entidades", + "unassign-entity-views-action-title": "Remover atribuição de { count, plural, 1 {1 entity view} other {# entity views} } a cliente", + "assign-new-entity-view": "Atribuir nova exibição de entidade", + "delete-entity-view-title": "Tem certeza de que deseja excluir a exibição de entidade '{{entityViewName}}'?", + "delete-entity-view-text": "Cuidado, após confirmar, não será possível recuperar a exibição de entidade e todos os dados associados.", + "delete-entity-views-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 entity view} other {# entity views} }?", + "delete-entity-views-action-title": "Excluir { count, plural, 1 {1 entity view} other {# entity views} }", + "delete-entity-views-text": "Cuidado, após confirmar, todas as exibições de entidades selecionadas serão removidas e não será possível recuperar nenhum dado associado.", + "unassign-entity-view-title": "Tem certeza de que deseja remover a atribuição da exibição de entidade '{{entityViewName}}'?", + "unassign-entity-view-text": "Após confirmar, a atribuição da exibição de entidade será removida e ela não poderá ser acessada pelo cliente.", + "unassign-entity-view": "Remover atribuição de exibição de entidade", + "unassign-entity-views-title": "Tem certeza de que deseja remover a atribuição de { count, plural, 1 {1 entity view} other {# entity views} }?", + "unassign-entity-views-text": "Após confirmar, a atribuição de todas as exibições de entidade selecionadas será removida e elas não poderão ser acessadas pelo cliente.", + "entity-view-type": "Tipo de exibições de entidades", + "entity-view-type-required": "O tipo de exibição de entidade é obrigatório.", + "select-entity-view-type": "Selecionar tipo de exibição de entidade", + "enter-entity-view-type": "Inserir tipo de exibição de entidade", + "any-entity-view": "Qualquer exibição de entidade", + "no-entity-view-types-matching": "Nenhum tipo de exibição de entidade encontrado que coincida com '{{entitySubtype}}'.", + "entity-view-type-list-empty": "Nenhum tipo de exibição de entidade selecionado.", + "entity-view-types": "Tipos de exibição de entidade", + "created-time": "Hora de criação", + "name": "Nome", + "name-required": "O nome é obrigatório.", + "description": "Descrição", + "events": "Eventos", + "details": "Detalhes", + "copyId": "Copiar ID de exibição de entidade", + "idCopiedMessage": "O ID de exibição de entidade foi copiado para a área de transferência", + "assignedToCustomer": "Atribuído ao cliente", + "unable-entity-view-device-alias-title": "Impossível excluir alias de exibição de entidade", + "unable-entity-view-device-alias-text": "Não é possível excluir o alias do dispositivo '{{entityViewAlias}}' porque ele é usado pelo(s) seguinte(s) widget(s):
{{widgetsList}}", + "select-entity-view": "Selecionar exibição de entidade", + "make-public": "Tornar exibição de entidade pública", + "make-private": "Tornar exibição de entidade privada", + "start-date": "Data de início", + "start-ts": "Hora de início", + "end-date": "Data de término", + "end-ts": "Hora de término", + "date-limits": "Limites de datas", + "client-attributes": "Atributos do cliente", + "shared-attributes": "Atributos compartilhados", + "server-attributes": "Atributos do servidor", + "timeseries": "Intervalos de tempo", + "client-attributes-placeholder": "Atributos do cliente", + "shared-attributes-placeholder": "Atributos compartilhados", + "server-attributes-placeholder": "Atributos do servidor", + "timeseries-placeholder": "Intervalos de tempo", + "target-entity": "Entidade alvo", + "attributes-propagation": "Propagação de atributos", + "attributes-propagation-hint": "A exibição de entidade copiará automaticamente atributos especificados da entidade alvo sempre que essa exibição de entidade for salva ou atualizada. Por questões de desempenho, os atributos da entidade alvo não são propagados para a exibição de entidade em cada alteração de atributo. A propagação automática pode ser habilitada configurando o nó de regra \"copiar para exibição\" na cadeia de regras e vinculando mensagens de \"Pós-Atributos\" e \"Atributos Atualizados\" ao novo nó de regra.", + "timeseries-data": "Dados de intervalos de tempo", + "timeseries-data-hint": "Configurar chaves de dados de intervalos de tempo da entidade alvo que estará acessível para a exibição de entidade. Esses dados de intervalo de tempo são apenas de leitura.", + "make-public-entity-view-title": "Tem certeza de que deseja tornar a exibição de entidade '{{entityViewName}}' pública?", + "make-public-entity-view-text": "Após confirmar, a exibição de entidade e todos os dados associados a ela se tornarão públicos e poderão ser acessados por outros.", + "make-private-entity-view-title": "Tem certeza de que deseja tornar a exibição de entidade '{{entityViewName}}' privada?", + "make-private-entity-view-text": "Após confirmar, a exibição de entidade e todos os dados associados a ela se tornarão privados e não poderão ser acessados por outros.", + "search": "Pesquisar exibições de entidades", + "selected-entity-views": "{ count, plural, 1 {1 entity view} other {# entity views} } selecionado(s)" + }, + "event": { + "event-type": "Tipo de evento", + "type-error": "Erro", + "type-lc-event": "Evento de ciclo de vida", + "type-stats": "Estatísticas", + "type-debug-rule-node": "Depuração", + "type-debug-rule-chain": "Depuração", + "no-events-prompt": "Nenhum evento encontrado", + "error": "Erro", + "alarm": "Alarme", + "event-time": "Tempo de evento", + "server": "Servidor", + "body": "Corpo", + "method": "Método", + "type": "Tipo", + "entity": "Entidade", + "message-id": "ID de mensagem", + "message-type": "Tipo de Mensagem", + "data-type": "Tipo de Dados", + "relation-type": "Tipo de Relação", + "metadata": "Metadados", + "data": "Dados", + "event": "Evento", + "status": "Status", + "success": "Êxito", + "failed": "Falhou", + "messages-processed": "Mensagens processadas", + "errors-occurred": "Erros" + }, + "extension": { + "extensions": "Extensões", + "selected-extensions": "{ count, plural, 1 {1 extension} other {# extensions} } selecionado(s)", + "type": "Tipo", + "key": "Chave", + "value": "Valor", + "id": "ID", + "extension-id": "ID da extensão", + "extension-type": "Tipo de extensão", + "transformer-json": "JSON *", + "unique-id-required": "O ID de extensão atual já existe.", + "delete": "Excluir extensão", + "add": "Adicionar extensão", + "edit": "Editar extensão", + "delete-extension-title": "Tem certeza de que deseja excluir a extensão '{{extensionId}}'?", + "delete-extension-text": "Cuidado, após confirmar, não será possível recuperar a extensão e nenhum dado associado.", + "delete-extensions-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 extension} other {# extensions} }?", + "delete-extensions-text": "Cuidado, após confirmar, todas as extensões selecionadas serão removidas.", + "converters": "Conversores", + "converter-id": "ID do conversor", + "configuration": "Configuração", + "converter-configurations": "Configurações de conversores", + "token": "Token de segurança", + "add-converter": "Adicionar conversor", + "add-config": "Adicionar configuração de conversor", + "device-name-expression": "Expressão do nome do dispositivo", + "device-type-expression": "Expressão do tipo de dispositivo", + "custom": "Personalizado", + "to-double": "Duplicar", + "transformer": "Transformador", + "json-required": "O json do transformador é obrigatório", + "json-parse": "Impossível analisar json do transformador", + "attributes": "Atributos", + "add-attribute": "Adicionar atributo", + "add-map": "Adicionar elemento de mapeamento", + "timeseries": "Intervalos de tempo", + "add-timeseries": "Adicionar intervalos de tempo", + "field-required": "O campo é obrigatório", + "brokers": "Brokers", + "add-broker": "Adicionar broker", + "host": "Host", + "port": "Porta", + "port-range": "A porta deve estar em um intervalo de 1 a 65535.", + "ssl": "Ssl", + "credentials": "Credenciais", + "username": "Nome de usuário", + "password": "Senha", + "retry-interval": "Intervalo de repetição em milissegundos", + "anonymous": "Anônimo", + "basic": "Básico", + "pem": "PEM", + "ca-cert": "Arquivo de certificado da Autoridade de Certificação *", + "private-key": "Arquivo de chave privada *", + "cert": "Arquivo de certificado *", + "no-file": "Nenhum arquivo selecionado.", + "drop-file": "Solte um arquivo ou clique para selecionar um arquivo para carregar.", + "mapping": "Mapeamento", + "topic-filter": "Filtro de tópico", + "converter-type": "Tipo de conversor", + "converter-json": "Json", + "json-name-expression": "Expressão json do nome do dispositivo", + "topic-name-expression": "Expressão de tópico do nome do dispositivo", + "json-type-expression": "Expressão json do tipo de dispositivo", + "topic-type-expression": "Expressão de tópico do tipo de dispositivo", + "attribute-key-expression": "Expressão da chave de atributo", + "attr-json-key-expression": "Expressão json da chave de atributo", + "attr-topic-key-expression": "Expressão de tópico da chave de atributo", + "request-id-expression": "Expressão de ID da solicitação", + "request-id-json-expression": "Expressão json de ID da solicitação", + "request-id-topic-expression": "Expressão de tópico de ID da solicitação", + "response-topic-expression": "Expressão de tópico de resposta", + "value-expression": "Expressão do valor", + "topic": "Tópico", + "timeout": "Tempo limite em milissegundos", + "converter-json-required": "Json do conversor é obrigatório.", + "converter-json-parse": "Impossível analisar json do conversor", + "filter-expression": "Expressão do filtro", + "connect-requests": "Solicitações de conexão", + "add-connect-request": "Adicionar solicitação de conexão", + "disconnect-requests": "Solicitações de desconexão", + "add-disconnect-request": "Adicionar solicitação de desconexão", + "attribute-requests": "Solicitações de atributo", + "add-attribute-request": "Adicionar solicitação de atributo", + "attribute-updates": "Atualizações de atributos", + "add-attribute-update": "Adicionar atualização de atributo", + "server-side-rpc": "RPC no lado do servidor", + "add-server-side-rpc-request": "Adicionar solicitação de RPC no lado do servidor", + "device-name-filter": "Filtro do nome do dispositivo", + "attribute-filter": "Filtro de atributo", + "method-filter": "Filtro de método", + "request-topic-expression": "Expressão de tópico de solicitação", + "response-timeout": "Tempo limite de resposta em milissegundos", + "topic-expression": "Expressão de tópico", + "client-scope": "Escopo de cliente", + "add-device": "Adicionar dispositivo", + "opc-server": "Servidores", + "opc-add-server": "Adicionar servidor", + "opc-add-server-prompt": "Adicione servidor", + "opc-application-name": "Nome do aplicativo", + "opc-application-uri": "Uri do aplicativo", + "opc-scan-period-in-seconds": "Período de digitalização em segundos", + "opc-security": "Segurança", + "opc-identity": "Identidade", + "opc-keystore": "Keystore", + "opc-type": "Tipo", + "opc-keystore-type": "Tipo", + "opc-keystore-location": "Localização*", + "opc-keystore-password": "Senha", + "opc-keystore-alias": "Alias", + "opc-keystore-key-password": "Senha principal", + "opc-device-node-pattern": "Padrão de nó de dispositivo", + "opc-device-name-pattern": "Padrão de nome de dispositivo", + "modbus-server": "Servidores/subordinados", + "modbus-add-server": "Adicionar servidor/subordinado", + "modbus-add-server-prompt": "Adicione servidor/subordinado", + "modbus-transport": "Transporte", + "modbus-tcp-reconnect": "Reconectar automaticamente", + "modbus-rtu-over-tcp": "RTU sobre TCP", + "modbus-port-name": "Nome da porta serial", + "modbus-encoding": "Codificação", + "modbus-parity": "Paridade", + "modbus-baudrate": "Taxa de transmissão", + "modbus-databits": "Bits de dados", + "modbus-stopbits": "Bits de parada", + "modbus-databits-range": "Os bits de dados devem estar em um intervalo de 7 a 8.", + "modbus-stopbits-range": "Os bits de parada devem estar em um intervalo de 1 a 2.", + "modbus-unit-id": "ID da unidade", + "modbus-unit-id-range": "O ID da unidade deve estar em um intervalo de 1 a 247.", + "modbus-device-name": "Nome do dispositivo", + "modbus-poll-period": "Período de sondagem (ms)", + "modbus-attributes-poll-period": "Atributos do período de sondagem (ms)", + "modbus-timeseries-poll-period": "Intervalos de tempo do período de sondagem (ms)", + "modbus-poll-period-range": "O período de sondagem deve ser um valor positivo.", + "modbus-tag": "Etiqueta", + "modbus-function": "Função", + "modbus-register-address": "Endereço do registro", + "modbus-register-address-range": "O endereço do registro deve estar em um intervalo de 0 a 65535.", + "modbus-register-bit-index": "Índice de bits", + "modbus-register-bit-index-range": "O índice de bits deve estar em um intervalo de 0 a 15.", + "modbus-register-count": "Contagem de registro", + "modbus-register-count-range": "A contagem de registro deve ser um valor positivo.", + "modbus-byte-order": "Ordem de byte", + "sync": { + "status": "Status", + "sync": "Sincronizar", + "not-sync": "Não sincronizar", + "last-sync-time": "Horário da última sincronização", + "not-available": "Não disponível" + }, + "export-extensions-configuration": "Exportar configuração de extensões", + "import-extensions-configuration": "Importar configuração de extensões", + "import-extensions": "Importar extensões", + "import-extension": "Importar extensão", + "export-extension": "Exportar extensão", + "file": "Arquivo de extensões", + "invalid-file-error": "Arquivo de extensão inválido" + }, + "filter": { + "add": "Adicionar filtro", + "edit": "Editar filtro", + "name": "Nome do filtro", + "name-required": "O nome do filtro é obrigatório.", + "duplicate-filter": "Já existe um filtro com o mesmo nome.", + "filters": "Filtros", + "unable-delete-filter-title": "Impossível excluir filtro", + "unable-delete-filter-text": "Não é possível excluir o filtro '{{filter}}' porque ele é usado pelo(s) seguinte(s) widget(s):
{{widgetsList}}", + "duplicate-filter-error": "Filtro '{{filter}}' duplicado encontrado.
Os filtros devem ser exclusivos no dashboard.", + "missing-key-filters-error": "Filtros chave ausentes para o filtro '{{filter}}'.", + "filter": "Filtro", + "editable": "Editável", + "no-filters-found": "Nenhum filtro encontrado.", + "no-filter-matching": "'{{filter}}' não encontrado.", + "create-new-filter": "Criar um novo!", + "filter-required": "O filtro é obrigatório.", + "operation": { + "operation": "Operação", + "equal": "igual", + "not-equal": "diferente", + "starts-with": "começa com", + "ends-with": "termina com", + "contains": "contém", + "not-contains": "não contém", + "greater": "maior que", + "less": "menor que", + "greater-or-equal": "maior ou igual", + "less-or-equal": "menor ou igual", + "and": "e", + "or": "ou" + }, + "ignore-case": "Ignorar caixa", + "value": "Valor", + "remove-filter": "Remover filtro", + "no-filters": "Nenhum filtro configurado", + "add-filter": "Adicionar filtro", + "add-complex-filter": "Adicionar filtro complexo", + "add-complex": "Adicionar complexo", + "complex-filter": "Filtro complexo", + "edit-complex-filter": "Editar filtro complexo", + "edit-filter-user-params": "Editar parâmetros de usuário de predicado de filtro", + "user-parameters": "Parâmetros de usuário", + "display-label": "Etiqueta para exibição", + "autogenerated-label": "Gerar etiqueta automaticamente", + "order-priority": "Prioridade de ordem de campo", + "key-filter": "Filtro chave", + "key-filters": "Filtros chave", + "key-name": "Nome da chave", + "key-name-required": "O nome da chave é obrigatório.", + "key-type": { + "key-type": "Tipo de chave", + "attribute": "Atributo", + "timeseries": "Intervalos de tempo", + "entity-field": "Campo de entidade" + }, + "value-type": { + "value-type": "Tipo de valor", + "string": "Cadeia de caracteres", + "numeric": "Numérico", + "boolean": "Booliano", + "date-time": "Data/Hora" + }, + "value-type-required": "O tipo de valor chave é obrigatório.", + "key-value-type-change-title": "Tem certeza de que deseja alterar o tipo de valor chave?", + "key-value-type-change-message": "Se confirmar o novo tipo de valor, todos os filtros chave inseridos serão removidos.", + "no-key-filters": "Nenhum filtro chave configurado", + "add-key-filter": "Adicionar filtro chave", + "remove-key-filter": "Remover filtro chave", + "edit-key-filter": "Editar filtro chave", + "date": "Data", + "time": "Hora", + "current-tenant": "Locatário atual", + "current-customer": "Cliente atual", + "current-user": "Usuário atual", + "default-value": "Valor padrão", + "dynamic-source-type": "Tipo de fonte dinâmica", + "no-dynamic-value": "Nenhum valor dinâmico", + "source-attribute": "Atributo da fonte", + "switch-to-dynamic-value": "Alternar para valor dinâmico", + "switch-to-default-value": "Alternar para valor padrão" + }, + "fullscreen": { + "expand": "Expandir para tela inteira", + "exit": "Sair de tela inteira", + "toggle": "Alternar modo de tela inteira", + "fullscreen": "Tela inteira" + }, + "function": { + "function": "Função" + }, + "gateway": { + "add-entry": "Adicionar configuração", + "connector-add": "Adicionar novo conector", + "connector-enabled": "Habilitar conector", + "connector-name": "Nome do conector", + "connector-name-required": "O nome do conector é obrigatório.", + "connector-type": "Tipo de conector", + "connector-type-required": "O tipo de conector é obrigatório.", + "connectors": "Configuração de conectores", + "create-new-gateway": "Criar um novo gateway", + "create-new-gateway-text": "Tem certeza de que deseja criar um novo gateway com o nome: '{{gatewayName}}'?", + "delete": "Excluir configuração", + "download-tip": "Download de arquivo de configuração", + "gateway": "Gateway", + "gateway-exists": "Já existe um dispositivo com o mesmo nome.", + "gateway-name": "Nome do gateway", + "gateway-name-required": "O nome do gateway é obrigatório.", + "gateway-saved": "A configuração do gateway foi salva corretamente.", + "json-parse": "JSON inválido.", + "json-required": "O campo não pode estar em branco.", + "no-connectors": "Sem conectores", + "no-data": "Sem configurações", + "no-gateway-found": "Nenhum gateway encontrado.", + "no-gateway-matching": " '{{item}}' não encontrado.", + "path-logs": "Caminho para arquivos de log", + "path-logs-required": "O caminho é obrigatório", + "remote": "Configuração remota", + "remote-logging-level": "Nível de registro em log", + "remove-entry": "Remover configuração", + "save-tip": "Salvar arquivo de configuração", + "security-type": "Tipo de segurança", + "security-types": { + "access-token": "Token de Acesso", + "tls": "TLS" + }, + "storage": "Armazenamento", + "storage-max-file-records": "Número máximo de registros em arquivo", + "storage-max-files": "Número máximo de arquivos", + "storage-max-files-min": "O número mínimo é 1.", + "storage-max-files-pattern": "O número não é válido.", + "storage-max-files-required": "O número é obrigatório.", + "storage-max-records": "Número máximo de registros em armazenamento", + "storage-max-records-min": "O número mínimo de registros é 1.", + "storage-max-records-pattern": "O número não é válido.", + "storage-max-records-required": "O número máximo de registros é obrigatório.", + "storage-pack-size": "Tamanho máximo de pacote de eventos", + "storage-pack-size-min": "O número mínimo é 1.", + "storage-pack-size-pattern": "O número não é válido.", + "storage-pack-size-required": "O tamanho máximo de pacote de eventos é obrigatório.", + "storage-path": "Caminho de armazenamento", + "storage-path-required": "O caminho de armazenamento é obrigatório.", + "storage-type": "Tipo de armazenamento", + "storage-types": { + "file-storage": "Armazenamento de arquivo", + "memory-storage": "Armazenamento de memória" + }, + "thingsboard": "ThingsBoard", + "thingsboard-host": "Host ThingsBoard", + "thingsboard-host-required": "O host é obrigatório.", + "thingsboard-port": "Porta ThingsBoard", + "thingsboard-port-max": "O número máximo de portas é 65535.", + "thingsboard-port-min": "O número mínimo de portas é 1.", + "thingsboard-port-pattern": "A porta não é válida.", + "thingsboard-port-required": "A porta é obrigatória.", + "tidy": "Tidy", + "tidy-tip": "Config Tidy JSON", + "title-connectors-json": "Configuração do conector {{typeName}}", + "tls-path-ca-certificate": "Caminho para certificado de Autoridade de Certificação no gateway", + "tls-path-client-certificate": "Caminho para certificado de cliente no gateway", + "tls-path-private-key": "Caminho para chave privada no gateway", + "toggle-fullscreen": "Alternar tela inteira", + "transformer-json-config": "Configuração JSON*", + "update-config": "Adicionar/atualizar configuração de JSON" + }, + "grid": { + "delete-item-title": "Tem certeza de que deseja excluir este item?", + "delete-item-text": "Cuidado, após confirmar, não será possível recuperar este item e nenhum dado associado.", + "delete-items-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 item} other {# items} }?", + "delete-items-action-title": "Excluir { count, plural, 1 {1 item} other {# items} }", + "delete-items-text": "Cuidado, após confirmar, todos os itens selecionados serão removidos e não será possível recuperar nenhum dado associado.", + "add-item-text": "Adicionar novo item", + "no-items-text": "Nenhum item encontrado", + "item-details": "Detalhes do item", + "delete-item": "Excluir item", + "delete-items": "Excluir itens", + "scroll-to-top": "Rolar para o início" + }, + "help": { + "goto-help-page": "Ir para a página de Ajuda" + }, + "home": { + "home": "Página Inicial", + "profile": "Perfil", + "logout": "Logout", + "menu": "Menu", + "avatar": "Avatar", + "open-user-menu": "Abrir menu de usuário" + }, + "import": { + "no-file": "Nenhum arquivo selecionado", + "drop-file": "Solte um arquivo JSON ou clique para selecionar um arquivo para carregar.", + "drop-file-csv": "Solte um arquivo CSV ou clique para selecionar um arquivo para carregar.", + "column-value": "Valor", + "column-title": "Título", + "column-example": "Exemplo de dados de valor", + "column-key": "Chave de atributo/telemetria", + "csv-delimiter": "Delimitador de CSV", + "csv-first-line-header": "A primeira linha contém nomes de coluna", + "csv-update-data": "Atualizar atributos/telemetria", + "import-csv-number-columns-error": "Um arquivo deve conter no mínimo duas colunas", + "import-csv-invalid-format-error": "Formato de arquivo inválido. Linha: '{{line}}'", + "column-type": { + "name": "Nome", + "type": "Tipo", + "label": "Etiqueta", + "column-type": "Tipo de coluna", + "client-attribute": "Atributo do cliente", + "shared-attribute": "Atributo compartilhado", + "server-attribute": "Atributo do servidor", + "timeseries": "Intervalos de tempo", + "entity-field": "Campo de entidade", + "access-token": "Token de acesso", + "isgateway": "É Gateway", + "description": "Descrição" + }, + "stepper-text": { + "select-file": "Selecionar um arquivo", + "configuration": "Importar configuração", + "column-type": "Selecionar tipos de colunas", + "creat-entities": "Criar novas entidades", + "done": "Concluído" + }, + "message": { + "create-entities": "{{count}} novas entidades foram criadas corretamente.", + "update-entities": "{{count}} novas entidades foram atualizadas corretamente.", + "error-entities": "Ocorreu um erro ao criar {{count}} entidades." + } + }, + "item": { + "selected": "Selecionado" + }, + "js-func": { + "no-return-error": "A função deve retornar um valor!", + "return-type-mismatch": "A função deve retornar um valor do tipo '{{type}}'!", + "tidy": "Tidy", + "mini": "Mini" + }, + "key-val": { + "key": "Chave", + "value": "Valor", + "remove-entry": "Remover entrada", + "add-entry": "Adicionar entrada", + "no-data": "Sem entradas" + }, + "layout": { + "layout": "Layout", + "manage": "Gerenciar layouts", + "settings": "Configuração de layout", + "color": "Cor", + "main": "Principal", + "right": "Direita", + "select": "Selecionar layout alvo" + }, + "legend": { + "direction": "Direção da legenda", + "position": "Posição da legenda", + "show-max": "Mostrar valor máximo", + "show-min": "Mostrar valor mínimo", + "show-avg": "Mostrar valor médio", + "show-total": "Mostrar valor total", + "settings": "Configuração de legenda", + "min": "mín.", + "max": "máx.", + "avg": "méd.", + "total": "total", + "comparison-time-ago": { + "days": "(dia atrás)", + "weeks": "(semana atrás)", + "months": "(mês atrás)", + "years": "(ano atrás)" + } + }, + "login": { + "login": "Login", + "request-password-reset": "Solicitar redefinição de senha", + "reset-password": "Redefinir senha", + "create-password": "Criar senha", + "passwords-mismatch-error": "As senhas inseridas devem ser idênticas!", + "password-again": "Repetir senha", + "sign-in": "Inscreva-se", + "username": "Nome de usuário (e-mail)", + "remember-me": "Lembrar-me", + "forgot-password": "Esqueceu a senha?", + "password-reset": "Redefinir senha", + "expired-password-reset-message": "As credenciais de expiraram! Crie uma nova senha.", + "new-password": "Nova senha", + "new-password-again": "Repetir nova senha", + "password-link-sent-message": "O link de redefinição de senha foi enviado corretamente!", + "email": "E-mail", + "login-with": "Login com {{name}}", + "or": "ou", + "error": "Erro de login" + }, + "position": { + "top": "Acima", + "bottom": "Abaixo", + "left": "Esquerda", + "right": "Direita" + }, + "profile": { + "profile": "Perfil", + "last-login-time": "Último login", + "change-password": "Modificar senha", + "current-password": "Senha atual" + }, + "relation": { + "relations": "Relações", + "direction": "Direção", + "search-direction": { + "FROM": "De", + "TO": "Para" + }, + "direction-type": { + "FROM": "de", + "TO": "para" + }, + "from-relations": "Relações de saída", + "to-relations": "Relações de entrada", + "selected-relations": "{ count, plural, 1 {1 relation} other {# relations} } selecionado(s)", + "type": "Tipo", + "to-entity-type": "Para tipo de entidade", + "to-entity-name": "Para nome de entidade", + "from-entity-type": "De tipo de entidade", + "from-entity-name": "De nome de entidade", + "to-entity": "Para entidade", + "from-entity": "De entidade", + "delete": "Excluir relação", + "relation-type": "Tipo de relação", + "relation-type-required": "O tipo de relação é obrigatório.", + "any-relation-type": "Qualquer tipo", + "add": "Adicionar relação", + "edit": "Editar relação", + "delete-to-relation-title": "Tem certeza de que deseja excluir a relação com a entidade '{{entityName}}'?", + "delete-to-relation-text": "Cuidado, após confirmar, a relação da entidade '{{entityName}}' com a entidade atual será anulada.", + "delete-to-relations-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 relation} other {# relations} }?", + "delete-to-relations-text": "Cuidado, após confirmar, todas as relações serão removidas e a relação das entidades correspondentes com a entidade atual será anulada.", + "delete-from-relation-title": "Tem certeza de que deseja excluir a relação da entidade '{{entityName}}'?", + "delete-from-relation-text": "Cuidado, após confirmar, a relação da entidade atual com a entidade '{{entityName}}' será anulada.", + "delete-from-relations-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 relation} other {# relations} }?", + "delete-from-relations-text": "Cuidado, após confirmar, todas as relações serão removidas e a relação da entidade atual com as entidades correspondentes será anulada.", + "remove-relation-filter": "Remover filtro de relação", + "add-relation-filter": "Adicionar filtro de relação", + "any-relation": "Qualquer relação", + "relation-filters": "Filtros de relação", + "additional-info": "Informações adicionais (JSON)", + "invalid-additional-info": "Impossível analisar informações adicionais de json", + "no-relations-text": "Nenhuma relação encontrada" + }, + "rulechain": { + "rulechain": "Cadeia de regras", + "rulechains": "Cadeias de regras", + "root": "Raiz", + "delete": "Excluir cadeia de regras", + "name": "Nome", + "name-required": "O nome é obrigatório.", + "description": "Descrição", + "add": "Adicionar cadeia de regras", + "set-root": "Tornar cadeia de regras em raiz", + "set-root-rulechain-title": "Tem certeza de que deseja tornar a cadeia de regras '{{ruleChainName}}' em raiz?", + "set-root-rulechain-text": "Após confirmar, a cadeia de regras se tornará em raiz e irá tratar todas as mensagens de transporte recebidas.", + "delete-rulechain-title": "Tem certeza de que deseja excluir a cadeia de regras '{{ruleChainName}}'?", + "delete-rulechain-text": "Cuidado, após confirmar, não será possível recuperar a cadeia de regras e todos os dados associados.", + "delete-rulechains-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 rule chain} other {# rule chains} }?", + "delete-rulechains-action-title": "Excluir { count, plural, 1 {1 rule chain} other {# rule chains} }", + "delete-rulechains-text": "Cuidado, após confirmar, todas as cadeias de regras serão removidas e não será possível recuperar nenhum dado associado.", + "add-rulechain-text": "Adicionar nova cadeia de regras", + "no-rulechains-text": "Nenhuma cadeia de regras encontrada", + "rulechain-details": "Detalhes de cadeia de regras", + "details": "Detalhes", + "events": "Eventos", + "system": "Sistema", + "import": "Importar cadeia de regras", + "export": "Exportar cadeia de regras", + "export-failed-error": "Impossível exportar cadeia de regras: {{error}}", + "create-new-rulechain": "Criar nova cadeia de regras", + "rulechain-file": "Arquivo de cadeia de regras", + "invalid-rulechain-file-error": "Impossível importar cadeia de regras: Estrutura de dados de cadeia de regras inválida.", + "copyId": "Copiar ID de cadeia de regras", + "idCopiedMessage": "O ID da cadeia de regras foi copiado para a área de transferência", + "select-rulechain": "Selecionar cadeia de regras", + "no-rulechains-matching": "Nenhuma cadeia de regras encontrada que coincida com '{{entity}}'.", + "rulechain-required": "A cadeia de regras é obrigatória", + "management": "Gerenciamento de regras", + "debug-mode": "Modo de depuração", + "search": "Pesquisar cadeias de regras", + "selected-rulechains": "{ count, plural, 1 {1 rule chain} other {# rule chains} } selecionada(s)", + "open-rulechain": "Abrir cadeia de regras" + }, + "rulenode": { + "details": "Detalhes", + "events": "Eventos", + "search": "Pesquisar nós", + "open-node-library": "Abrir biblioteca de nós", + "add": "Adicionar nó de regra", + "name": "Nome", + "name-required": "O nome é obrigatório.", + "type": "Tipo", + "description": "Descrição", + "delete": "Excluir nó de regras", + "select-all-objects": "Selecionar todos os nós e conexões", + "deselect-all-objects": "Desmarcar todos os nós e conexões", + "delete-selected-objects": "Excluir nós e conexões selecionados", + "delete-selected": "Excluir selecionado", + "select-all": "Selecionar tudo", + "copy-selected": "Copiar selecionado(s)", + "deselect-all": "Desmarcar tudo", + "rulenode-details": "Detalhes do nó de regras", + "debug-mode": "Modo de depuração", + "configuration": "Configuração", + "link": "Link", + "link-details": "Detalhes do link do nó de regras", + "add-link": "Adicionar link", + "link-label": "Etiqueta de link", + "link-label-required": "A etiqueta de link é obrigatória.", + "custom-link-label": "Etiqueta de link personalizada", + "custom-link-label-required": "A etiqueta de link personalizada é obrigatória.", + "link-labels": "Etiquetas de links", + "link-labels-required": "As etiquetas de links são obrigatórias.", + "no-link-labels-found": "Nenhuma etiqueta de link encontrada", + "no-link-label-matching": "'{{label}}' não encontrado.", + "create-new-link-label": "Criar um novo!", + "type-filter": "Filtrar", + "type-filter-details": "Filtrar mensagens recebidas com condições configuradas", + "type-enrichment": "Enriquecimento", + "type-enrichment-details": "Adicionar informações adicionais aos Metadados de mensagens", + "type-transformation": "Transformação", + "type-transformation-details": "Alterar payload e metadados de mensagens", + "type-action": "Ação", + "type-action-details": "Executar ação especial", + "type-external": "Externo", + "type-external-details": "Interage com sistema externo", + "type-rule-chain": "Cadeia de Regras", + "type-rule-chain-details": "Encaminha mensagens recebidas para cadeia de regras especificada", + "type-input": "Entrada", + "type-input-details": "Entrada lógica de cadeia de regras, encaminha mensagens recebidas para nó de regras associado", + "type-unknown": "Desconhecido", + "type-unknown-details": "Nó de regras não resolvido", + "directive-is-not-loaded": "Diretriz de configuração '{{directiveName}}' definida não está disponível.", + "ui-resources-load-error": "Erro ao carregar configuração de recursos de interface de usuário.", + "invalid-target-rulechain": "Impossível resolver cadeia de regras alvo!", + "test-script-function": "Testar funcionamento de script", + "message": "Mensagem", + "message-type": "Tipo de mensagem", + "select-message-type": "Selecionar tipo de mensagem", + "message-type-required": "O tipo de mensagem é obrigatório", + "metadata": "Metadados", + "metadata-required": "As entradas de metadados não podem estar em branco.", + "output": "Saída", + "test": "Teste", + "help": "Ajuda", + "reset-debug-mode": "Redefinir modo de depuração em todos os nós" + }, + "timezone": { + "timezone": "Fuso horário", + "select-timezone": "Selecionar fuso horário", + "no-timezones-matching": "Nenhum fuso horário encontrado que coincida com '{{timezone}}'.", + "timezone-required": "O fuso horário é obrigatório." + }, + "queue": { + "select_name": "Selecionar nome de fila", + "name": "Nome de Fila", + "name_required": "O nome de fila é obrigatório!" + }, + "tenant": { + "tenant": "Locatário", + "tenants": "Locatários", + "management": "Gerenciamento de locatários", + "add": "Adicionar Locatário", + "admins": "Administradores", + "manage-tenant-admins": "Gerenciar administradores de locatários", + "delete": "Excluir locatário", + "add-tenant-text": "Adicionar novo locatário", + "no-tenants-text": "Nenhum locatário encontrado", + "tenant-details": "Detalhes do locatário", + "delete-tenant-title": "Tem certeza de que deseja excluir o locatário '{{tenantTitle}}'?", + "delete-tenant-text": "Cuidado, após confirmar, não será possível recuperar o locatário e nenhum dado associado.", + "delete-tenants-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 tenant} other {# tenants} }?", + "delete-tenants-action-title": "Excluir { count, plural, 1 {1 tenant} other {# tenants} }", + "delete-tenants-text": "Cuidado, após confirmar, todos os locatários selecionados serão removidos e não será possível recuperar nenhum dado associado.", + "title": "Título", + "title-required": "O título é obrigatório.", + "description": "Descrição", + "details": "Detalhes", + "events": "Eventos", + "copyId": "Copiar ID do locatário", + "idCopiedMessage": "O ID do locatário foi copiado para a área de transferência", + "select-tenant": "Selecionar locatário", + "no-tenants-matching": "Nenhum locatário encontrado que coincida com '{{entity}}'.", + "tenant-required": "O locatário é obrigatório", + "search": "Pesquisar locatários", + "selected-tenants": "{ count, plural, 1 {1 tenant} other {# tenants} } selecionado(s)", + "isolated-tb-core": "Processamento em contêiner isolado do ThingsBoard Core", + "isolated-tb-rule-engine": "Processamento em contêiner isolado do ThingsBoard Rule Engine", + "isolated-tb-core-details": "Exige microsserviço(s) separado(s) para cada locatário isolado", + "isolated-tb-rule-engine-details": "Exige microsserviço(s) separado(s) para cada locatário isolado" + }, + "timeinterval": { + "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", + "minutes-interval": "{ minutes, plural, 1 {1 minute} other {# minutes} }", + "hours-interval": "{ hours, plural, 1 {1 hour} other {# hours} }", + "days-interval": "{ days, plural, 1 {1 day} other {# days} }", + "days": "Dias", + "hours": "Horas", + "minutes": "Minutos", + "seconds": "Segundos", + "advanced": "Avançado" + }, + "timewindow": { + "days": "{ days, plural, 1 { day } other {# days } }", + "hours": "{ hours, plural, 0 { hour } 1 {1 hour } other {# hours } }", + "minutes": "{ minutes, plural, 0 { minute } 1 {1 minute } other {# minutes } }", + "seconds": "{ seconds, plural, 0 { second } 1 {1 second } other {# seconds } }", + "realtime": "Tempo real", + "history": "Histórico", + "last-prefix": "último", + "period": "de {{ startTime }} até {{ endTime }}", + "edit": "Editar janela de tempo", + "date-range": "Intervalo de datas", + "last": "Última", + "time-period": "Período de tempo", + "hide": "Ocultar" + }, + "user": { + "user": "Usuário", + "users": "Usuários", + "customer-users": "Usuários do cliente", + "tenant-admins": "Administradores de locatários", + "sys-admin": "Administrador do sistema", + "tenant-admin": "Administrador de locatários", + "customer": "Cliente", + "anonymous": "Anônimo", + "add": "Adicionar usuário", + "delete": "Excluir usuário", + "add-user-text": "Adicionar novo usuário", + "no-users-text": "Nenhum usuário encontrado", + "user-details": "Detalhes do usuário", + "delete-user-title": "Tem certeza de que deseja excluir o usuário '{{userEmail}}'?", + "delete-user-text": "Cuidado, após confirmar, não será possível recuperar o usuário e nenhum dado associado.", + "delete-users-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 user} other {# users} }?", + "delete-users-action-title": "Excluir { count, plural, 1 {1 user} other {# users} }", + "delete-users-text": "Cuidado, após confirmar, todos os usuários selecionados serão removidos e não será possível recuperar nenhum dado associado.", + "activation-email-sent-message": "O e-mail de ativação foi enviado corretamente!", + "resend-activation": "Reenviar ativação", + "email": "E-mail", + "email-required": "O e-mail é obrigatório.", + "invalid-email-format": "Formato de e-mail inválido.", + "first-name": "Nome", + "last-name": "Sobrenome", + "description": "Descrição", + "default-dashboard": "Dashboard padrão", + "always-fullscreen": "Sempre tela inteira", + "select-user": "Selecionar usuário", + "no-users-matching": "Nenhum usuário encontrado que coincida com '{{entity}}'.", + "user-required": "O usuário é obrigatório", + "activation-method": "Método de ativação", + "display-activation-link": "Exibir link de ativação", + "send-activation-mail": "Enviar e-mail de ativação", + "activation-link": "Link de ativação de usuário", + "activation-link-text": "Para ativar o usuário, utilize o seguinte link de ativação:", + "copy-activation-link": "Copiar link de ativação", + "activation-link-copied-message": "O link de ativação foi copiado para a área de transferência", + "details": "Detalhes", + "login-as-tenant-admin": "Login como administrador de locatários", + "login-as-customer-user": "Login como usuário do cliente", + "search": "Pesquisar usuários", + "selected-users": "{ count, plural, 1 {1 user} other {# users} } selecionado(s)", + "disable-account": "Desativar conta de usuário", + "enable-account": "Ativar conta de usuário", + "enable-account-message": "A conta de usuário foi ativada corretamente!", + "disable-account-message": "A conta de usuário foi desativada corretamente!" + }, + "value": { + "type": "Tipo de valor", + "string": "Cadeia de caracteres", + "string-value": "Valor da cadeia de caracteres", + "string-value-required": "O valor da cadeia de caracteres é obrigatório", + "integer": "Número inteiro", + "integer-value": "Valor do número inteiro", + "integer-value-required": "O valor do número inteiro é obrigatório", + "invalid-integer-value": "Valor de número inteiro inválido", + "double": "Duplo", + "double-value": "Valor duplo", + "double-value-required": "O valor duplo de caracteres é obrigatório", + "boolean": "Booliano", + "boolean-value": "Valor booliano", + "false": "Falso", + "true": "Verdadeiro", + "long": "Longo", + "json": "JSON", + "json-value": "Valor de JSON", + "json-value-invalid": "O formato do valor de JSON é inválido", + "json-value-required": "O valor de JSON é obrigatório." + }, + "widget": { + "widget-library": "Biblioteca de widgets", + "widget-bundle": "Pacote de widgets", + "select-widgets-bundle": "Selecionar pacote de widgets", + "management": "Gerenciamento de widgets", + "editor": "Editor de widgets", + "widget-type-not-found": "Problema ao carregar configuração de widget.
Provavelmente\n o tipo de widget foi removido.", + "widget-type-load-error": "O widget não foi carregado devido aos seguintes erros:", + "remove": "Remover widget", + "edit": "Editar widget", + "remove-widget-title": "Tem certeza de que deseja remover o widget '{{widgetTitle}}'?", + "remove-widget-text": "Após confirmar, não será possível recuperar o widget e nenhum dado associado.", + "timeseries": "Intervalos de tempo", + "search-data": "Pesquisar dados", + "no-data-found": "Nenhum dado encontrado", + "latest-values": "Últimos valores", + "rpc": "Widget de controle", + "alarm": "Widget de alarme", + "static": "Widget estático", + "select-widget-type": "Selecionar tipo de widget", + "missing-widget-title-error": "É necessário especificar o título de widget!", + "widget-saved": "Widget salvo", + "unable-to-save-widget-error": "Impossível salvar widget! O widget tem erros!", + "save": "Salvar widget", + "saveAs": "Salvar widget como", + "save-widget-type-as": "Salvar tipo de widget como", + "save-widget-type-as-text": "Inserir novo título de widget e/ou selecionar pacote de widgets alvo", + "toggle-fullscreen": "Alternar tela inteira", + "run": "Executar widget", + "title": "Título de widget", + "title-required": "O título do widget é obrigatório.", + "type": "Tipo de widget", + "resources": "Recursos", + "resource-url": "URL de JavaScript/CSS", + "resource-is-module": "É módulo", + "remove-resource": "Remover recurso", + "add-resource": "Adicionar recurso", + "html": "HTML", + "tidy": "Tidy", + "css": "CSS", + "settings-schema": "Esquema de configuração", + "datakey-settings-schema": "Esquema de configuração de chave de dados", + "javascript": "Javascript", + "js": "JS", + "remove-widget-type-title": "Tem certeza de que deseja remover o tipo de widget '{{widgetName}}'?", + "remove-widget-type-text": "Após confirmar, não será possível recuperar o tipo de widget e nenhum dado associado.", + "remove-widget-type": "Remover tipo de widget", + "add-widget-type": "Adicionar novo tipo de widget", + "widget-type-load-failed-error": "Erro ao carregar tipo de widget!", + "widget-template-load-failed-error": "Erro ao carregar modelo de widget!", + "add": "Adicionar widget", + "undo": "Desfazer alterações de widget", + "export": "Exportar widget", + "no-data": "Nenhum dado para exibição em widget", + "data-overflow": "O widget exibe {{count}} de um total de {{total}} entidades", + "alarm-data-overflow": "O widget exibe alarmes para (no máximo) {{allowedEntities}} entidades de um total de {{totalEntities}} entidades" + }, + "widget-action": { + "header-button": "Botão de cabeçalho de widget", + "open-dashboard-state": "Navegar para novo estado de dashboard", + "update-dashboard-state": "Atualizar estado de dashboard atual", + "open-dashboard": "Navegar para outro dashboard", + "custom": "Personalizar ação", + "custom-pretty": "Personalizar ação (com modelo de HTML)", + "target-dashboard-state": "Estado de dashboard alvo", + "target-dashboard-state-required": "O estado do dashboard alvo é obrigatório", + "set-entity-from-widget": "Definir entidade em widget", + "target-dashboard": "Dashboard alvo", + "open-right-layout": "Abrir layout do dashboard à direita (exibição móvel)" + }, + "widgets-bundle": { + "current": "Pacote atual", + "widgets-bundles": "Pacotes de widgets", + "add": "Adicionar pacote de widgets", + "delete": "Excluir pacote de widgets", + "title": "Título", + "title-required": "O título é obrigatório.", + "add-widgets-bundle-text": "Adicionar novo pacote de widgets", + "no-widgets-bundles-text": "Nenhum pacote de widgets encontrado", + "empty": "O pacote de widgets está vazio", + "details": "Detalhes", + "widgets-bundle-details": "Detalhes do pacote de widgets", + "delete-widgets-bundle-title": "Tem certeza de que deseja excluir o pacote de widgets '{{widgetsBundleTitle}}'?", + "delete-widgets-bundle-text": "Cuidado, após confirmar, não será possível recuperar o pacote de widgets e nenhum dado associado.", + "delete-widgets-bundles-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 widgets bundle} other {# widgets bundles} }?", + "delete-widgets-bundles-action-title": "Excluir { count, plural, 1 {1 widgets bundle} other {# widgets bundles} }", + "delete-widgets-bundles-text": "Cuidado, após confirmar, todos os pacotes de widgets selecionados serão removidos e não será possível recuperar nenhum dado associado.", + "no-widgets-bundles-matching": "Nenhum conversor de pacotes de widgets encontrado que coincida com '{{widgetsBundle}}'.", + "widgets-bundle-required": "O pacote de widgets é obrigatório.", + "system": "Sistema", + "import": "Importar pacote de widgets", + "export": "Exportar pacote de widgets", + "export-failed-error": "Impossível exportar pacote de widgets: {{error}}", + "create-new-widgets-bundle": "Criar novo pacote de widgets", + "widgets-bundle-file": "Arquivo de pacote de widgets", + "invalid-widgets-bundle-file-error": "Impossível importar pacote de widgets: Estrutura de dados de pacote de widgets inválida.", + "search": "Pesquisar pacotes de widgets", + "selected-widgets-bundles": "{ count, plural, 1 {1 widgets bundle} other {# widgets bundles} } selecionado(s)", + "open-widgets-bundle": "Abrir pacote de widgets" + }, + "widget-config": { + "data": "Dados", + "settings": "Configurações", + "advanced": "Avançado", + "title": "Título", + "title-tooltip": "Título da dica de ferramenta", + "general-settings": "Configuração geral", + "display-title": "Exibir título", + "drop-shadow": "Sombra projetada", + "enable-fullscreen": "Habilitar tela inteira", + "background-color": "Cor de fundo", + "text-color": "Cor de texto", + "padding": "Preenchimento", + "margin": "Margem", + "widget-style": "Estilo de widget", + "title-style": "Estilo do título", + "mobile-mode-settings": "Configuração do modo móvel", + "order": "Ordem", + "height": "Altura", + "units": "Símbolo especial a ser exibido ao lado do valor", + "decimals": "Número de dígitos após ponto de flutuação", + "timewindow": "Janela de tempo", + "use-dashboard-timewindow": "Usar janela de tempo do dashboard", + "display-timewindow": "Exibir timewindow", + "display-legend": "Exibir legenda", + "datasources": "Fontes de dados", + "maximum-datasources": "No máximo { count, plural, 1 {1 datasource is allowed.} other {# datasources are allowed} }", + "datasource-type": "Tipo", + "datasource-parameters": "Parâmetros", + "remove-datasource": "Remover fonte de dados", + "add-datasource": "Adicionar fonte de dados", + "target-device": "Dispositivo alvo", + "alarm-source": "Fonte do alarme", + "actions": "Ações", + "action": "Ação", + "add-action": "Adicionar ação", + "search-actions": "Pesquisar ações", + "no-actions-text": "Nenhuma ação encontrada", + "action-source": "Fonte da ação", + "action-source-required": "A fonte da ação é obrigatória.", + "action-name": "Nome", + "action-name-required": "O nome da ação é obrigatório!", + "action-name-not-unique": "Já existe outra ação com o mesmo nome.
O nome da ação na mesma fonte de ação deve ser exclusivo.", + "action-icon": "Ícone", + "action-type": "Tipo", + "action-type-required": "O tipo de ação é obrigatório.", + "edit-action": "Editar ação", + "delete-action": "Excluir ação", + "delete-action-title": "Excluir ação de widget", + "delete-action-text": "Tem certeza de que deseja excluir a ação de widget com o nome '{{actionName}}'?", + "display-icon": "Exibir ícone de título", + "icon-color": "Cor do ícone", + "icon-size": "Tamanho do ícone" + }, + "widget-type": { + "import": "Importar tipo de widget", + "export": "Exportar tipo de widget", + "export-failed-error": "Impossível exportar tipo de widget: {{error}}", + "create-new-widget-type": "Criar novo tipo de widget", + "widget-type-file": "Arquivo de tipo de widget", + "invalid-widget-type-file-error": "Impossível importar tipo de widget: Estrutura de dados de tipo de widget inválida." + }, + "widgets": { + "date-range-navigator": { + "localizationMap": { + "Sun": "Dom", + "Mon": "Seg", + "Tue": "Ter", + "Wed": "Qua", + "Thu": "Qui", + "Fri": "Sex", + "Sat": "Sáb", + "Jan": "Jan", + "Feb": "Fev", + "Mar": "Mar", + "Apr": "Abr", + "May": "Maio", + "Jun": "Jun", + "Jul": "Jul", + "Aug": "Ago", + "Sep": "Set", + "Oct": "Out", + "Nov": "Nov", + "Dec": "Dez", + "January": "Janeiro", + "February": "Fevereiro", + "March": "Março", + "April": "Abril", + "June": "Junho", + "July": "Julho", + "August": "Agosto", + "September": "Setembro", + "October": "Outubro", + "November": "Novembro", + "December": "Dezembro", + "Custom Date Range": "Personalizar intervalo de datas", + "Date Range Template": "Modelo de intervalo de datas", + "Today": "Hoje", + "Yesterday": "Ontem", + "This Week": "Esta semana", + "Last Week": "Semana passada", + "This Month": "Este mês", + "Last Month": "Mês passado", + "Year": "Ano", + "This Year": "Este ano", + "Last Year": "Ano passado", + "Date picker": "Seletor de data", + "Hour": "Hora", + "Day": "Dia", + "Week": "Semana", + "2 weeks": "2 semanas", + "Month": "Mês", + "3 months": "3 meses", + "6 months": "6 meses", + "Custom interval": "Intervalo personalizado", + "Interval": "Intervalo", + "Step size": "Tamanho da etapa", + "Ok": "OK" + } + }, + "input-widgets": { + "attribute-not-allowed": "O parâmetro de atributo não pode ser usado neste widget", + "blocked-location": "A geolocalização está bloqueada no seu navegador", + "claim-device": "Obter dispositivo", + "claim-failed": "Erro ao obter dispositivo!", + "claim-not-found": "Dispositivo não encontrado!", + "claim-successful": "O dispositivo foi obtido corretamente!", + "date": "Data", + "device-name": "Nome do dispositivo", + "device-name-required": "O nome do dispositivo é obrigatório", + "discard-changes": "Descartar alterações", + "entity-attribute-required": "O atributo da entidade é obrigatório", + "entity-coordinate-required": "Os dois campos, latitude e longitude, são obrigatórios", + "entity-timeseries-required": "Os intervalos de tempo de entidade são obrigatórios", + "get-location": "Obter localização atual", + "latitude": "Latitude", + "longitude": "Longitude", + "not-allowed-entity": "A entidade selecionada não pode ter atributos compartilhados", + "no-attribute-selected": "Nenhum atributo selecionado", + "no-datakey-selected": "Nenhuma chave de dados selecionada", + "no-coordinate-specified": "Chave de dados de latitude/longitude não especificada", + "no-entity-selected": "Nenhuma entidade selecionada", + "no-image": "Sem imagem", + "no-support-geolocation": "Seu navegador não é compatível com geolocalização", + "no-support-web-camera": "Nenhuma câmera Web compatível", + "no-timeseries-selected": "Nenhum intervalo de tempo selecionado", + "secret-key": "Chave de segredo", + "secret-key-required": "A chave de segredo é obrigatória", + "switch-attribute-value": "Alternar valor de atributo da entidade", + "switch-camera": "Alternar câmera", + "switch-timeseries-value": "Alternar valor de intervalo de tempo da entidade", + "take-photo": "Tirar foto", + "time": "Hora", + "timeseries-not-allowed": "O parâmetro de intervalo de série não pode ser usado neste widget", + "update-failed": "Atualização falhou", + "update-successful": "Atualização bem-sucedida", + "update-attribute": "Atualizar atributo", + "update-timeseries": "Atualizar intervalos de tempo", + "value": "Valor" + } + }, + "icon": { + "icon": "Ícone", + "select-icon": "Selecionar ícone", + "material-icons": "Ícones de materiais", + "show-all": "Mostrar todos os ícones" + }, + "custom": { + "widget-action": { + "action-cell-button": "Botão da célula de ação", + "row-click": "Clicar em linha", + "polygon-click": "Clique no polígono", + "marker-click": "Clique no marcador", + "tooltip-tag-action": "Ação do rótulo de dica de ferramenta", + "node-selected": "Selecionado no nó", + "element-click": "Clique em elemento de HTML", + "pie-slice-click": "Clique na fatia", + "row-double-click": "Dois clique na fila" + } + }, + "language": { + "language": "Idioma" + } +} From a64c4e3e203e1854c0ced8349dfb56e948a24ded Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 3 Nov 2020 13:05:31 +0200 Subject: [PATCH 284/463] DeviceProfileNode fix for conditions based on the attributes --- .../service/apiusage/DefaultTbApiUsageStateService.java | 6 +++--- .../controller/BaseTenantProfileControllerTest.java | 5 ++++- .../server/controller/ControllerSqlTestSuite.java | 2 +- .../server/dao/tenant/TenantProfileServiceImpl.java | 8 +++++++- .../server/dao/usagerecord/ApiUsageStateServiceImpl.java | 2 +- .../thingsboard/rule/engine/profile/AlarmRuleState.java | 4 ++++ 6 files changed, 20 insertions(+), 7 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 02d6e88284..f7e00b7242 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -156,7 +156,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } finally { updateLock.unlock(); } - tsWsService.saveAndNotify(tenantId, tenantState.getApiUsageState().getId(), updatedEntries, 0L, VOID_CALLBACK); + tsWsService.saveAndNotifyInternal(tenantId, tenantState.getApiUsageState().getId(), updatedEntries, 0L, VOID_CALLBACK); if (!result.isEmpty()) { persistAndNotify(tenantState, result); } @@ -256,7 +256,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } if (!profileThresholds.isEmpty()) { - tsWsService.saveAndNotify(tenantId, id, profileThresholds, 0L, VOID_CALLBACK); + tsWsService.saveAndNotifyInternal(tenantId, id, profileThresholds, 0L, VOID_CALLBACK); } } @@ -266,7 +266,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { long ts = System.currentTimeMillis(); List stateTelemetry = new ArrayList<>(); result.forEach(((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new BooleanDataEntry(apiFeature.getApiStateKey(), aState))))); - tsWsService.saveAndNotify(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, 0L, VOID_CALLBACK); + tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, 0L, VOID_CALLBACK); } private void checkStartOfNextCycle() { diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java index 311a93bf82..4f0d1e201b 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java @@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -285,7 +286,9 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController TenantProfile tenantProfile = new TenantProfile(); tenantProfile.setName(name); tenantProfile.setDescription(name + " Test"); - tenantProfile.setProfileData(new TenantProfileData()); + TenantProfileData tenantProfileData = new TenantProfileData(); + tenantProfileData.setConfiguration(new DefaultTenantProfileConfiguration()); + tenantProfile.setProfileData(tenantProfileData); tenantProfile.setDefault(false); tenantProfile.setIsolatedTbCore(false); tenantProfile.setIsolatedTbRuleEngine(false); diff --git a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java index 64f9b8fddf..bc2c36b501 100644 --- a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java @@ -27,7 +27,7 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({ // "org.thingsboard.server.controller.sql.WebsocketApiSqlTest", -// "org.thingsboard.server.controller.sql.EntityQueryControllerSqlTest", +// "org.thingsboard.server.controller.sql.TenantProfileControllerSqlTest", "org.thingsboard.server.controller.sql.*Test", }) public class ControllerSqlTestSuite { diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java index af327d3505..5bafe4f799 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -216,6 +216,12 @@ public class TenantProfileServiceImpl extends AbstractEntityService implements T if (StringUtils.isEmpty(tenantProfile.getName())) { throw new DataValidationException("Tenant profile name should be specified!"); } + if (tenantProfile.getProfileData() == null) { + throw new DataValidationException("Tenant profile data should be specified!"); + } + if (tenantProfile.getProfileData().getConfiguration() == null) { + throw new DataValidationException("Tenant profile data configuration should be specified!"); + } if (tenantProfile.isDefault()) { TenantProfile defaultTenantProfile = findDefaultTenantProfile(tenantId); if (defaultTenantProfile != null && !defaultTenantProfile.getId().equals(tenantProfile.getId())) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index e1b494e2e8..26184a5824 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java index 8be2b81a59..b1eeb82b15 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java @@ -99,6 +99,10 @@ class AlarmRuleState { } } for (EntityKey key : changedKeys) { + EntityKeyType keyType = key.getType(); + if (EntityKeyType.CLIENT_ATTRIBUTE.equals(keyType) || EntityKeyType.SERVER_ATTRIBUTE.equals(keyType) || EntityKeyType.SHARED_ATTRIBUTE.equals(keyType)) { + key = new EntityKey(EntityKeyType.ATTRIBUTE, key.getKey()); + } if (entityKeys.contains(key)) { return true; } From 35dbe509f071d94fb1cf53fc637a3a8e4ff5e36f Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 3 Nov 2020 13:43:27 +0200 Subject: [PATCH 285/463] Data Points storage --- .../DefaultTbApiUsageStateService.java | 4 +- .../AbstractSubscriptionService.java | 6 +-- .../DefaultTelemetrySubscriptionService.java | 40 +++++++++++-------- .../telemetry/InternalTelemetryService.java | 2 +- .../dao/timeseries/TimeseriesService.java | 4 +- 5 files changed, 32 insertions(+), 24 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index f7e00b7242..88868d2258 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -71,9 +71,9 @@ import java.util.concurrent.locks.ReentrantLock; public class DefaultTbApiUsageStateService implements TbApiUsageStateService { public static final String HOURLY = "Hourly"; - public static final FutureCallback VOID_CALLBACK = new FutureCallback() { + public static final FutureCallback VOID_CALLBACK = new FutureCallback() { @Override - public void onSuccess(@Nullable Void result) {} + public void onSuccess(@Nullable Integer result) {} @Override public void onFailure(Throwable t) {} diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java index bc4099e81b..4178502c77 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java @@ -105,10 +105,10 @@ public abstract class AbstractSubscriptionService implements ApplicationListener } } - protected void addWsCallback(ListenableFuture> saveFuture, Consumer callback) { - Futures.addCallback(saveFuture, new FutureCallback>() { + protected void addWsCallback(ListenableFuture> saveFuture, Consumer callback) { + Futures.addCallback(saveFuture, new FutureCallback>() { @Override - public void onSuccess(@Nullable List result) { + public void onSuccess(@Nullable List result) { callback.accept(null); } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index d4786a6f54..53d0e08da2 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -20,10 +20,9 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.id.EntityId; @@ -42,10 +41,9 @@ import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.service.queue.TbClusterService; -import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import javax.annotation.Nullable; @@ -59,12 +57,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.function.Consumer; -import java.util.stream.Collectors; /** * Created by ashvayka on 27.03.18. @@ -76,6 +70,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer private final AttributesService attrService; private final TimeseriesService tsService; private final EntityViewService entityViewService; + private final TbApiUsageClient apiUsageClient; private ExecutorService tsCallBackExecutor; @@ -83,11 +78,13 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer TimeseriesService tsService, EntityViewService entityViewService, TbClusterService clusterService, - PartitionService partitionService) { + PartitionService partitionService, + TbApiUsageClient apiUsageClient) { super(clusterService, partitionService); this.attrService = attrService; this.tsService = tsService; this.entityViewService = entityViewService; + this.apiUsageClient = apiUsageClient; } @PostConstruct @@ -117,12 +114,25 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @Override public void saveAndNotify(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback) { checkInternalEntity(entityId); - saveAndNotifyInternal(tenantId, entityId, ts, ttl, callback); + saveAndNotifyInternal(tenantId, entityId, ts, ttl, new FutureCallback() { + @Override + public void onSuccess(Integer result) { + if (result != null && result > 0) { + apiUsageClient.report(tenantId, ApiUsageRecordKey.STORAGE_DP_COUNT, result); + } + callback.onSuccess(null); + } + + @Override + public void onFailure(Throwable t) { + callback.onFailure(t); + } + }); } @Override - public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback) { - ListenableFuture> saveFuture = tsService.save(tenantId, entityId, ts, ttl); + public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback) { + ListenableFuture> saveFuture = tsService.save(tenantId, entityId, ts, ttl); addMainCallback(saveFuture, callback); addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts)); if (EntityType.DEVICE.equals(entityId.getEntityType()) || EntityType.ASSET.equals(entityId.getEntityType())) { @@ -147,9 +157,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer Optional tsKvEntry = entries.stream() .filter(entry -> entry.getTs() > startTs && entry.getTs() <= endTs) .max(Comparator.comparingLong(TsKvEntry::getTs)); - if (tsKvEntry.isPresent()) { - entityViewLatest.add(tsKvEntry.get()); - } + tsKvEntry.ifPresent(entityViewLatest::add); } } if (!entityViewLatest.isEmpty()) { diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java index 275630a161..53cc23f23a 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java @@ -29,7 +29,7 @@ import java.util.List; */ public interface InternalTelemetryService extends RuleEngineTelemetryService { - void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback); + void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback); void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List attributes, boolean notifyDevice, FutureCallback callback); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java index 2a5a701f9e..81dedff132 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java @@ -36,9 +36,9 @@ public interface TimeseriesService { ListenableFuture> findAllLatest(TenantId tenantId, EntityId entityId); - ListenableFuture> save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry); + ListenableFuture> save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry); - ListenableFuture> save(TenantId tenantId, EntityId entityId, List tsKvEntry, long ttl); + ListenableFuture> save(TenantId tenantId, EntityId entityId, List tsKvEntry, long ttl); ListenableFuture> saveLatest(TenantId tenantId, EntityId entityId, List tsKvEntry); From 123ea76c94718c4eaf251a738887727ad2564abb Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 3 Nov 2020 18:59:12 +0200 Subject: [PATCH 286/463] Improvements for data points storage --- .../DefaultTbApiUsageStateService.java | 30 ++++---- .../DefaultRuleEngineStatisticsService.java | 8 +-- .../AbstractSubscriptionService.java | 6 +- .../DefaultTelemetrySubscriptionService.java | 68 +++++++++++++------ .../telemetry/InternalTelemetryService.java | 2 + .../dao/timeseries/TimeseriesService.java | 4 +- .../server/common/data/kv/BasicTsKvEntry.java | 19 +++++- .../server/common/data/kv/TsKvEntry.java | 5 ++ ...stractChunkedAggregationTimeseriesDao.java | 2 +- .../dao/sqlts/AbstractSqlTimeseriesDao.java | 21 ++++++ .../dao/sqlts/hsql/JpaHsqlTimeseriesDao.java | 7 +- .../dao/sqlts/psql/JpaPsqlTimeseriesDao.java | 7 +- .../timescale/TimescaleTimeseriesDao.java | 11 +-- .../dao/tenant/TenantProfileServiceImpl.java | 2 +- .../dao/timeseries/BaseTimeseriesService.java | 34 +++++++--- .../CassandraBaseTimeseriesDao.java | 19 ++++-- .../server/dao/timeseries/TimeseriesDao.java | 4 +- .../usagerecord/ApiUsageStateServiceImpl.java | 2 +- .../dao/service/BaseEntityServiceTest.java | 4 +- 19 files changed, 181 insertions(+), 74 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 88868d2258..42f980909a 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -18,7 +18,9 @@ package org.thingsboard.server.service.apiusage; import com.google.common.util.concurrent.FutureCallback; import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.nullness.qual.Nullable; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.ApiUsageRecordKey; @@ -51,7 +53,6 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.profile.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.telemetry.InternalTelemetryService; -import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import javax.annotation.PostConstruct; import java.util.ArrayList; @@ -73,20 +74,25 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { public static final String HOURLY = "Hourly"; public static final FutureCallback VOID_CALLBACK = new FutureCallback() { @Override - public void onSuccess(@Nullable Integer result) {} + public void onSuccess(@Nullable Integer result) { + } @Override - public void onFailure(Throwable t) {} + public void onFailure(Throwable t) { + } }; private final TbClusterService clusterService; private final PartitionService partitionService; private final TenantService tenantService; - private final ApiUsageStateService apiUsageStateService; private final TimeseriesService tsService; - private final InternalTelemetryService tsWsService; + private final ApiUsageStateService apiUsageStateService; private final SchedulerComponent scheduler; private final TbTenantProfileCache tenantProfileCache; + @Lazy + @Autowired + private InternalTelemetryService tsWsService; + // Tenants that should be processed on this server private final Map myTenantStates = new ConcurrentHashMap<>(); // Tenants that should be processed on other servers @@ -102,16 +108,16 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { public DefaultTbApiUsageStateService(TbClusterService clusterService, PartitionService partitionService, - TenantService tenantService, ApiUsageStateService apiUsageStateService, - TimeseriesService tsService, TelemetrySubscriptionService tsWsService, + TenantService tenantService, + TimeseriesService tsService, + ApiUsageStateService apiUsageStateService, SchedulerComponent scheduler, TbTenantProfileCache tenantProfileCache) { this.clusterService = clusterService; this.partitionService = partitionService; this.tenantService = tenantService; - this.apiUsageStateService = apiUsageStateService; this.tsService = tsService; - this.tsWsService = tsWsService; + this.apiUsageStateService = apiUsageStateService; this.scheduler = scheduler; this.tenantProfileCache = tenantProfileCache; } @@ -156,7 +162,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } finally { updateLock.unlock(); } - tsWsService.saveAndNotifyInternal(tenantId, tenantState.getApiUsageState().getId(), updatedEntries, 0L, VOID_CALLBACK); + tsWsService.saveAndNotifyInternal(tenantId, tenantState.getApiUsageState().getId(), updatedEntries, VOID_CALLBACK); if (!result.isEmpty()) { persistAndNotify(tenantState, result); } @@ -256,7 +262,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } if (!profileThresholds.isEmpty()) { - tsWsService.saveAndNotifyInternal(tenantId, id, profileThresholds, 0L, VOID_CALLBACK); + tsWsService.saveAndNotifyInternal(tenantId, id, profileThresholds, VOID_CALLBACK); } } @@ -266,7 +272,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { long ts = System.currentTimeMillis(); List stateTelemetry = new ArrayList<>(); result.forEach(((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new BooleanDataEntry(apiFeature.getApiStateKey(), aState))))); - tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, 0L, VOID_CALLBACK); + tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK); } private void checkStartOfNextCycle() { diff --git a/application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java b/application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java index b46033389b..a832eb0870 100644 --- a/application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java +++ b/application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java @@ -48,9 +48,9 @@ import java.util.stream.Collectors; public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsService { public static final String TB_SERVICE_QUEUE = "TbServiceQueue"; - public static final FutureCallback CALLBACK = new FutureCallback() { + public static final FutureCallback CALLBACK = new FutureCallback() { @Override - public void onSuccess(@Nullable Void result) { + public void onSuccess(@Nullable Integer result) { } @@ -85,7 +85,7 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS .map(kv -> new BasicTsKvEntry(ts, new LongDataEntry(kv.getKey(), (long) kv.getValue().get()))) .collect(Collectors.toList()); if (!tsList.isEmpty()) { - tsService.saveAndNotify(tenantId, serviceAssetId, tsList, CALLBACK); + tsService.saveAndNotifyInternal(tenantId, serviceAssetId, tsList, CALLBACK); } } } catch (DataValidationException e) { @@ -97,7 +97,7 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS ruleEngineStats.getTenantExceptions().forEach((tenantId, e) -> { TsKvEntry tsKv = new BasicTsKvEntry(ts, new JsonDataEntry("ruleEngineException", e.toJsonString())); try { - tsService.saveAndNotify(tenantId, getServiceAssetId(tenantId, queueName), Collections.singletonList(tsKv), CALLBACK); + tsService.saveAndNotifyInternal(tenantId, getServiceAssetId(tenantId, queueName), Collections.singletonList(tsKv), CALLBACK); } catch (DataValidationException e2) { if (!e2.getMessage().equalsIgnoreCase("Asset is referencing to non-existent tenant!")) { throw e2; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java index 4178502c77..468c3c26b0 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java @@ -105,10 +105,10 @@ public abstract class AbstractSubscriptionService implements ApplicationListener } } - protected void addWsCallback(ListenableFuture> saveFuture, Consumer callback) { - Futures.addCallback(saveFuture, new FutureCallback>() { + protected void addWsCallback(ListenableFuture saveFuture, Consumer callback) { + Futures.addCallback(saveFuture, new FutureCallback() { @Override - public void onSuccess(@Nullable List result) { + public void onSuccess(@Nullable T result) { callback.accept(null); } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index 53d0e08da2..7a35e6b317 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -40,9 +40,11 @@ import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.timeseries.TimeseriesService; +import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.usagestats.TbApiUsageClient; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; @@ -71,6 +73,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer private final TimeseriesService tsService; private final EntityViewService entityViewService; private final TbApiUsageClient apiUsageClient; + private final TbApiUsageStateService apiUsageStateService; private ExecutorService tsCallBackExecutor; @@ -79,12 +82,14 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer EntityViewService entityViewService, TbClusterService clusterService, PartitionService partitionService, - TbApiUsageClient apiUsageClient) { + TbApiUsageClient apiUsageClient, + TbApiUsageStateService apiUsageStateService) { super(clusterService, partitionService); this.attrService = attrService; this.tsService = tsService; this.entityViewService = entityViewService; this.apiUsageClient = apiUsageClient; + this.apiUsageStateService = apiUsageStateService; } @PostConstruct @@ -114,25 +119,34 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @Override public void saveAndNotify(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback) { checkInternalEntity(entityId); - saveAndNotifyInternal(tenantId, entityId, ts, ttl, new FutureCallback() { - @Override - public void onSuccess(Integer result) { - if (result != null && result > 0) { - apiUsageClient.report(tenantId, ApiUsageRecordKey.STORAGE_DP_COUNT, result); + if (apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) { + saveAndNotifyInternal(tenantId, entityId, ts, ttl, new FutureCallback() { + @Override + public void onSuccess(Integer result) { + if (result != null && result > 0) { + apiUsageClient.report(tenantId, ApiUsageRecordKey.STORAGE_DP_COUNT, result); + } + callback.onSuccess(null); } - callback.onSuccess(null); - } - @Override - public void onFailure(Throwable t) { - callback.onFailure(t); - } - }); + @Override + public void onFailure(Throwable t) { + callback.onFailure(t); + } + }); + } else{ + callback.onFailure(new RuntimeException("DB storage writes are disabled due to API limits!")); + } + } + + @Override + public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, FutureCallback callback) { + saveAndNotifyInternal(tenantId, entityId, ts, 0L, callback); } @Override public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback) { - ListenableFuture> saveFuture = tsService.save(tenantId, entityId, ts, ttl); + ListenableFuture saveFuture = tsService.save(tenantId, entityId, ts, ttl); addMainCallback(saveFuture, callback); addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts)); if (EntityType.DEVICE.equals(entityId.getEntityType()) || EntityType.ASSET.equals(entityId.getEntityType())) { @@ -197,7 +211,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @Override public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List attributes, boolean notifyDevice, FutureCallback callback) { ListenableFuture> saveFuture = attrService.save(tenantId, entityId, scope, attributes); - addMainCallback(saveFuture, callback); + addVoidCallback(saveFuture, callback); addWsCallback(saveFuture, success -> onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice)); } @@ -210,7 +224,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @Override public void saveLatestAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, FutureCallback callback) { ListenableFuture> saveFuture = tsService.saveLatest(tenantId, entityId, ts); - addMainCallback(saveFuture, callback); + addVoidCallback(saveFuture, callback); addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts)); } @@ -223,7 +237,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @Override public void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List keys, FutureCallback callback) { ListenableFuture> deleteFuture = attrService.removeAll(tenantId, entityId, scope, keys); - addMainCallback(deleteFuture, callback); + addVoidCallback(deleteFuture, callback); addWsCallback(deleteFuture, success -> onAttributesDelete(tenantId, entityId, scope, keys)); } @@ -236,7 +250,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @Override public void deleteLatestInternal(TenantId tenantId, EntityId entityId, List keys, FutureCallback callback) { ListenableFuture> deleteFuture = tsService.removeLatest(tenantId, entityId, keys); - addMainCallback(deleteFuture, callback); + addVoidCallback(deleteFuture, callback); } @Override @@ -321,7 +335,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer } } - private void addMainCallback(ListenableFuture saveFuture, final FutureCallback callback) { + private void addVoidCallback(ListenableFuture saveFuture, final FutureCallback callback) { Futures.addCallback(saveFuture, new FutureCallback() { @Override public void onSuccess(@Nullable S result) { @@ -335,6 +349,20 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer }, tsCallBackExecutor); } + private void addMainCallback(ListenableFuture saveFuture, final FutureCallback callback) { + Futures.addCallback(saveFuture, new FutureCallback() { + @Override + public void onSuccess(@Nullable S result) { + callback.onSuccess(result); + } + + @Override + public void onFailure(Throwable t) { + callback.onFailure(t); + } + }, tsCallBackExecutor); + } + private void checkInternalEntity(EntityId entityId) { if (EntityType.API_USAGE_STATE.equals(entityId.getEntityType())) { throw new RuntimeException("Can't update API Usage State!"); diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java index 53cc23f23a..1ca2cdeacb 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java @@ -29,6 +29,8 @@ import java.util.List; */ public interface InternalTelemetryService extends RuleEngineTelemetryService { + void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, FutureCallback callback); + void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback); void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List attributes, boolean notifyDevice, FutureCallback callback); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java index 81dedff132..0e11aedaf5 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java @@ -36,9 +36,9 @@ public interface TimeseriesService { ListenableFuture> findAllLatest(TenantId tenantId, EntityId entityId); - ListenableFuture> save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry); + ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry); - ListenableFuture> save(TenantId tenantId, EntityId entityId, List tsKvEntry, long ttl); + ListenableFuture save(TenantId tenantId, EntityId entityId, List tsKvEntry, long ttl); ListenableFuture> saveLatest(TenantId tenantId, EntityId entityId, List tsKvEntry); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java index c2d6688004..bcf49899dc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java @@ -19,7 +19,7 @@ import java.util.Objects; import java.util.Optional; public class BasicTsKvEntry implements TsKvEntry { - + private static final int MAX_CHARS_PER_DATA_POINT = 512; private final long ts; private final KvEntry kv; @@ -99,4 +99,21 @@ public class BasicTsKvEntry implements TsKvEntry { public String getValueAsString() { return kv.getValueAsString(); } + + @Override + public int getDataPoints() { + int length; + switch (getDataType()) { + case STRING: + length = getStrValue().get().length(); + break; + case JSON: + length = getJsonValue().get().length(); + break; + default: + return 1; + } + return Math.max(1, (length + MAX_CHARS_PER_DATA_POINT - 1) / MAX_CHARS_PER_DATA_POINT); + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvEntry.java index 315d3940df..e2f8eaf1e5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvEntry.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvEntry.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.common.data.kv; +import com.fasterxml.jackson.annotation.JsonIgnore; + /** * Represents time series KV data entry * @@ -25,4 +27,7 @@ public interface TsKvEntry extends KvEntry { long getTs(); + @JsonIgnore + int getDataPoints(); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java index 3cee730ee3..616260ba45 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java @@ -100,7 +100,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq } @Override - public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { + public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { return Futures.immediateFuture(null); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java index 8dcefeca61..c585804b82 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java @@ -30,11 +30,14 @@ import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; import javax.annotation.Nullable; import java.util.List; import java.util.Objects; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Slf4j public abstract class AbstractSqlTimeseriesDao extends BaseAbstractSqlTimeseriesDao implements AggregationTimeseriesDao { + protected static final long SECONDS_IN_DAY = TimeUnit.DAYS.toSeconds(1); + @Autowired protected ScheduledLogExecutorComponent logExecutor; @@ -56,6 +59,9 @@ public abstract class AbstractSqlTimeseriesDao extends BaseAbstractSqlTimeseries @Value("${sql.batch_sort:false}") protected boolean batchSortEnabled; + @Value("${sql.ttl.ts.ts_key_value_ttl:0}") + private long systemTtl; + protected ListenableFuture> processFindAllAsync(TenantId tenantId, EntityId entityId, List queries) { List>> futures = queries .stream() @@ -75,4 +81,19 @@ public abstract class AbstractSqlTimeseriesDao extends BaseAbstractSqlTimeseries } }, service); } + + protected long computeTtl(long ttl) { + if (systemTtl > 0) { + if (ttl == 0) { + ttl = systemTtl; + } else { + ttl = Math.min(systemTtl, ttl); + } + } + return ttl; + } + + protected int getDataPointDays(TsKvEntry tsKvEntry, long ttl) { + return tsKvEntry.getDataPoints() * Math.max(1, (int) (ttl / SECONDS_IN_DAY)); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java index 5ea387328d..025134e187 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java @@ -15,7 +15,9 @@ */ package org.thingsboard.server.dao.sqlts.hsql; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.EntityId; @@ -35,7 +37,8 @@ import org.thingsboard.server.dao.util.SqlTsDao; public class JpaHsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDao implements TimeseriesDao { @Override - public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { + public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { + int dataPointDays = getDataPointDays(tsKvEntry, computeTtl(ttl)); String strKey = tsKvEntry.getKey(); Integer keyId = getOrSaveKeyId(strKey); TsKvEntity entity = new TsKvEntity(); @@ -48,7 +51,7 @@ public class JpaHsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); entity.setJsonValue(tsKvEntry.getJsonValue().orElse(null)); log.trace("Saving entity: {}", entity); - return tsQueue.add(entity); + return Futures.transform(tsQueue.add(entity), v -> dataPointDays, MoreExecutors.directExecutor()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java index 8c50bcb38c..3d971f61c6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java @@ -15,7 +15,9 @@ */ package org.thingsboard.server.dao.sqlts.psql; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -71,7 +73,8 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa } @Override - public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { + public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { + int dataPointDays = getDataPointDays(tsKvEntry, computeTtl(ttl)); savePartitionIfNotExist(tsKvEntry.getTs()); String strKey = tsKvEntry.getKey(); Integer keyId = getOrSaveKeyId(strKey); @@ -85,7 +88,7 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); entity.setJsonValue(tsKvEntry.getJsonValue().orElse(null)); log.trace("Saving entity: {}", entity); - return tsQueue.add(entity); + return Futures.transform(tsQueue.add(entity), v -> dataPointDays, MoreExecutors.directExecutor()); } private void savePartitionIfNotExist(long ts) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java index 6d5923c508..f6fb027d7e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Component; @@ -101,7 +102,8 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements } @Override - public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { + public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { + int dataPointDays = getDataPointDays(tsKvEntry, computeTtl(ttl)); String strKey = tsKvEntry.getKey(); Integer keyId = getOrSaveKeyId(strKey); TimescaleTsKvEntity entity = new TimescaleTsKvEntity(); @@ -113,14 +115,13 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); entity.setJsonValue(tsKvEntry.getJsonValue().orElse(null)); - log.trace("Saving entity to timescale db: {}", entity); - return tsQueue.add(entity); + return Futures.transform(tsQueue.add(entity), v -> dataPointDays, MoreExecutors.directExecutor()); } @Override - public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { - return Futures.immediateFuture(null); + public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { + return Futures.immediateFuture(0); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java index 5bafe4f799..95dd0ab45a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java index b86d6128f6..3b97e2bb82 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java @@ -15,11 +15,13 @@ */ package org.thingsboard.server.dao.timeseries; +import com.google.common.base.Function; import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -55,6 +57,20 @@ public class BaseTimeseriesService implements TimeseriesService { private static final int INSERTS_PER_ENTRY = 3; private static final int DELETES_PER_ENTRY = INSERTS_PER_ENTRY; + public static final Function, Integer> SUM_ALL_INTEGERS = new Function, Integer>() { + @Override + public @Nullable Integer apply(@Nullable List input) { + int result = 0; + if (input != null) { + for (Integer tmp : input) { + if (tmp != null) { + result += tmp; + } + } + } + return result; + } + }; @Value("${database.ts_max_intervals}") private long maxTsIntervals; @@ -101,26 +117,26 @@ public class BaseTimeseriesService implements TimeseriesService { } @Override - public ListenableFuture> save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { + public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { validate(entityId); if (tsKvEntry == null) { throw new IncorrectParameterException("Key value entry can't be null"); } - List> futures = Lists.newArrayListWithExpectedSize(INSERTS_PER_ENTRY); + List> futures = Lists.newArrayListWithExpectedSize(INSERTS_PER_ENTRY); saveAndRegisterFutures(tenantId, futures, entityId, tsKvEntry, 0L); - return Futures.allAsList(futures); + return Futures.transform(Futures.allAsList(futures), SUM_ALL_INTEGERS, MoreExecutors.directExecutor()); } @Override - public ListenableFuture> save(TenantId tenantId, EntityId entityId, List tsKvEntries, long ttl) { - List> futures = Lists.newArrayListWithExpectedSize(tsKvEntries.size() * INSERTS_PER_ENTRY); + public ListenableFuture save(TenantId tenantId, EntityId entityId, List tsKvEntries, long ttl) { + List> futures = Lists.newArrayListWithExpectedSize(tsKvEntries.size() * INSERTS_PER_ENTRY); for (TsKvEntry tsKvEntry : tsKvEntries) { if (tsKvEntry == null) { throw new IncorrectParameterException("Key value entry can't be null"); } saveAndRegisterFutures(tenantId, futures, entityId, tsKvEntry, ttl); } - return Futures.allAsList(futures); + return Futures.transform(Futures.allAsList(futures), SUM_ALL_INTEGERS, MoreExecutors.directExecutor()); } @Override @@ -135,12 +151,12 @@ public class BaseTimeseriesService implements TimeseriesService { return Futures.allAsList(futures); } - private void saveAndRegisterFutures(TenantId tenantId, List> futures, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { + private void saveAndRegisterFutures(TenantId tenantId, List> futures, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) { throw new IncorrectParameterException("Telemetry data can't be stored for entity view. Read only"); } futures.add(timeseriesDao.savePartition(tenantId, entityId, tsKvEntry.getTs(), tsKvEntry.getKey(), ttl)); - futures.add(timeseriesLatestDao.saveLatest(tenantId, entityId, tsKvEntry)); + futures.add(Futures.transform(timeseriesLatestDao.saveLatest(tenantId, entityId, tsKvEntry), v -> 0, MoreExecutors.directExecutor())); futures.add(timeseriesDao.save(tenantId, entityId, tsKvEntry, ttl)); } @@ -216,7 +232,7 @@ public class BaseTimeseriesService implements TimeseriesService { } else if (query.getAggregation() == null) { throw new IncorrectParameterException("Incorrect ReadTsKvQuery. Aggregation can't be empty"); } - if(!Aggregation.NONE.equals(query.getAggregation())) { + if (!Aggregation.NONE.equals(query.getAggregation())) { long step = Math.max(query.getInterval(), 1000); long intervalCounts = (query.getEndTs() - query.getStartTs()) / step; if (intervalCounts > maxTsIntervals || intervalCounts < 0) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index 2f3fe4854c..b9d8c62833 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -60,6 +60,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; @@ -74,6 +75,8 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD protected static final int MIN_AGGREGATION_STEP_MS = 1000; public static final String ASC_ORDER = "ASC"; + public static final long SECONDS_IN_DAY = TimeUnit.DAYS.toSeconds(1); + protected static List FIXED_PARTITION = Arrays.asList(new Long[]{0L}); @Autowired @@ -141,9 +144,10 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD } @Override - public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { + public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { List> futures = new ArrayList<>(); ttl = computeTtl(ttl); + int dataPointDays = tsKvEntry.getDataPoints() * Math.max(1, (int) (ttl / SECONDS_IN_DAY)); long partition = toPartitionTs(tsKvEntry.getTs()); DataType type = tsKvEntry.getDataType(); if (setNullValuesEnabled) { @@ -161,11 +165,11 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD } BoundStatement stmt = stmtBuilder.build(); futures.add(getFuture(executeAsyncWrite(tenantId, stmt), rs -> null)); - return Futures.transform(Futures.allAsList(futures), result -> null, MoreExecutors.directExecutor()); + return Futures.transform(Futures.allAsList(futures), result -> dataPointDays, MoreExecutors.directExecutor()); } @Override - public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { + public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { if (isFixedPartitioning()) { return Futures.immediateFuture(null); } @@ -181,7 +185,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD stmtBuilder.setInt(4, (int) ttl); } BoundStatement stmt = stmtBuilder.build(); - return getFuture(executeAsyncWrite(tenantId, stmt), rs -> null); + return getFuture(executeAsyncWrite(tenantId, stmt), rs -> 0); } @Override @@ -649,9 +653,10 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD } /** - // * Select existing partitions from the table - // * {@link ModelConstants#TS_KV_PARTITIONS_CF} for the given entity - // */ + * // * Select existing partitions from the table + * // * {@link ModelConstants#TS_KV_PARTITIONS_CF} for the given entity + * // + */ private TbResultSetFuture fetchPartitions(TenantId tenantId, EntityId entityId, String key, long minPartition, long maxPartition) { Select select = QueryBuilder.selectFrom(ModelConstants.TS_KV_PARTITIONS_CF).column(ModelConstants.PARTITION_COLUMN) .whereColumn(ModelConstants.ENTITY_TYPE_COLUMN).isEqualTo(literal(entityId.getEntityType().name())) diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java index dda2b227c3..2dd52580b6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java @@ -31,9 +31,9 @@ public interface TimeseriesDao { ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries); - ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl); + ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl); - ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl); + ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl); ListenableFuture remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query); diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index 26184a5824..e1b494e2e8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java index d04d18772d..ff87e17d56 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java @@ -793,7 +793,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { } } - List>> timeseriesFutures = new ArrayList<>(); + List> timeseriesFutures = new ArrayList<>(); for (int i = 0; i < devices.size(); i++) { Device device = devices.get(i); timeseriesFutures.add(saveLongTimeseries(device.getId(), "temperature", temperatures.get(i))); @@ -1272,7 +1272,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { return attributesService.save(SYSTEM_TENANT_ID, entityId, scope, Collections.singletonList(attr)); } - private ListenableFuture> saveLongTimeseries(EntityId entityId, String key, Double value) { + private ListenableFuture saveLongTimeseries(EntityId entityId, String key, Double value) { TsKvEntity tsKv = new TsKvEntity(); tsKv.setStrKey(key); tsKv.setDoubleValue(value); From 19c860ebfbf78eb77599f945ceaf4faf2f71b6d3 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 3 Nov 2020 19:36:11 +0200 Subject: [PATCH 287/463] fix mqtt-device-profile-transport-configuration.component.ts --- ...ofile-transport-configuration.component.ts | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index c1dc68f0cb..f44becd2f1 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -90,6 +90,11 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required], }) }); + let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; + configurationFormGroup.get('transportPayloadType').valueChanges.subscribe(payloadType => { + this.onTransportPayloadTypeChanged(payloadType, configurationFormGroup); + this.mqttDeviceProfileTransportConfigurationFormGroup.updateValueAndValidity(); + }); this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { this.updateModel(); }); @@ -112,11 +117,8 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control writeValue(value: MqttDeviceProfileTransportConfiguration | null): void { if (isDefinedAndNotNull(value)) { let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; - let payloadType = value.transportPayloadType; - if (payloadType === MqttTransportPayloadType.PROTOBUF) { - configurationFormGroup.registerControl('deviceTelemetryProtoSchema', this.fb.control(null, Validators.required)); - configurationFormGroup.registerControl('deviceAttributesProtoSchema', this.fb.control(null, Validators.required)); - } + let type = value.transportPayloadType; + this.updateTransportPayloadBasedControls(type, configurationFormGroup); this.mqttDeviceProfileTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); } } @@ -126,19 +128,24 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control if (this.mqttDeviceProfileTransportConfigurationFormGroup.valid) { configuration = this.mqttDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration; configuration.type = DeviceTransportType.MQTT; - let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; - let transportPayloadType = configuration.transportPayloadType; - if (transportPayloadType === MqttTransportPayloadType.PROTOBUF) { - configurationFormGroup.registerControl('deviceTelemetryProtoSchema', this.fb.control(null, Validators.required)); - configurationFormGroup.registerControl('deviceAttributesProtoSchema', this.fb.control(null, Validators.required)); - } else { - configurationFormGroup.removeControl('deviceTelemetryProtoSchema'); - configurationFormGroup.removeControl('deviceAttributesProtoSchema'); - } } this.propagateChange(configuration); } + private onTransportPayloadTypeChanged(type: MqttTransportPayloadType, configurationFormGroup: FormGroup) { + this.updateTransportPayloadBasedControls(type, configurationFormGroup) + } + + private updateTransportPayloadBasedControls(type: MqttTransportPayloadType, configurationFormGroup: FormGroup) { + if (type === MqttTransportPayloadType.PROTOBUF) { + configurationFormGroup.registerControl('deviceTelemetryProtoSchema', this.fb.control(null, Validators.required)); + configurationFormGroup.registerControl('deviceAttributesProtoSchema', this.fb.control(null, Validators.required)); + } else { + configurationFormGroup.removeControl('deviceTelemetryProtoSchema'); + configurationFormGroup.removeControl('deviceAttributesProtoSchema'); + } + } + private validationMQTTTopic(): ValidatorFn { return (c: FormControl) => { const newTopic = c.value; From bec3790164cbb647b52362d5ec45b8758f73b7b1 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 3 Nov 2020 19:44:12 +0200 Subject: [PATCH 288/463] clean up ui code --- ...mqtt-device-profile-transport-configuration.component.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index f44becd2f1..972c0412cd 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -92,7 +92,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control }); let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; configurationFormGroup.get('transportPayloadType').valueChanges.subscribe(payloadType => { - this.onTransportPayloadTypeChanged(payloadType, configurationFormGroup); + this.updateTransportPayloadBasedControls(type, configurationFormGroup) this.mqttDeviceProfileTransportConfigurationFormGroup.updateValueAndValidity(); }); this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { @@ -132,10 +132,6 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control this.propagateChange(configuration); } - private onTransportPayloadTypeChanged(type: MqttTransportPayloadType, configurationFormGroup: FormGroup) { - this.updateTransportPayloadBasedControls(type, configurationFormGroup) - } - private updateTransportPayloadBasedControls(type: MqttTransportPayloadType, configurationFormGroup: FormGroup) { if (type === MqttTransportPayloadType.PROTOBUF) { configurationFormGroup.registerControl('deviceTelemetryProtoSchema', this.fb.control(null, Validators.required)); From 5ef30fb2efe43db9eb9f52fd66c101d9a6b7b73c Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 3 Nov 2020 19:45:05 +0200 Subject: [PATCH 289/463] fix typo --- .../mqtt-device-profile-transport-configuration.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index 972c0412cd..f9f815a439 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -92,7 +92,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control }); let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; configurationFormGroup.get('transportPayloadType').valueChanges.subscribe(payloadType => { - this.updateTransportPayloadBasedControls(type, configurationFormGroup) + this.updateTransportPayloadBasedControls(payloadType, configurationFormGroup) this.mqttDeviceProfileTransportConfigurationFormGroup.updateValueAndValidity(); }); this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { From 0ff8d4e7a436e6a3b3597f4ad5dade3740b1735d Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 3 Nov 2020 19:46:30 +0200 Subject: [PATCH 290/463] fix more typos --- ...qtt-device-profile-transport-configuration.component.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index f9f815a439..2b301420fa 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -87,12 +87,12 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control configuration: this.fb.group({ deviceAttributesTopic: [null, [Validators.required, this.validationMQTTTopic()]], deviceTelemetryTopic: [null, [Validators.required, this.validationMQTTTopic()]], - transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required], + transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required] }) }); let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; configurationFormGroup.get('transportPayloadType').valueChanges.subscribe(payloadType => { - this.updateTransportPayloadBasedControls(payloadType, configurationFormGroup) + this.updateTransportPayloadBasedControls(payloadType, configurationFormGroup); this.mqttDeviceProfileTransportConfigurationFormGroup.updateValueAndValidity(); }); this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { @@ -117,8 +117,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control writeValue(value: MqttDeviceProfileTransportConfiguration | null): void { if (isDefinedAndNotNull(value)) { let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; - let type = value.transportPayloadType; - this.updateTransportPayloadBasedControls(type, configurationFormGroup); + this.updateTransportPayloadBasedControls(value.transportPayloadType, configurationFormGroup); this.mqttDeviceProfileTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); } } From 91e13a69bc16faa8573b13b416b29fb4e06ffaa6 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 2 Nov 2020 10:21:44 +0200 Subject: [PATCH 291/463] removed TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES from queue-kafka.env --- docker/queue-kafka.env | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker/queue-kafka.env b/docker/queue-kafka.env index 0207d64ef5..fec3cc50a6 100644 --- a/docker/queue-kafka.env +++ b/docker/queue-kafka.env @@ -1,3 +1,2 @@ TB_QUEUE_TYPE=kafka -TB_KAFKA_SERVERS=kafka:9092 -TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES=retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100 +TB_KAFKA_SERVERS=kafka:9092 \ No newline at end of file From 84ca08d36c64a3d27bd6a395ca01752a622fcf03 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 2 Nov 2020 10:27:24 +0200 Subject: [PATCH 292/463] added new line --- docker/queue-kafka.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/queue-kafka.env b/docker/queue-kafka.env index fec3cc50a6..63107942fb 100644 --- a/docker/queue-kafka.env +++ b/docker/queue-kafka.env @@ -1,2 +1,2 @@ TB_QUEUE_TYPE=kafka -TB_KAFKA_SERVERS=kafka:9092 \ No newline at end of file +TB_KAFKA_SERVERS=kafka:9092 From 938588c5bc0ad5491256d7c63947ed6f826e2c12 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 30 Oct 2020 12:23:08 +0200 Subject: [PATCH 293/463] sqs producer improvements --- .../server/queue/sqs/TbAwsSqsProducerTemplate.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsProducerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsProducerTemplate.java index 3e508b09e6..a4e977b6f0 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsProducerTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsProducerTemplate.java @@ -87,8 +87,9 @@ public class TbAwsSqsProducerTemplate implements TbQueuePr sendMsgRequest.withQueueUrl(getQueueUrl(tpi.getFullTopicName())); sendMsgRequest.withMessageBody(gson.toJson(new DefaultTbQueueMsg(msg))); - sendMsgRequest.withMessageGroupId(tpi.getTopic()); - sendMsgRequest.withMessageDeduplicationId(UUID.randomUUID().toString()); + String sqsMsgId = UUID.randomUUID().toString(); + sendMsgRequest.withMessageGroupId(sqsMsgId); + sendMsgRequest.withMessageDeduplicationId(sqsMsgId); ListenableFuture future = producerExecutor.submit(() -> sqsClient.sendMessage(sendMsgRequest)); From eeec6bac9fe510e0db95d597bf14bc8d836a55eb Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 30 Oct 2020 13:29:48 +0200 Subject: [PATCH 294/463] sqs producer improvements --- msa/js-executor/queue/awsSqsTemplate.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/msa/js-executor/queue/awsSqsTemplate.js b/msa/js-executor/queue/awsSqsTemplate.js index 52d4997f33..24315e5cad 100644 --- a/msa/js-executor/queue/awsSqsTemplate.js +++ b/msa/js-executor/queue/awsSqsTemplate.js @@ -52,11 +52,13 @@ function AwsSqsProducer() { queueUrls.set(responseTopic, responseQueueUrl); } + let msgId = uuid(); + let params = { MessageBody: msgBody, QueueUrl: responseQueueUrl, - MessageGroupId: 'js_eval', - MessageDeduplicationId: uuid() + MessageGroupId: msgId, + MessageDeduplicationId: msgId }; return new Promise((resolve, reject) => { From ecf530c63ebc489e01b1857bc0ba55875078c10a Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 3 Nov 2020 17:33:57 +0200 Subject: [PATCH 295/463] alarm state improvements (added details to alarm from device profile details pattern) --- .../rule/engine/profile/AlarmState.java | 53 ++++++++++++++++--- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index df46a4bf88..56fe68f010 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.profile; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.action.TbAlarmResult; @@ -29,6 +30,7 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.query.EntityKeyType; +import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceQueue; @@ -74,7 +76,7 @@ class AlarmState { public boolean createOrClearAlarms(TbContext ctx, T data, SnapshotUpdate update, BiFunction evalFunction) { boolean stateUpdate = false; - AlarmSeverity resultSeverity = null; + AlarmRuleState resultState = null; log.debug("[{}] processing update: {}", alarmDefinition.getId(), data); for (AlarmRuleState state : createRulesSortedBySeverityDesc) { if (!validateUpdate(update, state)) { @@ -84,15 +86,15 @@ class AlarmState { AlarmEvalResult evalResult = evalFunction.apply(state, data); stateUpdate |= state.checkUpdate(); if (AlarmEvalResult.TRUE.equals(evalResult)) { - resultSeverity = state.getSeverity(); + resultState = state; break; } else if (AlarmEvalResult.FALSE.equals(evalResult)) { state.clear(); stateUpdate |= state.checkUpdate(); } } - if (resultSeverity != null) { - TbAlarmResult result = calculateAlarmResult(ctx, resultSeverity); + if (resultState != null) { + TbAlarmResult result = calculateAlarmResult(ctx, resultState, data); if (result != null) { pushMsg(ctx, result); } @@ -186,7 +188,8 @@ class AlarmState { } } - private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmSeverity severity) { + private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmRuleState ruleState, T data) { + AlarmSeverity severity = ruleState.getSeverity(); if (currentAlarm != null) { // TODO: In some extremely rare cases, we might miss the event of alarm clear (If one use in-mem queue and restarted the server) or (if one manipulated the rule chain). // Maybe we should fetch alarm every time? @@ -212,7 +215,7 @@ class AlarmState { currentAlarm.setSeverity(severity); currentAlarm.setStartTs(System.currentTimeMillis()); currentAlarm.setEndTs(currentAlarm.getStartTs()); - currentAlarm.setDetails(JacksonUtil.OBJECT_MAPPER.createObjectNode()); + currentAlarm.setDetails(createDetails(ruleState, (DataSnapshot) data)); currentAlarm.setOriginator(originator); currentAlarm.setTenantId(ctx.getTenantId()); currentAlarm.setPropagate(alarmDefinition.isPropagate()); @@ -225,6 +228,44 @@ class AlarmState { } } + private JsonNode createDetails(AlarmRuleState ruleState, DataSnapshot dataSnapshot) { + ObjectNode details = JacksonUtil.OBJECT_MAPPER.createObjectNode(); + String alarmDetails = ruleState.getAlarmRule().getAlarmDetails(); + + if (alarmDetails != null) { + for (KeyFilter keyFilter : ruleState.getAlarmRule().getCondition().getCondition()) { + EntityKeyValue entityKeyValue = dataSnapshot.getValue(keyFilter.getKey()); + alarmDetails = alarmDetails.replaceAll(String.format("\\$\\{%s}", keyFilter.getKey().getKey()), getValueAsString(entityKeyValue)); + } + + details.put("data", alarmDetails); + } + + return details; + } + + private static String getValueAsString(EntityKeyValue entityKeyValue) { + Object result = null; + switch (entityKeyValue.getDataType()) { + case STRING: + result = entityKeyValue.getStrValue(); + break; + case JSON: + result = entityKeyValue.getJsonValue(); + break; + case LONG: + result = String.valueOf(entityKeyValue.getLngValue()); + break; + case DOUBLE: + result = String.valueOf(entityKeyValue.getDblValue()); + break; + case BOOLEAN: + result = String.valueOf(entityKeyValue.getBoolValue()); + break; + } + return String.valueOf(result); + } + public boolean processAlarmClear(TbContext ctx, Alarm alarmNf) { boolean updated = false; if (currentAlarm != null && currentAlarm.getId().equals(alarmNf.getId())) { From da8a03e98efb359ba40556ab22d9c29c40005207 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 4 Nov 2020 13:40:38 +0200 Subject: [PATCH 296/463] refactored --- .../org/thingsboard/rule/engine/profile/AlarmState.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 56fe68f010..0d729c5d69 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -254,13 +254,13 @@ class AlarmState { result = entityKeyValue.getJsonValue(); break; case LONG: - result = String.valueOf(entityKeyValue.getLngValue()); + result = entityKeyValue.getLngValue(); break; case DOUBLE: - result = String.valueOf(entityKeyValue.getDblValue()); + result = entityKeyValue.getDblValue(); break; case BOOLEAN: - result = String.valueOf(entityKeyValue.getBoolValue()); + result = entityKeyValue.getBoolValue(); break; } return String.valueOf(result); From 1c4ab0e71162942353e2d2b4403da12a8b6c107b Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 4 Nov 2020 18:02:37 +0200 Subject: [PATCH 297/463] Revert "refactored" This reverts commit da8a03e98efb359ba40556ab22d9c29c40005207. --- .../org/thingsboard/rule/engine/profile/AlarmState.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 0d729c5d69..56fe68f010 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -254,13 +254,13 @@ class AlarmState { result = entityKeyValue.getJsonValue(); break; case LONG: - result = entityKeyValue.getLngValue(); + result = String.valueOf(entityKeyValue.getLngValue()); break; case DOUBLE: - result = entityKeyValue.getDblValue(); + result = String.valueOf(entityKeyValue.getDblValue()); break; case BOOLEAN: - result = entityKeyValue.getBoolValue(); + result = String.valueOf(entityKeyValue.getBoolValue()); break; } return String.valueOf(result); From a99a8e458c3bbfc0d25b960547154a3351daf2b4 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 4 Nov 2020 18:02:37 +0200 Subject: [PATCH 298/463] Revert "alarm state improvements (added details to alarm from device profile details pattern)" This reverts commit ecf530c63ebc489e01b1857bc0ba55875078c10a. --- .../rule/engine/profile/AlarmState.java | 53 +++---------------- 1 file changed, 6 insertions(+), 47 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 56fe68f010..df46a4bf88 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -16,7 +16,6 @@ package org.thingsboard.rule.engine.profile; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.action.TbAlarmResult; @@ -30,7 +29,6 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.query.EntityKeyType; -import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceQueue; @@ -76,7 +74,7 @@ class AlarmState { public boolean createOrClearAlarms(TbContext ctx, T data, SnapshotUpdate update, BiFunction evalFunction) { boolean stateUpdate = false; - AlarmRuleState resultState = null; + AlarmSeverity resultSeverity = null; log.debug("[{}] processing update: {}", alarmDefinition.getId(), data); for (AlarmRuleState state : createRulesSortedBySeverityDesc) { if (!validateUpdate(update, state)) { @@ -86,15 +84,15 @@ class AlarmState { AlarmEvalResult evalResult = evalFunction.apply(state, data); stateUpdate |= state.checkUpdate(); if (AlarmEvalResult.TRUE.equals(evalResult)) { - resultState = state; + resultSeverity = state.getSeverity(); break; } else if (AlarmEvalResult.FALSE.equals(evalResult)) { state.clear(); stateUpdate |= state.checkUpdate(); } } - if (resultState != null) { - TbAlarmResult result = calculateAlarmResult(ctx, resultState, data); + if (resultSeverity != null) { + TbAlarmResult result = calculateAlarmResult(ctx, resultSeverity); if (result != null) { pushMsg(ctx, result); } @@ -188,8 +186,7 @@ class AlarmState { } } - private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmRuleState ruleState, T data) { - AlarmSeverity severity = ruleState.getSeverity(); + private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmSeverity severity) { if (currentAlarm != null) { // TODO: In some extremely rare cases, we might miss the event of alarm clear (If one use in-mem queue and restarted the server) or (if one manipulated the rule chain). // Maybe we should fetch alarm every time? @@ -215,7 +212,7 @@ class AlarmState { currentAlarm.setSeverity(severity); currentAlarm.setStartTs(System.currentTimeMillis()); currentAlarm.setEndTs(currentAlarm.getStartTs()); - currentAlarm.setDetails(createDetails(ruleState, (DataSnapshot) data)); + currentAlarm.setDetails(JacksonUtil.OBJECT_MAPPER.createObjectNode()); currentAlarm.setOriginator(originator); currentAlarm.setTenantId(ctx.getTenantId()); currentAlarm.setPropagate(alarmDefinition.isPropagate()); @@ -228,44 +225,6 @@ class AlarmState { } } - private JsonNode createDetails(AlarmRuleState ruleState, DataSnapshot dataSnapshot) { - ObjectNode details = JacksonUtil.OBJECT_MAPPER.createObjectNode(); - String alarmDetails = ruleState.getAlarmRule().getAlarmDetails(); - - if (alarmDetails != null) { - for (KeyFilter keyFilter : ruleState.getAlarmRule().getCondition().getCondition()) { - EntityKeyValue entityKeyValue = dataSnapshot.getValue(keyFilter.getKey()); - alarmDetails = alarmDetails.replaceAll(String.format("\\$\\{%s}", keyFilter.getKey().getKey()), getValueAsString(entityKeyValue)); - } - - details.put("data", alarmDetails); - } - - return details; - } - - private static String getValueAsString(EntityKeyValue entityKeyValue) { - Object result = null; - switch (entityKeyValue.getDataType()) { - case STRING: - result = entityKeyValue.getStrValue(); - break; - case JSON: - result = entityKeyValue.getJsonValue(); - break; - case LONG: - result = String.valueOf(entityKeyValue.getLngValue()); - break; - case DOUBLE: - result = String.valueOf(entityKeyValue.getDblValue()); - break; - case BOOLEAN: - result = String.valueOf(entityKeyValue.getBoolValue()); - break; - } - return String.valueOf(result); - } - public boolean processAlarmClear(TbContext ctx, Alarm alarmNf) { boolean updated = false; if (currentAlarm != null && currentAlarm.getId().equals(alarmNf.getId())) { From 6dbb4897b4816a4aa4733e0745f18dd5cd755a43 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 4 Nov 2020 20:17:56 +0200 Subject: [PATCH 299/463] UI: Fixed resize width windows device-wizard-dialog; improvement translate en_US --- .../wizard/device-wizard-dialog.component.html | 5 ++++- .../wizard/device-wizard-dialog.component.scss | 10 ++++++++++ ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index 362b3c14ce..8e91be0cc5 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -82,7 +82,8 @@ [selectFirstProfile]="true" [editProfileEnabled]="false"> - + device-profile.new-device-profile-name @@ -93,12 +94,14 @@
diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss index 55878d638b..b84ff8105f 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss @@ -22,6 +22,10 @@ max-height: 75vh; } } + + .invisible{ + visibility: hidden; + } } :host ::ng-deep { @@ -60,6 +64,12 @@ height: 100%; } } + + tb-device-profile-autocomplete, tb-queue-type-list{ + .mat-form-field-wrapper{ + width: 180px !important; + } + } } } } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index e3b7b4b81d..5c044cbff0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -874,7 +874,7 @@ "profile-configuration": "Profile configuration", "transport-configuration": "Transport configuration", "default-rule-chain": "Default rule chain", - "select-queue-hint": "The queue name can be selected from a drop-down list or add a custom name.", + "select-queue-hint": "Select from a drop-down list or add a custom name.", "delete-device-profile-title": "Are you sure you want to delete the device profile '{{deviceProfileName}}'?", "delete-device-profile-text": "Be careful, after the confirmation the device profile and all related data will become unrecoverable.", "delete-device-profiles-title": "Are you sure you want to delete { count, plural, 1 {1 device profile} other {# device profiles} }?", From f5550f52cfcb7b041b6d5b40561ef2a6508d8758 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Wed, 4 Nov 2020 23:17:01 +0200 Subject: [PATCH 300/463] Refactoring: add invisible elements --- .../home/components/wizard/device-wizard-dialog.component.html | 1 + 1 file changed, 1 insertion(+) diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index 8e91be0cc5..57bf9ed5cb 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -76,6 +76,7 @@ [required]="!createProfile" [transportType]="deviceWizardFormGroup.get('transportType').value" formControlName="deviceProfileId" + [ngClass]="{invisible: deviceWizardFormGroup.get('addProfileType').value !== 0}" (deviceProfileChanged)="$event?.transportType ? deviceWizardFormGroup.get('transportType').patchValue($event?.transportType) : {}" [addNewProfile]="false" [selectDefaultProfile]="true" From 73489beaf66e76172413ffc8d0eb92b9d2127e79 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 5 Nov 2020 15:43:04 +0200 Subject: [PATCH 301/463] fix exceptions message regex, tests, ui code clean up --- .../src/main/resources/thingsboard.yml | 2 +- .../server/mqtt/proto/DynamicProtoTest.java | 26 +++++++++---------- .../DeviceProfileTransportConfiguration.java | 1 - ...toDeviceProfileTransportConfiguration.java | 6 ++--- ...ofile-transport-configuration.component.ts | 2 +- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index deb28ffd99..c935154b6e 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -429,7 +429,7 @@ spring: database-platform: "${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}" datasource: driverClassName: "${SPRING_DRIVER_CLASS_NAME:org.postgresql.Driver}" - url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard}" + url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard_ce_3_2}" username: "${SPRING_DATASOURCE_USERNAME:postgres}" password: "${SPRING_DATASOURCE_PASSWORD:postgres}" hikari: diff --git a/application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java b/application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java index eb6912f257..a5b79310ec 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java @@ -229,67 +229,67 @@ public class DynamicProtoTest { @Test public void testDynamicSchemaProtoFileValidation() { - processValidation("Failed to parse: testParseToProtoFile schema due to: Syntax error in :6:4: 'required' label forbidden in proto3 field declarations", IVALID_PROTO_SCHEMA_REQUIRED_FIELD_EXISTS, "testParseToProtoFile"); + processValidation("[Transport Configuration] failed to parse testParseToProtoFile due to: Syntax error in :6:4: 'required' label forbidden in proto3 field declarations", IVALID_PROTO_SCHEMA_REQUIRED_FIELD_EXISTS, "testParseToProtoFile"); } @Test public void testDynamicSchemaSyntaxValidation() { - processValidation("Invalid schema syntax: proto2 for: testSyntaxValidation provided! Only proto3 allowed!", INVALID_PROTO_SCHEMA_NOT_VALID_SYNTAX, "testSyntaxValidation"); + processValidation("[Transport Configuration] invalid schema syntax: proto2 for: testSyntaxValidation provided! Only proto3 allowed!", INVALID_PROTO_SCHEMA_NOT_VALID_SYNTAX, "testSyntaxValidation"); } @Test public void testDynamicSchemaOptionsValidation() { - processValidation("Invalid testOptionsValidation schema provided! Schema options don't support!", INVALID_PROTO_SCHEMA_OPTIONS_NOT_SUPPORTED, "testOptionsValidation"); + processValidation("[Transport Configuration] invalid testOptionsValidation schema provided! Schema options don't support!", INVALID_PROTO_SCHEMA_OPTIONS_NOT_SUPPORTED, "testOptionsValidation"); } @Test public void testDynamicSchemaPublicImportsValidation() { - processValidation("Invalid testPublicImportsValidation schema provided! Schema public imports don't support!", INVALID_PROTO_SCHEMA_PUBLIC_IMPORTS_NOT_SUPPORTED, "testPublicImportsValidation"); + processValidation("[Transport Configuration] invalid testPublicImportsValidation schema provided! Schema public imports don't support!", INVALID_PROTO_SCHEMA_PUBLIC_IMPORTS_NOT_SUPPORTED, "testPublicImportsValidation"); } @Test public void testDynamicSchemaImportsValidation() { - processValidation("Invalid testImportsValidation schema provided! Schema imports don't support!", INVALID_PROTO_SCHEMA_IMPORTS_NOT_SUPPORTED, "testImportsValidation"); + processValidation("[Transport Configuration] invalid testImportsValidation schema provided! Schema imports don't support!", INVALID_PROTO_SCHEMA_IMPORTS_NOT_SUPPORTED, "testImportsValidation"); } @Test public void testDynamicSchemaExtendDeclarationsValidation() { - processValidation("Invalid testExtendDeclarationsValidation schema provided! Schema extend declarations don't support!", INVALID_PROTO_SCHEMA_EXTEND_DECLARATION_NOT_SUPPORTED, "testExtendDeclarationsValidation"); + processValidation("[Transport Configuration] invalid testExtendDeclarationsValidation schema provided! Schema extend declarations don't support!", INVALID_PROTO_SCHEMA_EXTEND_DECLARATION_NOT_SUPPORTED, "testExtendDeclarationsValidation"); } @Test public void testDynamicSchemaEnumOptionsValidation() { - processValidation("Invalid testEnumOptionsValidation schema provided! Enum definitions options are not supported!", INVALID_PROTO_SCHEMA_ENUM_OPTIONS_NOT_SUPPORTED, "testEnumOptionsValidation"); + processValidation("[Transport Configuration] invalid testEnumOptionsValidation schema provided! Enum definitions options are not supported!", INVALID_PROTO_SCHEMA_ENUM_OPTIONS_NOT_SUPPORTED, "testEnumOptionsValidation"); } @Test public void testDynamicSchemaNoOneMessageTypeExistsValidation() { - processValidation("Invalid noOneMessageTypeExists schema provided! At least one Message definition should exists!", INVALID_PROTO_SCHEMA_NO_MESSAGE_TYPES_EXISTS, "noOneMessageTypeExists"); + processValidation("[Transport Configuration] invalid noOneMessageTypeExists schema provided! At least one Message definition should exists!", INVALID_PROTO_SCHEMA_NO_MESSAGE_TYPES_EXISTS, "noOneMessageTypeExists"); } @Test public void testDynamicSchemaMessageTypeOptionsValidation() { - processValidation("Invalid messageTypeOptions schema provided! Message definition options don't support!", INVALID_PROTO_SCHEMA_MESSAGE_OPTIONS_NOT_SUPPORTED, "messageTypeOptions"); + processValidation("[Transport Configuration] invalid messageTypeOptions schema provided! Message definition options don't support!", INVALID_PROTO_SCHEMA_MESSAGE_OPTIONS_NOT_SUPPORTED, "messageTypeOptions"); } @Test public void testDynamicSchemaMessageTypeExtensionsValidation() { - processValidation("Invalid messageTypeExtensions schema provided! Message definition extensions don't support!", INVALID_PROTO_SCHEMA_MESSAGE_EXTENSIONS_NOT_SUPPORTED, "messageTypeExtensions"); + processValidation("[Transport Configuration] invalid messageTypeExtensions schema provided! Message definition extensions don't support!", INVALID_PROTO_SCHEMA_MESSAGE_EXTENSIONS_NOT_SUPPORTED, "messageTypeExtensions"); } @Test public void testDynamicSchemaMessageTypeReservedElementsValidation() { - processValidation("Invalid messageTypeReservedElements schema provided! Message definition reserved elements don't support!", INVALID_PROTO_SCHEMA_MESSAGE_RESERVED_NOT_SUPPORTED, "messageTypeReservedElements"); + processValidation("[Transport Configuration] invalid messageTypeReservedElements schema provided! Message definition reserved elements don't support!", INVALID_PROTO_SCHEMA_MESSAGE_RESERVED_NOT_SUPPORTED, "messageTypeReservedElements"); } @Test public void testDynamicSchemaMessageTypeGroupsElementsValidation() { - processValidation("Invalid messageTypeGroupsElements schema provided! Message definition groups don't support!", INVALID_PROTO_SCHEMA_MESSAGE_GROUPS_NOT_SUPPORTED, "messageTypeGroupsElements"); + processValidation("[Transport Configuration] invalid messageTypeGroupsElements schema provided! Message definition groups don't support!", INVALID_PROTO_SCHEMA_MESSAGE_GROUPS_NOT_SUPPORTED, "messageTypeGroupsElements"); } @Test public void testDynamicSchemaOneOfsTypeGroupsElementsValidation() { - processValidation("Invalid oneOfsTypeGroupsElements schema provided! OneOf definition groups don't support!", INVALID_PROTO_SCHEMA_ONE_OFS_GROUPS_NOT_SUPPORTED, "oneOfsTypeGroupsElements"); + processValidation("[Transport Configuration] invalid oneOfsTypeGroupsElements schema provided! OneOf definition groups don't support!", INVALID_PROTO_SCHEMA_ONE_OFS_GROUPS_NOT_SUPPORTED, "oneOfsTypeGroupsElements"); } private void processValidation(String expectedMessage, String schema, String schemaName) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java index 5447866b4d..e685692895 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java @@ -33,7 +33,6 @@ import org.thingsboard.server.common.data.DeviceTransportType; @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M")}) public interface DeviceProfileTransportConfiguration { -// @JsonIgnore DeviceTransportType getType(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java index f0833ba292..158acfaf2a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java @@ -56,7 +56,7 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf public static final String TELEMETRY_PROTO_SCHEMA = "telemetry proto schema"; public static String invalidSchemaProvidedMessage(String schemaName) { - return "Invalid " + schemaName + " schema provided!"; + return "[Transport Configuration] invalid " + schemaName + " schema provided!"; } private String deviceTelemetryProtoSchema; @@ -73,7 +73,7 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf try { protoFileElement = schemaParser.readProtoFile(); } catch (Exception e) { - throw new IllegalArgumentException("Failed to parse: " + schemaName + " schema due to: " + e.getMessage()); + throw new IllegalArgumentException("[Transport Configuration] failed to parse " + schemaName + " due to: " + e.getMessage()); } checkProtoFileSyntax(schemaName, protoFileElement); checkProtoFileCommonSettings(schemaName, protoFileElement.getOptions().isEmpty(), " Schema options don't support!"); @@ -127,7 +127,7 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf private void checkProtoFileSyntax(String schemaName, ProtoFileElement protoFileElement) { if (protoFileElement.getSyntax() == null || !protoFileElement.getSyntax().equals(Syntax.PROTO_3)) { - throw new IllegalArgumentException("Invalid schema syntax: " + protoFileElement.getSyntax() + + throw new IllegalArgumentException("[Transport Configuration] invalid schema syntax: " + protoFileElement.getSyntax() + " for: " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!"); } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index 2b301420fa..5569a0b530 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -34,7 +34,7 @@ import { MqttTransportPayloadType, mqttTransportPayloadTypeTranslationMap } from '@shared/models/device.models'; -import {isDefinedAndNotNull} from '@core/utils'; +import { isDefinedAndNotNull } from '@core/utils'; @Component({ selector: 'tb-mqtt-device-profile-transport-configuration', From 39c2e3c39dd11f2f4bd9cc41a0a72cf031f39d22 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 6 Nov 2020 15:58:00 +0200 Subject: [PATCH 302/463] UI: Fixed updated Alarm widget in change entity state parameters --- .../src/app/core/api/alarm-data-subscription.ts | 4 ++-- ui-ngx/src/app/core/api/alarm-data.service.ts | 17 ++++++++--------- ui-ngx/src/app/core/api/widget-subscription.ts | 4 ++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/ui-ngx/src/app/core/api/alarm-data-subscription.ts b/ui-ngx/src/app/core/api/alarm-data-subscription.ts index 4d3524324e..35a43c3f0f 100644 --- a/ui-ngx/src/app/core/api/alarm-data-subscription.ts +++ b/ui-ngx/src/app/core/api/alarm-data-subscription.ts @@ -52,6 +52,7 @@ export interface AlarmDataSubscriptionOptions { export class AlarmDataSubscription { + private alarmDataSubscriptionOptions = this.listener.alarmDataSubscriptionOptions; private datasourceType: DatasourceType = this.alarmDataSubscriptionOptions.datasourceType; private history: boolean; @@ -65,8 +66,7 @@ export class AlarmDataSubscription { private subsTw: SubscriptionTimewindow; - constructor(public alarmDataSubscriptionOptions: AlarmDataSubscriptionOptions, - private listener: AlarmDataListener, + constructor(private listener: AlarmDataListener, private telemetryService: TelemetryService) { } diff --git a/ui-ngx/src/app/core/api/alarm-data.service.ts b/ui-ngx/src/app/core/api/alarm-data.service.ts index 23488c99c5..3454036db7 100644 --- a/ui-ngx/src/app/core/api/alarm-data.service.ts +++ b/ui-ngx/src/app/core/api/alarm-data.service.ts @@ -32,6 +32,7 @@ export interface AlarmDataListener { alarmSource: Datasource; alarmsLoaded: (pageData: PageData, allowedEntities: number, totalEntities: number) => void; alarmsUpdated: (update: Array, pageData: PageData) => void; + alarmDataSubscriptionOptions?: AlarmDataSubscriptionOptions; subscription?: AlarmDataSubscription; } @@ -47,11 +48,11 @@ export class AlarmDataService { pageLink: AlarmDataPageLink, keyFilters: KeyFilter[]) { const alarmSource = listener.alarmSource; + listener.alarmDataSubscriptionOptions = this.createAlarmSubscriptionOptions(listener, pageLink, keyFilters); if (alarmSource.type === DatasourceType.entity && (!alarmSource.entityFilter || !pageLink)) { return; } - listener.subscription = this.createSubscription(listener, - pageLink, alarmSource.keyFilters, keyFilters); + listener.subscription = new AlarmDataSubscription(listener, this.telemetryService); return listener.subscription.subscribe(); } @@ -61,10 +62,9 @@ export class AlarmDataService { } } - private createSubscription(listener: AlarmDataListener, - pageLink: AlarmDataPageLink, - keyFilters: KeyFilter[], - additionalKeyFilters: KeyFilter[]): AlarmDataSubscription { + private createAlarmSubscriptionOptions(listener: AlarmDataListener, + pageLink: AlarmDataPageLink, + additionalKeyFilters: KeyFilter[]): AlarmDataSubscriptionOptions { const alarmSource = listener.alarmSource; const alarmSubscriptionDataKeys: Array = []; alarmSource.dataKeys.forEach((dataKey) => { @@ -82,11 +82,10 @@ export class AlarmDataService { if (alarmDataSubscriptionOptions.datasourceType === DatasourceType.entity) { alarmDataSubscriptionOptions.entityFilter = alarmSource.entityFilter; alarmDataSubscriptionOptions.pageLink = pageLink; - alarmDataSubscriptionOptions.keyFilters = keyFilters; + alarmDataSubscriptionOptions.keyFilters = alarmSource.keyFilters; alarmDataSubscriptionOptions.additionalKeyFilters = additionalKeyFilters; } - return new AlarmDataSubscription(alarmDataSubscriptionOptions, - listener, this.telemetryService); + return alarmDataSubscriptionOptions; } } diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index f7017652d8..070538ddc5 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -960,8 +960,8 @@ export class WidgetSubscription implements IWidgetSubscription { private updateAlarmDataSubscription() { if (this.alarmDataListener) { - const pageLink = this.alarmDataListener.subscription.alarmDataSubscriptionOptions.pageLink; - const keyFilters = this.alarmDataListener.subscription.alarmDataSubscriptionOptions.additionalKeyFilters; + const pageLink = this.alarmDataListener.alarmDataSubscriptionOptions.pageLink; + const keyFilters = this.alarmDataListener.alarmDataSubscriptionOptions.additionalKeyFilters; this.subscribeForAlarms(pageLink, keyFilters); } } From 5f50fd44d2e6fbb53f4cab1a32bb52fc69748c35 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 9 Nov 2020 14:06:20 +0200 Subject: [PATCH 303/463] Usage Records implementation --- .../server/controller/TenantController.java | 2 ++ .../apiusage/DefaultTbApiUsageStateService.java | 9 +++++++-- .../thingsboard/server/common/data/TenantProfile.java | 11 +++++++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantController.java b/application/src/main/java/org/thingsboard/server/controller/TenantController.java index 83e3d98b1b..a42cbb0f91 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantController.java @@ -93,6 +93,8 @@ public class TenantController extends BaseController { } tenantProfileCache.evict(tenant.getId()); tbClusterService.onTenantChange(tenant, null); + tbClusterService.onEntityStateChange(tenant.getId(), tenant.getId(), + newTenant ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); return tenant; } catch (Exception e) { throw handleException(e); diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 42f980909a..2c8879ba94 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -243,6 +243,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { private void updateTenantState(TenantApiUsageState state, TenantProfile tenantProfile) { TenantProfileData oldProfileData = state.getTenantProfileData(); + state.setTenantProfileId(tenantProfile.getId()); state.setTenantProfileData(tenantProfile.getProfileData()); Map result = state.checkStateUpdatedDueToThresholds(); if (!result.isEmpty()) { @@ -257,8 +258,10 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { long ts = System.currentTimeMillis(); List profileThresholds = new ArrayList<>(); for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { - if (oldData == null || oldData.getProfileThreshold(key) != newData.getProfileThreshold(key)) { - profileThresholds.add(new BasicTsKvEntry(ts, new LongDataEntry(key.getApiLimitKey(), newData.getProfileThreshold(key)))); + long newProfileThreshold = newData.getProfileThreshold(key); + if (oldData == null || oldData.getProfileThreshold(key) != newProfileThreshold) { + log.info("[{}] Updating profile threshold [{}]:[{}]", tenantId, key, newProfileThreshold); + profileThresholds.add(new BasicTsKvEntry(ts, new LongDataEntry(key.getApiLimitKey(), newProfileThreshold))); } } if (!profileThresholds.isEmpty()) { @@ -267,6 +270,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } private void persistAndNotify(TenantApiUsageState state, Map result) { + log.info("[{}] Detected update of the API state: {}", state.getTenantId(), result); apiUsageStateService.update(state.getApiUsageState()); clusterService.onApiStateChange(state.getApiUsageState(), null); long ts = System.currentTimeMillis(); @@ -320,6 +324,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } } + log.debug("[{}] Initialized state: {}", tenantId, dbStateEntity); myTenantStates.put(tenantId, tenantState); } catch (InterruptedException | ExecutionException e) { log.warn("[{}] Failed to fetch api usage state from db.", tenantId, e); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java index 5e5f4a09de..acb9c239b8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java @@ -21,6 +21,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.id.TenantProfileId; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import java.io.ByteArrayInputStream; @@ -79,15 +80,21 @@ public class TenantProfile extends SearchTextBased implements H profileData = mapper.readValue(new ByteArrayInputStream(profileDataBytes), TenantProfileData.class); } catch (IOException e) { log.warn("Can't deserialize tenant profile data: ", e); - return null; + return createDefaultTenantProfileData(); } return profileData; } else { - return null; + return createDefaultTenantProfileData(); } } } + public TenantProfileData createDefaultTenantProfileData() { + TenantProfileData tpd = new TenantProfileData(); + tpd.setConfiguration(new DefaultTenantProfileConfiguration()); + return tpd; + } + public void setProfileData(TenantProfileData data) { this.profileData = data; try { From d40f5823ff1c7d996f7deea208826f17b4284346 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 9 Nov 2020 15:18:37 +0200 Subject: [PATCH 304/463] Add getEntityTimeseries method to Attributes Service --- ui-ngx/src/app/core/http/attribute.service.ts | 30 ++++++++++++++++++- .../models/telemetry/telemetry.models.ts | 14 +++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/core/http/attribute.service.ts b/ui-ngx/src/app/core/http/attribute.service.ts index 044b9314ba..775360be5c 100644 --- a/ui-ngx/src/app/core/http/attribute.service.ts +++ b/ui-ngx/src/app/core/http/attribute.service.ts @@ -19,8 +19,9 @@ import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { forkJoin, Observable, of } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { EntityId } from '@shared/models/id/entity-id'; -import { AttributeData, AttributeScope } from '@shared/models/telemetry/telemetry.models'; +import { AttributeData, AttributeScope, DataSortOrder, TimeseriesData } from '@shared/models/telemetry/telemetry.models'; import { isDefinedAndNotNull } from '@core/utils'; +import { AggregationType } from '@shared/models/time/time.models'; @Injectable({ providedIn: 'root' @@ -110,4 +111,31 @@ export class AttributeService { } return forkJoin([saveEntityTimeseriesObservable, deleteEntityTimeseriesObservable]); } + + public getEntityTimeseries(entityId: EntityId, keys: Array, startTs: number, endTs: number, + limit: number = 100, agg: AggregationType = AggregationType.NONE, interval?: number, + orderBy: DataSortOrder = DataOrder.DESC, useStrictDataTypes: boolean = false, + config?: RequestConfig): Observable { + let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/values/timeseries`; + url += `?keys=${keys.join(',')}`; + url += `&startTs=${startTs}`; + url += `&endTs=${endTs}`; + if (isDefinedAndNotNull(limit)) { + url += `&limit=${limit}`; + } + if (isDefinedAndNotNull(agg)) { + url += `&agg=${agg}`; + } + if (isDefinedAndNotNull(interval)) { + url += `&interval=${interval}`; + } + if (isDefinedAndNotNull(orderBy)) { + url += `&orderBy=${orderBy}`; + } + if (isDefinedAndNotNull(useStrictDataTypes)) { + url += `&useStrictDataTypes=${useStrictDataTypes}`; + } + + return this.http.get(url, defaultHttpOptionsFromConfig(config)); + } } diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index 18858bdbf7..28a887fe9b 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -81,6 +81,20 @@ export interface AttributeData { value: any; } +export interface TimeseriesData { + [key: string]: Array; +} + +export interface TimeseriesKeyData { + value: any; + ts: number; +} + +export enum DataSortOrder { + ASC = 'ASC', + DESC = 'DESC' +} + export interface WebsocketCmd { cmdId: number; } From 9f5d3a6c9533b146ca6fe3f0a455f531ff671a5e Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 9 Nov 2020 15:32:19 +0200 Subject: [PATCH 305/463] Type correction --- ui-ngx/src/app/core/http/attribute.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/core/http/attribute.service.ts b/ui-ngx/src/app/core/http/attribute.service.ts index 775360be5c..1fce00290d 100644 --- a/ui-ngx/src/app/core/http/attribute.service.ts +++ b/ui-ngx/src/app/core/http/attribute.service.ts @@ -114,7 +114,7 @@ export class AttributeService { public getEntityTimeseries(entityId: EntityId, keys: Array, startTs: number, endTs: number, limit: number = 100, agg: AggregationType = AggregationType.NONE, interval?: number, - orderBy: DataSortOrder = DataOrder.DESC, useStrictDataTypes: boolean = false, + orderBy: DataSortOrder = DataSortOrder.DESC, useStrictDataTypes: boolean = false, config?: RequestConfig): Observable { let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/values/timeseries`; url += `?keys=${keys.join(',')}`; From ae8686605f4dfe6d8a34dd877be124dbb7213021 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 9 Nov 2020 15:54:05 +0200 Subject: [PATCH 306/463] Refactoring --- ui-ngx/src/app/core/http/attribute.service.ts | 7 ++----- .../shared/models/telemetry/telemetry.models.ts | 16 +++++++++------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/ui-ngx/src/app/core/http/attribute.service.ts b/ui-ngx/src/app/core/http/attribute.service.ts index 1fce00290d..c9e4246d86 100644 --- a/ui-ngx/src/app/core/http/attribute.service.ts +++ b/ui-ngx/src/app/core/http/attribute.service.ts @@ -116,10 +116,7 @@ export class AttributeService { limit: number = 100, agg: AggregationType = AggregationType.NONE, interval?: number, orderBy: DataSortOrder = DataSortOrder.DESC, useStrictDataTypes: boolean = false, config?: RequestConfig): Observable { - let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/values/timeseries`; - url += `?keys=${keys.join(',')}`; - url += `&startTs=${startTs}`; - url += `&endTs=${endTs}`; + let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/values/timeseries?keys=${keys.join(',')}&startTs=${startTs}&endTs=${endTs}`; if (isDefinedAndNotNull(limit)) { url += `&limit=${limit}`; } @@ -136,6 +133,6 @@ export class AttributeService { url += `&useStrictDataTypes=${useStrictDataTypes}`; } - return this.http.get(url, defaultHttpOptionsFromConfig(config)); + return this.http.get(url, defaultHttpOptionsFromConfig(config)); } } diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index 28a887fe9b..aa4fb51fd1 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -21,7 +21,14 @@ import { Observable, ReplaySubject, Subject } from 'rxjs'; import { EntityId } from '@shared/models/id/entity-id'; import { map } from 'rxjs/operators'; import { NgZone } from '@angular/core'; -import { AlarmData, AlarmDataQuery, EntityData, EntityDataQuery, EntityKey } from '@shared/models/query/query.models'; +import { + AlarmData, + AlarmDataQuery, + EntityData, + EntityDataQuery, + EntityKey, + TsValue +} from '@shared/models/query/query.models'; import { PageData } from '@shared/models/page/page-data'; export enum DataKeyType { @@ -82,12 +89,7 @@ export interface AttributeData { } export interface TimeseriesData { - [key: string]: Array; -} - -export interface TimeseriesKeyData { - value: any; - ts: number; + [key: string]: Array; } export enum DataSortOrder { From 6d90dc8420b20de82a47cbe77a7ecddf8e11cd0c Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 9 Nov 2020 18:09:24 +0200 Subject: [PATCH 307/463] AccessValidator improvements --- .../server/service/security/AccessValidator.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java index b4bc79e06e..8c7c629414 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java +++ b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java @@ -53,6 +53,7 @@ import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.entityview.EntityViewService; +import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; @@ -152,7 +153,11 @@ public class AccessValidator { new FutureCallback>() { @Override public void onSuccess(@Nullable DeferredResult result) { - onSuccess.accept(response, currentUser.getTenantId(), entityId); + try { + onSuccess.accept(response, currentUser.getTenantId(), entityId); + } catch (Exception e) { + onFailure(e); + } } @Override @@ -404,9 +409,9 @@ public class AccessValidator { public static void handleError(Throwable e, final DeferredResult response, HttpStatus defaultErrorStatus) { ResponseEntity responseEntity; - if (e != null && e instanceof ToErrorResponseEntity) { + if (e instanceof ToErrorResponseEntity) { responseEntity = ((ToErrorResponseEntity) e).toErrorResponseEntity(); - } else if (e != null && e instanceof IllegalArgumentException) { + } else if (e instanceof IllegalArgumentException || e instanceof IncorrectParameterException) { responseEntity = new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST); } else { responseEntity = new ResponseEntity<>(defaultErrorStatus); From f32aed7d5812005275710451a97ad1c0e7e53015 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 10 Nov 2020 12:06:16 +0200 Subject: [PATCH 308/463] UI: Refactoring, rename locale file --- .../profile/tenant-profile.component.ts | 4 +- ...enant-profile-configuration.component.html | 244 +++++++++--------- ...-tenant-profile-configuration.component.ts | 51 ++-- ...-pl_BR.json => locale.constant-pt_BR.json} | 0 4 files changed, 145 insertions(+), 154 deletions(-) rename ui-ngx/src/assets/locale/{locale.constant-pl_BR.json => locale.constant-pt_BR.json} (100%) diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts index 7cca3ae414..4e960e18a3 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts @@ -23,7 +23,7 @@ import { TenantProfile, TenantProfileData, TenantProfileType -} from '@app/shared/models/tenant.model'; +} from '@shared/models/tenant.model'; import { ActionNotificationShow } from '@app/core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; @@ -73,7 +73,7 @@ export class TenantProfileComponent extends EntityComponent { this.entityForm.patchValue({name: entity.name}); this.entityForm.patchValue({isolatedTbCore: entity.isolatedTbCore}); this.entityForm.patchValue({isolatedTbRuleEngine: entity.isolatedTbRuleEngine}); - this.entityForm.patchValue({profileData: !this.isAdd ? entity.profileData: { + this.entityForm.patchValue({profileData: !this.isAdd ? entity.profileData : { configuration: createTenantProfileConfiguration(TenantProfileType.DEFAULT) } as TenantProfileData}); this.entityForm.patchValue({description: entity.description}); diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html index cdb4febcc0..12258ffd5f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html @@ -15,127 +15,125 @@ limitations under the License. --> -
-
- - tenant-profile.maximum-devices - - - {{ 'tenant-profile.maximum-devices-required' | translate}} - - - {{ 'tenant-profile.maximum-devices-range' | translate}} - - - - tenant-profile.maximum-assets - - - {{ 'tenant-profile.maximum-assets-required' | translate}} - - - {{ 'tenant-profile.maximum-assets-range' | translate}} - - - - tenant-profile.max-transport-messages - - - {{ 'tenant-profile.max-transport-messages-range' | translate}} - - - {{ 'tenant-profile.max-transport-messages-required' | translate}} - - - - tenant-profile.max-transport-data-points - - - {{ 'tenant-profile.max-transport-data-points-required' | translate}} - - - {{ 'tenant-profile.max-transport-data-points-range' | translate}} - - - - tenant-profile.max-r-e-executions - - - {{ 'tenant-profile.max-r-e-executions-required' | translate}} - - - {{ 'tenant-profile.max-r-e-executions-range' | translate}} - - - - tenant-profile.max-j-s-executions - - - {{ 'tenant-profile.max-j-s-executions-required' | translate}} - - - {{ 'tenant-profile.max-j-s-executions-range' | translate}} - - - - tenant-profile.max-d-p-storage-days - - - {{ 'tenant-profile.max-d-p-storage-days-required' | translate}} - - - {{ 'tenant-profile.max-d-p-storage-days-range' | translate}} - - - - tenant-profile.max-rule-node-executions-per-message - - - {{ 'tenant-profile.max-rule-node-executions-per-message-required' | translate}} - - - {{ 'tenant-profile.max-rule-node-executions-per-message-range' | translate}} - - - - tenant-profile.transport-tenant-msg-rate-limit - - - - tenant-profile.transport-tenant-telemetry-msg-rate-limit - - - - tenant-profile.transport-tenant-telemetry-data-points-rate-limit - - - - tenant-profile.transport-device-msg-rate-limit - - - - tenant-profile.transport-device-telemetry-msg-rate-limit - - - - tenant-profile.transport-device-telemetry-data-points-rate-limit - - -
+
+ + tenant-profile.maximum-devices + + + {{ 'tenant-profile.maximum-devices-required' | translate}} + + + {{ 'tenant-profile.maximum-devices-range' | translate}} + + + + tenant-profile.maximum-assets + + + {{ 'tenant-profile.maximum-assets-required' | translate}} + + + {{ 'tenant-profile.maximum-assets-range' | translate}} + + + + tenant-profile.max-transport-messages + + + {{ 'tenant-profile.max-transport-messages-range' | translate}} + + + {{ 'tenant-profile.max-transport-messages-required' | translate}} + + + + tenant-profile.max-transport-data-points + + + {{ 'tenant-profile.max-transport-data-points-required' | translate}} + + + {{ 'tenant-profile.max-transport-data-points-range' | translate}} + + + + tenant-profile.max-r-e-executions + + + {{ 'tenant-profile.max-r-e-executions-required' | translate}} + + + {{ 'tenant-profile.max-r-e-executions-range' | translate}} + + + + tenant-profile.max-j-s-executions + + + {{ 'tenant-profile.max-j-s-executions-required' | translate}} + + + {{ 'tenant-profile.max-j-s-executions-range' | translate}} + + + + tenant-profile.max-d-p-storage-days + + + {{ 'tenant-profile.max-d-p-storage-days-required' | translate}} + + + {{ 'tenant-profile.max-d-p-storage-days-range' | translate}} + + + + tenant-profile.max-rule-node-executions-per-message + + + {{ 'tenant-profile.max-rule-node-executions-per-message-required' | translate}} + + + {{ 'tenant-profile.max-rule-node-executions-per-message-range' | translate}} + + + + tenant-profile.transport-tenant-msg-rate-limit + + + + tenant-profile.transport-tenant-telemetry-msg-rate-limit + + + + tenant-profile.transport-tenant-telemetry-data-points-rate-limit + + + + tenant-profile.transport-device-msg-rate-limit + + + + tenant-profile.transport-device-telemetry-msg-rate-limit + + + + tenant-profile.transport-device-telemetry-data-points-rate-limit + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts index f83b59c35a..1b54126fd5 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts @@ -19,11 +19,7 @@ import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Valida import { Store } from '@ngrx/store'; import { AppState } from '@app/core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; -import { - DefaultTenantProfileConfiguration, - TenantProfileConfiguration, - TenantProfileType -} from '@shared/models/tenant.model'; +import { DefaultTenantProfileConfiguration, TenantProfileConfiguration } from '@shared/models/tenant.model'; import { isDefinedAndNotNull } from '@core/utils'; @Component({ @@ -56,6 +52,25 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA constructor(private store: Store, private fb: FormBuilder) { + this.defaultTenantProfileConfigurationFormGroup = this.fb.group({ + maxDevices: [null, [Validators.required, Validators.min(0)]], + maxAssets: [null, [Validators.required, Validators.min(0)]], + transportTenantMsgRateLimit: [null, []], + transportTenantTelemetryMsgRateLimit: [null, []], + transportTenantTelemetryDataPointsRateLimit: [null, []], + transportDeviceMsgRateLimit: [null, []], + transportDeviceTelemetryMsgRateLimit: [null, []], + transportDeviceTelemetryDataPointsRateLimit: [null, []], + maxTransportMessages: [null, [Validators.required, Validators.min(0)]], + maxTransportDataPoints: [null, [Validators.required, Validators.min(0)]], + maxREExecutions: [null, [Validators.required, Validators.min(0)]], + maxJSExecutions: [null, [Validators.required, Validators.min(0)]], + maxDPStorageDays: [null, [Validators.required, Validators.min(0)]], + maxRuleNodeExecutionsPerMessage: [null, [Validators.required, Validators.min(0)]] + }); + this.defaultTenantProfileConfigurationFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); } registerOnChange(fn: any): void { @@ -66,27 +81,6 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA } ngOnInit() { - this.defaultTenantProfileConfigurationFormGroup = this.fb.group({ - configuration: this.fb.group({ - maxDevices: [null, [Validators.required, Validators.min(0)]], - maxAssets: [null, [Validators.required, Validators.min(0)]], - transportTenantMsgRateLimit: [null, []], - transportTenantTelemetryMsgRateLimit: [null, []], - transportTenantTelemetryDataPointsRateLimit: [null, []], - transportDeviceMsgRateLimit: [null, []], - transportDeviceTelemetryMsgRateLimit: [null, []], - transportDeviceTelemetryDataPointsRateLimit: [null, []], - maxTransportMessages: [null, [Validators.required, Validators.min(0)]], - maxTransportDataPoints: [null, [Validators.required, Validators.min(0)]], - maxREExecutions: [null, [Validators.required, Validators.min(0)]], - maxJSExecutions: [null, [Validators.required, Validators.min(0)]], - maxDPStorageDays: [null, [Validators.required, Validators.min(0)]], - maxRuleNodeExecutionsPerMessage: [null, [Validators.required, Validators.min(0)]] - }) - }); - this.defaultTenantProfileConfigurationFormGroup.valueChanges.subscribe(() => { - this.updateModel(); - }); } setDisabledState(isDisabled: boolean): void { @@ -100,15 +94,14 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA writeValue(value: DefaultTenantProfileConfiguration | null): void { if (isDefinedAndNotNull(value)) { - this.defaultTenantProfileConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); + this.defaultTenantProfileConfigurationFormGroup.patchValue(value, {emitEvent: false}); } } private updateModel() { let configuration: TenantProfileConfiguration = null; if (this.defaultTenantProfileConfigurationFormGroup.valid) { - configuration = this.defaultTenantProfileConfigurationFormGroup.getRawValue().configuration; - configuration.type = TenantProfileType.DEFAULT; + configuration = this.defaultTenantProfileConfigurationFormGroup.getRawValue(); } this.propagateChange(configuration); } diff --git a/ui-ngx/src/assets/locale/locale.constant-pl_BR.json b/ui-ngx/src/assets/locale/locale.constant-pt_BR.json similarity index 100% rename from ui-ngx/src/assets/locale/locale.constant-pl_BR.json rename to ui-ngx/src/assets/locale/locale.constant-pt_BR.json From ac866faab08151301363d29ae3751cef6baf9b48 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 10 Nov 2020 16:19:44 +0200 Subject: [PATCH 309/463] Improvements to the ApiUsageState --- .../DefaultTbApiUsageStateService.java | 26 ++-- .../service/apiusage/TenantApiUsageState.java | 135 ++++++++---------- .../install/SqlDatabaseUpgradeService.java | 24 ++++ .../server/common/data}/ApiFeature.java | 8 +- .../server/common/data/ApiUsageRecordKey.java | 34 ++++- .../server/common/data/ApiUsageState.java | 32 +++-- .../common/data/ApiUsageStateValue.java | 26 ++++ .../DefaultTenantProfileConfiguration.java | 6 + .../profile/TenantProfileConfiguration.java | 3 + .../queue/settings/TbQueueCoreSettings.java | 2 +- .../server/dao/model/ModelConstants.java | 8 +- .../dao/model/sql/ApiUsageStateEntity.java | 44 +++--- .../usagerecord/ApiUsageStateServiceImpl.java | 5 + .../resources/sql/schema-entities-hsql.sql | 8 +- .../main/resources/sql/schema-entities.sql | 8 +- .../service/BaseApiUsageStateServiceTest.java | 3 +- .../src/main/resources/tb-coap-transport.yml | 1 + .../src/main/resources/tb-http-transport.yml | 1 + .../src/main/resources/tb-mqtt-transport.yml | 1 + 19 files changed, 239 insertions(+), 136 deletions(-) rename {application/src/main/java/org/thingsboard/server/service/apiusage => common/data/src/main/java/org/thingsboard/server/common/data}/ApiFeature.java (83%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateValue.java diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 2c8879ba94..c7fda26dac 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -23,16 +23,18 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; -import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; @@ -57,8 +59,10 @@ import org.thingsboard.server.service.telemetry.InternalTelemetryService; import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; @@ -137,7 +141,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB())); TenantApiUsageState tenantState; List updatedEntries; - Map result = new HashMap<>(); + Map result; updateLock.lock(); try { tenantState = getOrFetchState(tenantId); @@ -148,17 +152,16 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { tenantState.setHour(newHourTs); } updatedEntries = new ArrayList<>(ApiUsageRecordKey.values().length); + Set apiFeatures = new HashSet<>(); for (UsageStatsKVProto kvProto : statsMsg.getValuesList()) { ApiUsageRecordKey recordKey = ApiUsageRecordKey.valueOf(kvProto.getKey()); long newValue = tenantState.add(recordKey, kvProto.getValue()); updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.getApiCountKey(), newValue))); long newHourlyValue = tenantState.addToHourly(recordKey, kvProto.getValue()); updatedEntries.add(new BasicTsKvEntry(hourTs, new LongDataEntry(recordKey.getApiCountKey() + HOURLY, newHourlyValue))); - Pair update = tenantState.checkStateUpdatedDueToThreshold(recordKey); - if (update != null) { - result.put(update.getFirst(), update.getSecond()); - } + apiFeatures.add(recordKey.getApiFeature()); } + result = tenantState.checkStateUpdatedDueToThreshold(apiFeatures); } finally { updateLock.unlock(); } @@ -196,7 +199,9 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { state = otherTenantStates.get(tenantId); if (state == null) { state = apiUsageStateService.findTenantApiUsageState(tenantId); - otherTenantStates.put(tenantId, state); + if (state != null) { + otherTenantStates.put(tenantId, state); + } } } finally { updateLock.unlock(); @@ -245,7 +250,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { TenantProfileData oldProfileData = state.getTenantProfileData(); state.setTenantProfileId(tenantProfile.getId()); state.setTenantProfileData(tenantProfile.getProfileData()); - Map result = state.checkStateUpdatedDueToThresholds(); + Map result = state.checkStateUpdatedDueToThresholds(); if (!result.isEmpty()) { persistAndNotify(state, result); } @@ -269,14 +274,15 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } - private void persistAndNotify(TenantApiUsageState state, Map result) { + private void persistAndNotify(TenantApiUsageState state, Map result) { log.info("[{}] Detected update of the API state: {}", state.getTenantId(), result); apiUsageStateService.update(state.getApiUsageState()); clusterService.onApiStateChange(state.getApiUsageState(), null); long ts = System.currentTimeMillis(); List stateTelemetry = new ArrayList<>(); - result.forEach(((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new BooleanDataEntry(apiFeature.getApiStateKey(), aState))))); + result.forEach(((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(apiFeature.getApiStateKey(), aState.name()))))); tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK); + //TODO: notify tenant admin via email! } private void checkStartOfNextCycle() { diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java index 8fcc1d46cb..a43afcd2c5 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -18,17 +18,21 @@ package org.thingsboard.server.service.apiusage; import lombok.Getter; import lombok.Setter; import org.springframework.data.util.Pair; +import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; -import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.msg.tools.SchedulerUtils; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; public class TenantApiUsageState { @@ -103,99 +107,80 @@ public class TenantApiUsageState { return tenantProfileData.getConfiguration().getProfileThreshold(key); } - public TenantId getTenantId() { - return apiUsageState.getTenantId(); - } - - public boolean isTransportEnabled() { - return apiUsageState.isTransportEnabled(); - } - - public boolean isDbStorageEnabled() { - return apiUsageState.isDbStorageEnabled(); - } - - public boolean isRuleEngineEnabled() { - return apiUsageState.isReExecEnabled(); - } - - public boolean isJsExecEnabled() { - return apiUsageState.isJsExecEnabled(); - } - - public void setTransportEnabled(boolean transportEnabled) { - apiUsageState.setTransportEnabled(transportEnabled); + public long getProfileWarnThreshold(ApiUsageRecordKey key) { + return tenantProfileData.getConfiguration().getWarnThreshold(key); } - public void setDbStorageEnabled(boolean dbStorageEnabled) { - apiUsageState.setDbStorageEnabled(dbStorageEnabled); - } - - public void setRuleEngineEnabled(boolean ruleEngineEnabled) { - apiUsageState.setReExecEnabled(ruleEngineEnabled); - } - - public void setJsExecEnabled(boolean jsExecEnabled) { - apiUsageState.setJsExecEnabled(jsExecEnabled); + public TenantId getTenantId() { + return apiUsageState.getTenantId(); } - public boolean isFeatureEnabled(ApiUsageRecordKey recordKey) { - switch (recordKey) { - case TRANSPORT_MSG_COUNT: - case TRANSPORT_DP_COUNT: - return isTransportEnabled(); - case RE_EXEC_COUNT: - return isRuleEngineEnabled(); - case STORAGE_DP_COUNT: - return isDbStorageEnabled(); - case JS_EXEC_COUNT: - return isJsExecEnabled(); + public ApiUsageStateValue getFeatureValue(ApiFeature feature) { + switch (feature) { + case TRANSPORT: + return apiUsageState.getTransportState(); + case RE: + return apiUsageState.getReExecState(); + case DB: + return apiUsageState.getDbStorageState(); + case JS: + return apiUsageState.getJsExecState(); default: - return true; + return ApiUsageStateValue.ENABLED; } } - public ApiFeature setFeatureValue(ApiUsageRecordKey recordKey, boolean value) { - ApiFeature feature = null; - boolean currentValue = isFeatureEnabled(recordKey); - switch (recordKey) { - case TRANSPORT_MSG_COUNT: - case TRANSPORT_DP_COUNT: - feature = ApiFeature.TRANSPORT; - setTransportEnabled(value); + public boolean setFeatureValue(ApiFeature feature, ApiUsageStateValue value) { + ApiUsageStateValue currentValue = getFeatureValue(feature); + switch (feature) { + case TRANSPORT: + apiUsageState.setTransportState(value); break; - case RE_EXEC_COUNT: - feature = ApiFeature.RE; - setRuleEngineEnabled(value); + case RE: + apiUsageState.setReExecState(value); break; - case STORAGE_DP_COUNT: - feature = ApiFeature.DB; - setDbStorageEnabled(value); + case DB: + apiUsageState.setDbStorageState(value); break; - case JS_EXEC_COUNT: - feature = ApiFeature.JS; - setJsExecEnabled(value); + case JS: + apiUsageState.setJsExecState(value); break; } - return currentValue == value ? null : feature; + return !currentValue.equals(value); } - public Map checkStateUpdatedDueToThresholds() { - Map result = new HashMap<>(); - for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { - Pair featureUpdate = checkStateUpdatedDueToThreshold(key); - if (featureUpdate != null) { - result.put(featureUpdate.getFirst(), featureUpdate.getSecond()); + public Map checkStateUpdatedDueToThresholds() { + return checkStateUpdatedDueToThreshold(new HashSet<>(Arrays.asList(ApiFeature.values()))); + } + + public Map checkStateUpdatedDueToThreshold(Set features) { + Map result = new HashMap<>(); + for (ApiFeature feature : features) { + Pair tmp = checkStateUpdatedDueToThreshold(feature); + if (tmp != null) { + result.put(tmp.getFirst(), tmp.getSecond()); } } return result; } - public Pair checkStateUpdatedDueToThreshold(ApiUsageRecordKey recordKey) { - long value = get(recordKey); - long threshold = getProfileThreshold(recordKey); - boolean featureValue = threshold == 0 || value < threshold; - ApiFeature feature = setFeatureValue(recordKey, featureValue); - return feature != null ? Pair.of(feature, featureValue) : null; + public Pair checkStateUpdatedDueToThreshold(ApiFeature feature) { + ApiUsageStateValue featureValue = ApiUsageStateValue.ENABLED; + for (ApiUsageRecordKey recordKey : ApiUsageRecordKey.getKeys(feature)) { + long value = get(recordKey); + long threshold = getProfileThreshold(recordKey); + long warnThreshold = getProfileWarnThreshold(recordKey); + ApiUsageStateValue tmpValue; + if (threshold == 0 || value < warnThreshold) { + tmpValue = ApiUsageStateValue.ENABLED; + } else if (value < threshold) { + tmpValue = ApiUsageStateValue.WARNING; + } else { + tmpValue = ApiUsageStateValue.DISABLED; + } + featureValue = ApiUsageStateValue.toMoreRestricted(featureValue, tmpValue); + } + return setFeatureValue(feature, featureValue) ? Pair.of(feature, featureValue) : null; } + } diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index d6068b9616..19727684a3 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -28,6 +28,7 @@ import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.service.install.sql.SqlDbHelper; import java.nio.charset.Charset; @@ -96,6 +97,9 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService @Autowired private DeviceProfileService deviceProfileService; + @Autowired + private ApiUsageStateService apiUsageStateService; + @Override public void upgradeDatabase(String fromVersion) throws Exception { @@ -352,6 +356,22 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService } catch (Exception e) { } + try { + conn.createStatement().execute("CREATE TABLE IF NOT EXISTS api_usage_state (" + + " id uuid NOT NULL CONSTRAINT usage_record_pkey PRIMARY KEY," + + " created_time bigint NOT NULL," + + " tenant_id uuid," + + " entity_type varchar(32)," + + " entity_id uuid," + + " transport varchar(32)," + + " db_storage varchar(32)," + + " re_exec varchar(32)," + + " js_exec varchar(32)," + + " CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id)\n" + + ");"); + } catch (Exception e) { + } + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.1", "schema_update_before.sql"); loadSql(schemaUpdateFile, conn); @@ -367,6 +387,10 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService do { pageData = tenantService.findTenants(pageLink); for (Tenant tenant : pageData.getData()) { + try { + apiUsageStateService.createDefaultApiUsageState(tenant.getId()); + } catch (Exception e) { + } List deviceTypes = deviceService.findDeviceTypesByTenantId(tenant.getId()).get(); try { deviceProfileService.createDefaultDeviceProfile(tenant.getId()); diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java similarity index 83% rename from application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java rename to common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java index f2dfab54d4..e12cb5fb14 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java @@ -13,12 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.apiusage; +package org.thingsboard.server.common.data; import lombok.Getter; public enum ApiFeature { - TRANSPORT("transportApiState"), DB("dbApiState"), RE("ruleEngineApiState"), JS("jsExecutionApiState"); + TRANSPORT("transportApiState"), + DB("dbApiState"), + RE("ruleEngineApiState"), + JS("jsExecutionApiState"); @Getter private final String apiStateKey; @@ -26,4 +29,5 @@ public enum ApiFeature { ApiFeature(String apiStateKey) { this.apiStateKey = apiStateKey; } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java index b1ff230e71..dc8556a755 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java @@ -19,20 +19,42 @@ import lombok.Getter; public enum ApiUsageRecordKey { - TRANSPORT_MSG_COUNT("transportMsgCount", "transportMsgLimit"), - TRANSPORT_DP_COUNT("transportDataPointsCount", "transportDataPointsLimit"), - STORAGE_DP_COUNT("storageDataPointsCount", "storageDataPointsLimit"), - RE_EXEC_COUNT("ruleEngineExecutionCount", "ruleEngineExecutionLimit"), - JS_EXEC_COUNT("jsExecutionCount", "jsExecutionLimit"); + TRANSPORT_MSG_COUNT(ApiFeature.TRANSPORT, "transportMsgCount", "transportMsgLimit"), + TRANSPORT_DP_COUNT(ApiFeature.TRANSPORT, "transportDataPointsCount", "transportDataPointsLimit"), + STORAGE_DP_COUNT(ApiFeature.DB, "storageDataPointsCount", "storageDataPointsLimit"), + RE_EXEC_COUNT(ApiFeature.RE, "ruleEngineExecutionCount", "ruleEngineExecutionLimit"), + JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit"); + private static final ApiUsageRecordKey[] JS_RECORD_KEYS = {JS_EXEC_COUNT}; + private static final ApiUsageRecordKey[] RE_RECORD_KEYS = {RE_EXEC_COUNT}; + private static final ApiUsageRecordKey[] DB_RECORD_KEYS = {STORAGE_DP_COUNT}; + private static final ApiUsageRecordKey[] TRANSPORT_RECORD_KEYS = {TRANSPORT_MSG_COUNT, TRANSPORT_DP_COUNT}; + @Getter + private final ApiFeature apiFeature; @Getter private final String apiCountKey; @Getter private final String apiLimitKey; - ApiUsageRecordKey(String apiCountKey, String apiLimitKey) { + ApiUsageRecordKey(ApiFeature apiFeature, String apiCountKey, String apiLimitKey) { + this.apiFeature = apiFeature; this.apiCountKey = apiCountKey; this.apiLimitKey = apiLimitKey; } + public static ApiUsageRecordKey[] getKeys(ApiFeature feature) { + switch (feature) { + case TRANSPORT: + return TRANSPORT_RECORD_KEYS; + case DB: + return DB_RECORD_KEYS; + case RE: + return RE_RECORD_KEYS; + case JS: + return JS_RECORD_KEYS; + default: + return new ApiUsageRecordKey[]{}; + } + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java index 5144861790..52797baa3b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java @@ -37,16 +37,16 @@ public class ApiUsageState extends BaseData implements HasTenan private EntityId entityId; @Getter @Setter - private boolean transportEnabled = true; + private ApiUsageStateValue transportState; @Getter @Setter - private boolean dbStorageEnabled = true; + private ApiUsageStateValue dbStorageState; @Getter @Setter - private boolean reExecEnabled = true; + private ApiUsageStateValue reExecState; @Getter @Setter - private boolean jsExecEnabled = true; + private ApiUsageStateValue jsExecState; public ApiUsageState() { super(); @@ -60,9 +60,25 @@ public class ApiUsageState extends BaseData implements HasTenan super(ur); this.tenantId = ur.getTenantId(); this.entityId = ur.getEntityId(); - this.transportEnabled = ur.isTransportEnabled(); - this.dbStorageEnabled = ur.isDbStorageEnabled(); - this.reExecEnabled = ur.isReExecEnabled(); - this.jsExecEnabled = ur.isJsExecEnabled(); + this.transportState = ur.getTransportState(); + this.dbStorageState = ur.getDbStorageState(); + this.reExecState = ur.getReExecState(); + this.jsExecState = ur.getJsExecState(); + } + + public boolean isTransportEnabled() { + return !ApiUsageStateValue.DISABLED.equals(transportState); + } + + public boolean isReExecEnabled() { + return !ApiUsageStateValue.DISABLED.equals(reExecState); + } + + public boolean isDbStorageEnabled() { + return !ApiUsageStateValue.DISABLED.equals(dbStorageState); + } + + public boolean isJsExecEnabled() { + return !ApiUsageStateValue.DISABLED.equals(jsExecState); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateValue.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateValue.java new file mode 100644 index 0000000000..ae14ff80bf --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateValue.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +public enum ApiUsageStateValue { + + ENABLED, WARNING, DISABLED; + + + public static ApiUsageStateValue toMoreRestricted(ApiUsageStateValue a, ApiUsageStateValue b) { + return a.ordinal() > b.ordinal() ? a : b; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java index 1804731c04..250f9c75a5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java @@ -39,6 +39,8 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura private long maxDPStorageDays; private int maxRuleNodeExecutionsPerMessage; + private double warnThreshold; + @Override public long getProfileThreshold(ApiUsageRecordKey key) { switch (key) { @@ -56,6 +58,10 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura return 0L; } + @Override + public long getWarnThreshold(ApiUsageRecordKey key) { + return (long) (getProfileThreshold(key) * (warnThreshold > 0.0 ? warnThreshold : 0.8)); + } @Override public TenantProfileType getType() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java index c4c6d7563a..ea4a0e1fbe 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java @@ -37,6 +37,9 @@ public interface TenantProfileConfiguration { @JsonIgnore long getProfileThreshold(ApiUsageRecordKey key); + @JsonIgnore + long getWarnThreshold(ApiUsageRecordKey key); + @JsonIgnore int getMaxRuleNodeExecsPerMessage(); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java index d406139dbb..d78c6d1bad 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java @@ -26,7 +26,7 @@ public class TbQueueCoreSettings { @Value("${queue.core.topic}") private String topic; - @Value("${queue.core.usage-stats-topic}") + @Value("${queue.core.usage-stats-topic:tb_usage_stats}") private String usageStatsTopic; @Value("${queue.core.partitions}") diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 8e1e62b90f..e863de1edc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -446,10 +446,10 @@ public class ModelConstants { public static final String API_USAGE_STATE_TENANT_ID_COLUMN = TENANT_ID_PROPERTY; public static final String API_USAGE_STATE_ENTITY_TYPE_COLUMN = ENTITY_TYPE_COLUMN; public static final String API_USAGE_STATE_ENTITY_ID_COLUMN = ENTITY_ID_COLUMN; - public static final String API_USAGE_STATE_TRANSPORT_ENABLED_COLUMN = "transport_enabled"; - public static final String API_USAGE_STATE_DB_STORAGE_ENABLED_COLUMN = "db_storage_enabled"; - public static final String API_USAGE_STATE_RE_EXEC_ENABLED_COLUMN = "re_exec_enabled"; - public static final String API_USAGE_STATE_JS_EXEC_ENABLED_COLUMN = "js_exec_enabled"; + public static final String API_USAGE_STATE_TRANSPORT_COLUMN = "transport"; + public static final String API_USAGE_STATE_DB_STORAGE_COLUMN = "db_storage"; + public static final String API_USAGE_STATE_RE_EXEC_COLUMN = "re_exec"; + public static final String API_USAGE_STATE_JS_EXEC_COLUMN = "js_exec"; /** * Cassandra attributes and timeseries constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java index 262252517a..bc4aed6398 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java @@ -17,10 +17,9 @@ package org.thingsboard.server.dao.model.sql; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.ApiUsageStateId; @@ -31,6 +30,8 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; import javax.persistence.Table; import java.util.UUID; @@ -46,21 +47,22 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements @Column(name = ModelConstants.API_USAGE_STATE_TENANT_ID_COLUMN) private UUID tenantId; - @Column(name = ModelConstants.API_USAGE_STATE_ENTITY_TYPE_COLUMN) private String entityType; - @Column(name = ModelConstants.API_USAGE_STATE_ENTITY_ID_COLUMN) private UUID entityId; - - @Column(name = ModelConstants.API_USAGE_STATE_TRANSPORT_ENABLED_COLUMN) - private boolean transportEnabled = true; - @Column(name = ModelConstants.API_USAGE_STATE_DB_STORAGE_ENABLED_COLUMN) - private boolean dbStorageEnabled = true; - @Column(name = ModelConstants.API_USAGE_STATE_RE_EXEC_ENABLED_COLUMN) - private boolean reExecEnabled = true; - @Column(name = ModelConstants.API_USAGE_STATE_JS_EXEC_ENABLED_COLUMN) - private boolean jsExecEnabled = true; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.API_USAGE_STATE_TRANSPORT_COLUMN) + private ApiUsageStateValue transportState = ApiUsageStateValue.ENABLED; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.API_USAGE_STATE_DB_STORAGE_COLUMN) + private ApiUsageStateValue dbStorageState = ApiUsageStateValue.ENABLED; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.API_USAGE_STATE_RE_EXEC_COLUMN) + private ApiUsageStateValue reExecState = ApiUsageStateValue.ENABLED; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.API_USAGE_STATE_JS_EXEC_COLUMN) + private ApiUsageStateValue jsExecState = ApiUsageStateValue.ENABLED; public ApiUsageStateEntity() { } @@ -77,10 +79,10 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements this.entityType = ur.getEntityId().getEntityType().name(); this.entityId = ur.getEntityId().getId(); } - this.transportEnabled = ur.isTransportEnabled(); - this.dbStorageEnabled = ur.isDbStorageEnabled(); - this.reExecEnabled = ur.isReExecEnabled(); - this.jsExecEnabled = ur.isJsExecEnabled(); + this.transportState = ur.getTransportState(); + this.dbStorageState = ur.getDbStorageState(); + this.reExecState = ur.getReExecState(); + this.jsExecState = ur.getJsExecState(); } @Override @@ -93,10 +95,10 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements if (entityId != null) { ur.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId)); } - ur.setTransportEnabled(transportEnabled); - ur.setDbStorageEnabled(dbStorageEnabled); - ur.setReExecEnabled(reExecEnabled); - ur.setJsExecEnabled(jsExecEnabled); + ur.setTransportState(transportState); + ur.setDbStorageState(dbStorageState); + ur.setReExecState(reExecState); + ur.setJsExecState(jsExecState); return ur; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index e1b494e2e8..ed02bf7726 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; @@ -71,6 +72,10 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A ApiUsageState apiUsageState = new ApiUsageState(); apiUsageState.setTenantId(tenantId); apiUsageState.setEntityId(tenantId); + apiUsageState.setTransportState(ApiUsageStateValue.ENABLED); + apiUsageState.setReExecState(ApiUsageStateValue.ENABLED); + apiUsageState.setJsExecState(ApiUsageStateValue.ENABLED); + apiUsageState.setDbStorageState(ApiUsageStateValue.ENABLED); apiUsageStateValidator.validate(apiUsageState, ApiUsageState::getTenantId); ApiUsageState saved = apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index b7c028d5d7..0e8f8a7791 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -412,9 +412,9 @@ CREATE TABLE IF NOT EXISTS api_usage_state ( tenant_id uuid, entity_type varchar(32), entity_id uuid, - transport_enabled boolean, - db_storage_enabled boolean, - re_exec_enabled boolean, - js_exec_enabled boolean, + transport varchar(32), + db_storage varchar(32), + re_exec varchar(32), + js_exec varchar(32), CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) ); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 6fffea8fe9..db0362a877 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -438,10 +438,10 @@ CREATE TABLE IF NOT EXISTS api_usage_state ( tenant_id uuid, entity_type varchar(32), entity_id uuid, - transport_enabled boolean, - db_storage_enabled boolean, - re_exec_enabled boolean, - js_exec_enabled boolean, + transport varchar(32), + db_storage varchar(32), + re_exec varchar(32), + js_exec varchar(32), CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) ); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java index a33e190a03..024d3dd5c3 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java @@ -19,6 +19,7 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.id.TenantId; @@ -53,7 +54,7 @@ public abstract class BaseApiUsageStateServiceTest extends AbstractServiceTest { ApiUsageState apiUsageState = apiUsageStateService.findTenantApiUsageState(tenantId); Assert.assertNotNull(apiUsageState); Assert.assertTrue(apiUsageState.isTransportEnabled()); - apiUsageState.setTransportEnabled(false); + apiUsageState.setTransportState(ApiUsageStateValue.DISABLED); apiUsageState = apiUsageStateService.update(apiUsageState); Assert.assertNotNull(apiUsageState); apiUsageState = apiUsageStateService.findTenantApiUsageState(tenantId); diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index 229196d325..3a1d0e7414 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -145,6 +145,7 @@ queue: poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" partitions: "${TB_QUEUE_CORE_PARTITIONS:10}" pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:60000}" + usage-stats-topic: "${TB_QUEUE_US_TOPIC:tb_usage_stats}" stats: enabled: "${TB_QUEUE_CORE_STATS_ENABLED:false}" print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:10000}" diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index 6aaa42ca18..ef152bb47d 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -138,6 +138,7 @@ queue: poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" partitions: "${TB_QUEUE_CORE_PARTITIONS:10}" pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:60000}" + usage-stats-topic: "${TB_QUEUE_US_TOPIC:tb_usage_stats}" stats: enabled: "${TB_QUEUE_CORE_STATS_ENABLED:false}" print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:10000}" diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index f9341dfb86..fc8d970638 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -167,6 +167,7 @@ queue: poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" partitions: "${TB_QUEUE_CORE_PARTITIONS:10}" pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:60000}" + usage-stats-topic: "${TB_QUEUE_US_TOPIC:tb_usage_stats}" stats: enabled: "${TB_QUEUE_CORE_STATS_ENABLED:false}" print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:10000}" From b1c4332a0376e1da9d02ef2223b77dac64375c58 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 10 Nov 2020 17:08:16 +0200 Subject: [PATCH 310/463] UI: Improvement logo container size --- ui-ngx/src/app/modules/home/home.component.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui-ngx/src/app/modules/home/home.component.scss b/ui-ngx/src/app/modules/home/home.component.scss index f77e85fd87..6cb0adea27 100644 --- a/ui-ngx/src/app/modules/home/home.component.scss +++ b/ui-ngx/src/app/modules/home/home.component.scss @@ -37,6 +37,7 @@ .tb-nav-header-toolbar { min-height: 64px; height: inherit; + padding: 0; z-index: 2; flex-shrink: 0; white-space: nowrap; @@ -45,7 +46,9 @@ height: 64px; .tb-logo-title { width: auto; + max-width: 100%; height: 36px; + max-height: 100%; margin: auto; } } From c3623be872fac607b13f33860b1637f67c7ea0c7 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 10 Nov 2020 18:37:55 +0200 Subject: [PATCH 311/463] Fix for black box tests --- .../server/service/apiusage/DefaultTbApiUsageStateService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index c7fda26dac..b016cced95 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -71,7 +71,6 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @Slf4j -@TbCoreComponent @Service public class DefaultTbApiUsageStateService implements TbApiUsageStateService { From a535b320f13f7010328b45fb59893c0d7a22e04f Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 11 Nov 2020 09:18:48 +0200 Subject: [PATCH 312/463] Change ordering of the startup sequence --- .../server/actors/service/DefaultActorService.java | 2 ++ .../apiusage/DefaultTbApiUsageStateService.java | 10 ++++++++-- .../service/queue/DefaultTbCoreConsumerService.java | 2 ++ .../queue/processing/AbstractConsumerService.java | 2 ++ .../service/transport/TbCoreTransportApiService.java | 2 ++ .../server/queue/discovery/DummyDiscoveryService.java | 2 ++ .../server/queue/discovery/ZkDiscoveryService.java | 2 ++ 7 files changed, 20 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java index 2d3775c9f5..7be4dcb8be 100644 --- a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java +++ b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java @@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.Order; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.actors.ActorSystemContext; @@ -113,6 +114,7 @@ public class DefaultActorService implements ActorService { } @EventListener(ApplicationReadyEvent.class) + @Order(value = 2) public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { log.info("Received application ready event. Sending application init message to actor system"); appActor.tellWithHighPriority(new AppInitMsg()); diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index b016cced95..0dcef2c88d 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -20,7 +20,10 @@ import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.annotation.Lazy; +import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.Order; import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.ApiFeature; @@ -129,8 +132,8 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { public void init() { if (enabled) { log.info("Starting api usage service."); - initStatesFromDataBase(); scheduler.scheduleAtFixedRate(this::checkStartOfNextCycle, nextCycleCheckInterval, nextCycleCheckInterval, TimeUnit.MILLISECONDS); + log.info("Started api usage service."); } } @@ -340,12 +343,15 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { private void initStatesFromDataBase() { try { + log.info("Initializing tenant states."); PageDataIterable tenantIterator = new PageDataIterable<>(tenantService::findTenants, 1024); for (Tenant tenant : tenantIterator) { if (!myTenantStates.containsKey(tenant.getId()) && partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), tenant.getId()).isMyPartition()) { + log.debug("[{}] Initializing tenant state.", tenant.getId()); updateLock.lock(); try { updateTenantState(getOrFetchState(tenant.getId()), tenantProfileCache.get(tenant.getTenantProfileId())); + log.debug("[{}] Initialized tenant state.", tenant.getId()); } catch (Exception e) { log.warn("[{}] Failed to initialize tenant API state", tenant.getId(), e); } finally { @@ -353,7 +359,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } } - log.info("Api usage service started."); + log.info("Initialized tenant states."); } catch (Exception e) { log.warn("Unknown failure", e); } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index aaafa52c35..ac7aea9f44 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.Order; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; @@ -140,6 +141,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService Date: Mon, 9 Nov 2020 17:49:05 +0200 Subject: [PATCH 313/463] added validation with maxDevices and maxAssets to deviceService and assetService --- .../server/actors/ActorSystemContext.java | 2 +- .../thingsboard/server/actors/app/AppActor.java | 3 +-- .../server/controller/BaseController.java | 3 +-- .../apiusage/DefaultTbApiUsageStateService.java | 2 +- .../queue/DefaultTbCoreConsumerService.java | 2 +- .../DefaultTbRuleEngineConsumerService.java | 2 +- .../queue/DefaultTenantRoutingInfoService.java | 3 +-- .../processing/AbstractConsumerService.java | 2 +- .../transport/DefaultTransportApiService.java | 2 +- .../server/dao/tenant}/TbTenantProfileCache.java | 2 +- .../thingsboard/server/dao/asset/AssetDao.java | 2 ++ .../server/dao/asset/BaseAssetService.java | 16 ++++++++++++++++ .../thingsboard/server/dao/device/DeviceDao.java | 2 ++ .../server/dao/device/DeviceServiceImpl.java | 16 ++++++++++++++++ .../server/dao/sql/asset/AssetRepository.java | 1 + .../server/dao/sql/asset/JpaAssetDao.java | 6 ++++++ .../server/dao/sql/device/DeviceRepository.java | 2 ++ .../server/dao/sql/device/JpaDeviceDao.java | 5 +++++ .../dao/tenant}/DefaultTbTenantProfileCache.java | 9 ++------- 19 files changed, 62 insertions(+), 20 deletions(-) rename {application/src/main/java/org/thingsboard/server/service/profile => common/dao-api/src/main/java/org/thingsboard/server/dao/tenant}/TbTenantProfileCache.java (95%) rename {application/src/main/java/org/thingsboard/server/service/profile => dao/src/main/java/org/thingsboard/server/dao/tenant}/DefaultTbTenantProfileCache.java (90%) diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 5a0e291ac2..52dbb38c9c 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -73,7 +73,7 @@ import org.thingsboard.server.service.executors.ExternalCallExecutorService; import org.thingsboard.server.service.executors.SharedEventLoopGroupService; import org.thingsboard.server.service.mail.MailExecutorService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.service.profile.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java index fea5558540..ba8654854c 100644 --- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java @@ -30,7 +30,6 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.MsgType; @@ -41,7 +40,7 @@ import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg; import org.thingsboard.server.common.msg.queue.RuleEngineException; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.dao.tenant.TenantService; -import org.thingsboard.server.service.profile.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; import java.util.HashSet; diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index b8fe956217..869cbea127 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -35,7 +35,6 @@ import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; @@ -94,7 +93,7 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.service.profile.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.AccessControlService; diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 0dcef2c88d..79f05fa897 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -55,7 +55,7 @@ import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.profile.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.telemetry.InternalTelemetryService; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index ac7aea9f44..f56e08b10f 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -55,7 +55,7 @@ import org.thingsboard.server.queue.provider.TbCoreQueueFactory; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.service.profile.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.queue.processing.AbstractConsumerService; import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java index ffb431ca37..aa3be47ef9 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java @@ -45,7 +45,7 @@ import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; import org.thingsboard.server.queue.util.TbRuleEngineComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.service.profile.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.queue.processing.AbstractConsumerService; import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingDecision; import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingResult; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java index 4d5219fbfe..2bcf7e7e88 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java @@ -21,11 +21,10 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.queue.discovery.TenantRoutingInfo; import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; -import org.thingsboard.server.service.profile.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; @Slf4j @Service diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java index fd95f66346..0dfc9c5742 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java @@ -38,7 +38,7 @@ import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.service.profile.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbPackCallback; import org.thingsboard.server.service.queue.TbPackProcessingContext; diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index ab566fb147..3e9e413ceb 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -71,7 +71,7 @@ import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.service.profile.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.state.DeviceStateService; diff --git a/application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TbTenantProfileCache.java similarity index 95% rename from application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java rename to common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TbTenantProfileCache.java index de69b5f51c..735e23e73a 100644 --- a/application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TbTenantProfileCache.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.profile; +package org.thingsboard.server.dao.tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java index 00154ec602..1366df3ac2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java @@ -166,4 +166,6 @@ public interface AssetDao extends Dao { */ ListenableFuture> findTenantAssetTypesAsync(UUID tenantId); + Long countAssetsByTenantId(TenantId tenantId); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java index f0683ae5ae..923baa1df4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java @@ -26,6 +26,7 @@ import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Customer; @@ -44,12 +45,14 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantDao; import java.util.ArrayList; @@ -90,6 +93,10 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ @Autowired private CacheManager cacheManager; + @Autowired + @Lazy + private TbTenantProfileCache tenantProfileCache; + @Override public AssetInfo findAssetInfoById(TenantId tenantId, AssetId assetId) { log.trace("Executing findAssetInfoById [{}]", assetId); @@ -320,6 +327,15 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ @Override protected void validateCreate(TenantId tenantId, Asset asset) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + long maxAssets = profileConfiguration.getMaxAssets(); + if (maxAssets > 0) { + long currentAssetsCount = assetDao.countAssetsByTenantId(tenantId); + if (maxAssets >= currentAssetsCount) { + throw new DataValidationException("Can't create assets more then " + maxAssets); + } + } } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java index 54ca245ee8..2f321ff499 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java @@ -203,6 +203,8 @@ public interface DeviceDao extends Dao { */ ListenableFuture findDeviceByTenantIdAndIdAsync(TenantId tenantId, UUID id); + Long countDevicesByTenantId(TenantId tenantId); + Long countDevicesByDeviceProfileId(TenantId tenantId, UUID deviceProfileId); /** diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index b0290e8eb1..9d3e161eb4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -27,6 +27,7 @@ import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; @@ -57,6 +58,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionRequest; @@ -67,6 +69,7 @@ import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.util.mapping.JacksonUtil; @@ -120,6 +123,10 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe @Autowired private EventService eventService; + @Autowired + @Lazy + private TbTenantProfileCache tenantProfileCache; + @Override public DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId) { log.trace("Executing findDeviceInfoById [{}]", deviceId); @@ -520,6 +527,15 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe @Override protected void validateCreate(TenantId tenantId, Device device) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + long maxDevices = profileConfiguration.getMaxDevices(); + if (maxDevices > 0) { + long currentDevicesCount = deviceDao.countDevicesByTenantId(tenantId); + if (maxDevices >= currentDevicesCount) { + throw new DataValidationException("Can't create devices more then " + maxDevices); + } + } } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java index 5e9d942720..a6e016c418 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java @@ -122,4 +122,5 @@ public interface AssetRepository extends PagingAndSortingRepository findTenantAssetTypes(@Param("tenantId") UUID tenantId); + Long countByTenantId(UUID tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java index 1e67802873..698183ea0a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java @@ -176,4 +176,10 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao im } return list; } + + @Override + public Long countAssetsByTenantId(TenantId tenantId) { + return assetRepository.countByTenantId(tenantId.getId()); + + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java index b6ead2cdc3..9f3cc5b5a7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java @@ -168,4 +168,6 @@ public interface DeviceRepository extends PagingAndSortingRepository return deviceRepository.countByDeviceProfileId(deviceProfileId); } + @Override + public Long countDevicesByTenantId(TenantId tenantId) { + return deviceRepository.countByTenantId(tenantId.getId()); + } + private List convertTenantDeviceTypesToDto(UUID tenantId, List types) { List list = Collections.emptyList(); if (types != null && !types.isEmpty()) { diff --git a/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/DefaultTbTenantProfileCache.java similarity index 90% rename from application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java rename to dao/src/main/java/org/thingsboard/server/dao/tenant/DefaultTbTenantProfileCache.java index 42c09c104c..adf05e0f74 100644 --- a/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/DefaultTbTenantProfileCache.java @@ -13,20 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.profile; +package org.thingsboard.server.dao.tenant; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; -import org.thingsboard.server.dao.device.DeviceProfileService; -import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; From 92ba5907b0e8fb335e6562d636a3bcb627099f92 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 3 Nov 2020 16:51:41 +0200 Subject: [PATCH 314/463] Added validation for deviceName/attribute key contains only spaces situation --- .../thingsboard/server/controller/TelemetryController.java | 5 +++++ .../org/thingsboard/server/dao/device/DeviceServiceImpl.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index af4eb0f08f..e8c4756a6f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -393,6 +393,11 @@ public class TelemetryController extends BaseController { if (attributes.isEmpty()) { return getImmediateDeferredResult("No attributes data found in request body!", HttpStatus.BAD_REQUEST); } + for (AttributeKvEntry attributeKvEntry: attributes) { + if (attributeKvEntry.getKey().isEmpty() || attributeKvEntry.getKey().trim().length() == 0) { + return getImmediateDeferredResult("Key cannot be empty or contains only spaces", HttpStatus.BAD_REQUEST); + } + } SecurityUser user = getCurrentUser(); return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> { tsSubService.saveAndNotify(tenantId, entityId, scope, attributes, new FutureCallback() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index cb6d74cf10..8d29b9c447 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -388,7 +388,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe if (StringUtils.isEmpty(device.getType())) { throw new DataValidationException("Device type should be specified!"); } - if (StringUtils.isEmpty(device.getName())) { + if (StringUtils.isEmpty(device.getName()) || device.getName().trim().length() == 0) { throw new DataValidationException("Device name should be specified!"); } if (device.getTenantId() == null) { From 1daf5a44b5683a35b065f568e28498e414f95111 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 3 Nov 2020 17:33:57 +0200 Subject: [PATCH 315/463] alarm state improvements (added details to alarm from device profile details pattern) --- .../rule/engine/profile/AlarmState.java | 53 ++++++++++++++++--- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 6c771f0c86..3d29da6bd7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.profile; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.action.TbAlarmResult; @@ -29,6 +30,7 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.query.EntityKeyType; +import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceQueue; @@ -74,7 +76,7 @@ class AlarmState { public boolean createOrClearAlarms(TbContext ctx, T data, SnapshotUpdate update, BiFunction evalFunction) { boolean stateUpdate = false; - AlarmSeverity resultSeverity = null; + AlarmRuleState resultState = null; log.debug("[{}] processing update: {}", alarmDefinition.getId(), data); for (AlarmRuleState state : createRulesSortedBySeverityDesc) { if (!validateUpdate(update, state)) { @@ -84,15 +86,15 @@ class AlarmState { AlarmEvalResult evalResult = evalFunction.apply(state, data); stateUpdate |= state.checkUpdate(); if (AlarmEvalResult.TRUE.equals(evalResult)) { - resultSeverity = state.getSeverity(); + resultState = state; break; } else if (AlarmEvalResult.FALSE.equals(evalResult)) { state.clear(); stateUpdate |= state.checkUpdate(); } } - if (resultSeverity != null) { - TbAlarmResult result = calculateAlarmResult(ctx, resultSeverity); + if (resultState != null) { + TbAlarmResult result = calculateAlarmResult(ctx, resultState, data); if (result != null) { pushMsg(ctx, result); } @@ -187,7 +189,8 @@ class AlarmState { } } - private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmSeverity severity) { + private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmRuleState ruleState, T data) { + AlarmSeverity severity = ruleState.getSeverity(); if (currentAlarm != null) { // TODO: In some extremely rare cases, we might miss the event of alarm clear (If one use in-mem queue and restarted the server) or (if one manipulated the rule chain). // Maybe we should fetch alarm every time? @@ -213,7 +216,7 @@ class AlarmState { currentAlarm.setSeverity(severity); currentAlarm.setStartTs(System.currentTimeMillis()); currentAlarm.setEndTs(currentAlarm.getStartTs()); - currentAlarm.setDetails(JacksonUtil.OBJECT_MAPPER.createObjectNode()); + currentAlarm.setDetails(createDetails(ruleState, (DataSnapshot) data)); currentAlarm.setOriginator(originator); currentAlarm.setTenantId(ctx.getTenantId()); currentAlarm.setPropagate(alarmDefinition.isPropagate()); @@ -226,6 +229,44 @@ class AlarmState { } } + private JsonNode createDetails(AlarmRuleState ruleState, DataSnapshot dataSnapshot) { + ObjectNode details = JacksonUtil.OBJECT_MAPPER.createObjectNode(); + String alarmDetails = ruleState.getAlarmRule().getAlarmDetails(); + + if (alarmDetails != null) { + for (KeyFilter keyFilter : ruleState.getAlarmRule().getCondition().getCondition()) { + EntityKeyValue entityKeyValue = dataSnapshot.getValue(keyFilter.getKey()); + alarmDetails = alarmDetails.replaceAll(String.format("\\$\\{%s}", keyFilter.getKey().getKey()), getValueAsString(entityKeyValue)); + } + + details.put("data", alarmDetails); + } + + return details; + } + + private static String getValueAsString(EntityKeyValue entityKeyValue) { + Object result = null; + switch (entityKeyValue.getDataType()) { + case STRING: + result = entityKeyValue.getStrValue(); + break; + case JSON: + result = entityKeyValue.getJsonValue(); + break; + case LONG: + result = String.valueOf(entityKeyValue.getLngValue()); + break; + case DOUBLE: + result = String.valueOf(entityKeyValue.getDblValue()); + break; + case BOOLEAN: + result = String.valueOf(entityKeyValue.getBoolValue()); + break; + } + return String.valueOf(result); + } + public boolean processAlarmClear(TbContext ctx, Alarm alarmNf) { boolean updated = false; if (currentAlarm != null && currentAlarm.getId().equals(alarmNf.getId())) { From 55b8ae92ea298a5eb2bddfe7c6b0d83a67a063de Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 4 Nov 2020 13:40:38 +0200 Subject: [PATCH 316/463] refactored --- .../org/thingsboard/rule/engine/profile/AlarmState.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 3d29da6bd7..8fe7bcb777 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -255,13 +255,13 @@ class AlarmState { result = entityKeyValue.getJsonValue(); break; case LONG: - result = String.valueOf(entityKeyValue.getLngValue()); + result = entityKeyValue.getLngValue(); break; case DOUBLE: - result = String.valueOf(entityKeyValue.getDblValue()); + result = entityKeyValue.getDblValue(); break; case BOOLEAN: - result = String.valueOf(entityKeyValue.getBoolValue()); + result = entityKeyValue.getBoolValue(); break; } return String.valueOf(result); From a2eea121bedf483e5a0a0db3e16fbb1891e96bcd Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 4 Nov 2020 18:14:18 +0200 Subject: [PATCH 317/463] AlarmState improvements --- .../org/thingsboard/rule/engine/profile/AlarmState.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 8fe7bcb777..c59dbb75ca 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -55,6 +55,7 @@ class AlarmState { private volatile boolean initialFetchDone; private volatile TbMsgMetaData lastMsgMetaData; private volatile String lastMsgQueueName; + private volatile DataSnapshot dataSnapshot; AlarmState(ProfileState deviceProfile, EntityId originator, DeviceProfileAlarm alarmDefinition, PersistedAlarmState alarmState) { this.deviceProfile = deviceProfile; @@ -94,7 +95,7 @@ class AlarmState { } } if (resultState != null) { - TbAlarmResult result = calculateAlarmResult(ctx, resultState, data); + TbAlarmResult result = calculateAlarmResult(ctx, resultState); if (result != null) { pushMsg(ctx, result); } @@ -189,7 +190,7 @@ class AlarmState { } } - private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmRuleState ruleState, T data) { + private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmRuleState ruleState) { AlarmSeverity severity = ruleState.getSeverity(); if (currentAlarm != null) { // TODO: In some extremely rare cases, we might miss the event of alarm clear (If one use in-mem queue and restarted the server) or (if one manipulated the rule chain). @@ -216,7 +217,7 @@ class AlarmState { currentAlarm.setSeverity(severity); currentAlarm.setStartTs(System.currentTimeMillis()); currentAlarm.setEndTs(currentAlarm.getStartTs()); - currentAlarm.setDetails(createDetails(ruleState, (DataSnapshot) data)); + currentAlarm.setDetails(createDetails(ruleState)); currentAlarm.setOriginator(originator); currentAlarm.setTenantId(ctx.getTenantId()); currentAlarm.setPropagate(alarmDefinition.isPropagate()); @@ -229,7 +230,7 @@ class AlarmState { } } - private JsonNode createDetails(AlarmRuleState ruleState, DataSnapshot dataSnapshot) { + private JsonNode createDetails(AlarmRuleState ruleState) { ObjectNode details = JacksonUtil.OBJECT_MAPPER.createObjectNode(); String alarmDetails = ruleState.getAlarmRule().getAlarmDetails(); From 580c8aef51a355254ba14eff88974fee83e12f13 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Fri, 30 Oct 2020 14:28:30 +0200 Subject: [PATCH 318/463] Added handler for too long payload exception in MQTT transport --- .../server/transport/mqtt/MqttTransportHandler.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 5d24f812eb..abb8f629f1 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -115,7 +115,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement log.trace("[{}] Processing msg: {}", sessionId, msg); try { if (msg instanceof MqttMessage) { - processMqttMsg(ctx, (MqttMessage) msg); + MqttMessage message = (MqttMessage) msg; + if (message.decoderResult().isSuccess()) { + processMqttMsg(ctx, message); + } else { + log.error("[{}] Message processing failed: {}", sessionId, message.decoderResult().cause().getMessage()); + ctx.close(); + } } else { ctx.close(); } From f6e9959ca5e215a61c5b8029c203dd166e1d5c9e Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 11 Nov 2020 12:22:00 +0200 Subject: [PATCH 319/463] Minor improvements --- .../DefaultSystemDataLoaderService.java | 3 --- .../usagestats/DefaultTbApiUsageClient.java | 4 +++- .../transport/mqtt/MqttTransportHandler.java | 6 +++-- .../DefaultTransportRateLimitService.java | 24 ++++++++++++++----- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index fd757ed6dd..6c7678f023 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -134,7 +134,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { TenantProfile isolatedTbCoreProfile = new TenantProfile(); isolatedTbCoreProfile.setDefault(false); isolatedTbCoreProfile.setName("Isolated TB Core"); - isolatedTbCoreProfile.setProfileData(new TenantProfileData()); isolatedTbCoreProfile.setDescription("Isolated TB Core tenant profile"); isolatedTbCoreProfile.setIsolatedTbCore(true); isolatedTbCoreProfile.setIsolatedTbRuleEngine(false); @@ -148,7 +147,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { TenantProfile isolatedTbRuleEngineProfile = new TenantProfile(); isolatedTbRuleEngineProfile.setDefault(false); isolatedTbRuleEngineProfile.setName("Isolated TB Rule Engine"); - isolatedTbRuleEngineProfile.setProfileData(new TenantProfileData()); isolatedTbRuleEngineProfile.setDescription("Isolated TB Rule Engine tenant profile"); isolatedTbRuleEngineProfile.setIsolatedTbCore(false); isolatedTbRuleEngineProfile.setIsolatedTbRuleEngine(true); @@ -163,7 +161,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { TenantProfile isolatedTbCoreAndTbRuleEngineProfile = new TenantProfile(); isolatedTbCoreAndTbRuleEngineProfile.setDefault(false); isolatedTbCoreAndTbRuleEngineProfile.setName("Isolated TB Core and TB Rule Engine"); - isolatedTbCoreAndTbRuleEngineProfile.setProfileData(new TenantProfileData()); isolatedTbCoreAndTbRuleEngineProfile.setDescription("Isolated TB Core and TB Rule Engine tenant profile"); isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbCore(true); isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbRuleEngine(true); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java index 0702322838..5c5b593d96 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java @@ -94,7 +94,9 @@ public class DefaultTbApiUsageClient implements TbApiUsageClient { TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).newByTopic(msgProducer.getDefaultTopic()); msgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), builder.build()), null); })); - log.info("Report statistics for: {} tenants", report.size()); + if (!report.isEmpty()) { + log.info("Report statistics for: {} tenants", report.size()); + } } catch (Exception e) { log.warn("Failed to report statistics: ", e); } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 14712a7cb3..024e42f9ac 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -464,8 +464,10 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement String userName = msg.payload().userName(); log.info("[{}] Processing connect msg for client with user name: {}!", sessionId, userName); TransportProtos.ValidateBasicMqttCredRequestMsg.Builder request = TransportProtos.ValidateBasicMqttCredRequestMsg.newBuilder() - .setClientId(msg.payload().clientIdentifier()) - .setUserName(userName); + .setClientId(msg.payload().clientIdentifier()); + if (userName != null) { + request.setUserName(userName); + } String password = msg.payload().password(); if (password != null) { request.setPassword(password); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java index b6f160ad1e..6dc1db0fc2 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java @@ -21,6 +21,7 @@ import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; @@ -77,6 +78,7 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi @Override public void update(TenantProfileUpdateResult update) { + log.info("Received tenant profile update: {}", update.getProfile()); EntityTransportRateLimits tenantRateLimitPrototype = createRateLimits(update.getProfile(), true); EntityTransportRateLimits deviceRateLimitPrototype = createRateLimits(update.getProfile(), false); for (TenantId tenantId : update.getAffectedTenants()) { @@ -114,16 +116,26 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi tenantAllowed.put(tenantId, allowed); } - private void mergeLimits(T deviceId, EntityTransportRateLimits newRateLimits, - Function getFunction, - BiConsumer putFunction) { - EntityTransportRateLimits oldRateLimits = getFunction.apply(deviceId); + private void mergeLimits(T entityId, EntityTransportRateLimits newRateLimits, + Function getFunction, + BiConsumer putFunction) { + EntityTransportRateLimits oldRateLimits = getFunction.apply(entityId); if (oldRateLimits == null) { - putFunction.accept(deviceId, newRateLimits); + if (EntityType.TENANT.equals(entityId.getEntityType())) { + log.info("[{}] New rate limits: {}", entityId, newRateLimits); + } else { + log.debug("[{}] New rate limits: {}", entityId, newRateLimits); + } + putFunction.accept(entityId, newRateLimits); } else { EntityTransportRateLimits updated = merge(oldRateLimits, newRateLimits); if (updated != null) { - putFunction.accept(deviceId, updated); + if (EntityType.TENANT.equals(entityId.getEntityType())) { + log.info("[{}] Updated rate limits: {}", entityId, updated); + } else { + log.debug("[{}] Updated rate limits: {}", entityId, updated); + } + putFunction.accept(entityId, updated); } } } From f157d07c1f4e1550155159a383bc934964ba6f05 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 11 Nov 2020 12:45:14 +0200 Subject: [PATCH 320/463] Improvements --- .../apiusage/DefaultTbApiUsageStateService.java | 11 +++++++---- .../server/service/queue/DefaultTbClusterService.java | 1 + application/src/main/resources/thingsboard.yml | 1 - 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 0dcef2c88d..d7b96c78be 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -221,6 +221,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @Override public void onTenantProfileUpdate(TenantProfileId tenantProfileId) { + log.info("[{}] On Tenant Profile Update", tenantProfileId); TenantProfile tenantProfile = tenantProfileCache.get(tenantProfileId); updateLock.lock(); try { @@ -236,6 +237,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @Override public void onTenantUpdate(TenantId tenantId) { + log.info("[{}] On Tenant Update", tenantId); TenantProfile tenantProfile = tenantProfileCache.get(tenantId); updateLock.lock(); try { @@ -248,16 +250,17 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } - private void updateTenantState(TenantApiUsageState state, TenantProfile tenantProfile) { + private void updateTenantState(TenantApiUsageState state, TenantProfile profile) { + log.info("[{}] On Tenant Update.", state.getTenantId()); TenantProfileData oldProfileData = state.getTenantProfileData(); - state.setTenantProfileId(tenantProfile.getId()); - state.setTenantProfileData(tenantProfile.getProfileData()); + state.setTenantProfileId(profile.getId()); + state.setTenantProfileData(profile.getProfileData()); Map result = state.checkStateUpdatedDueToThresholds(); if (!result.isEmpty()) { persistAndNotify(state, result); } updateProfileThresholds(state.getTenantId(), state.getApiUsageState().getId(), - oldProfileData.getConfiguration(), tenantProfile.getProfileData().getConfiguration()); + oldProfileData.getConfiguration(), profile.getProfileData().getConfiguration()); } private void updateProfileThresholds(TenantId tenantId, ApiUsageStateId id, diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index 18f064b745..edd53c6727 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -274,6 +274,7 @@ public class DefaultTbClusterService implements TbClusterService { TbQueueProducer> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer(); Set tbRuleEngineServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)); if (msg.getEntityId().getEntityType().equals(EntityType.TENANT) + || msg.getEntityId().getEntityType().equals(EntityType.TENANT_PROFILE) || msg.getEntityId().getEntityType().equals(EntityType.DEVICE_PROFILE) || msg.getEntityId().getEntityType().equals(EntityType.API_USAGE_STATE)) { TbQueueProducer> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer(); diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index f0f4b9dd54..2766793acc 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -766,7 +766,6 @@ metrics: # Metrics percentiles returned by actuator for timer metrics. List of double values (divided by ,). percentiles: "${METRICS_TIMER_PERCENTILES:0.5}" - management: endpoints: web: From ebf10b55c8cc1f3287ffb23baab3a255160a2f03 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 11 Nov 2020 12:59:22 +0200 Subject: [PATCH 321/463] api usage state alias improvements --- .../dao/sql/query/DefaultEntityQueryRepository.java | 2 +- .../server/dao/sql/query/EntityKeyMapping.java | 3 ++- .../dao/usagerecord/ApiUsageStateServiceImpl.java | 13 +++++++++++++ ui-ngx/src/app/shared/models/entity-type.models.ts | 9 ++++++++- ui-ngx/src/assets/locale/locale.constant-en_US.json | 3 ++- 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java index 380e166aa5..db61e6ab05 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java @@ -210,7 +210,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { entityTableMap.put(EntityType.CUSTOMER, "customer"); entityTableMap.put(EntityType.USER, "tb_user"); entityTableMap.put(EntityType.TENANT, "tenant"); - entityTableMap.put(EntityType.API_USAGE_STATE, "(select aus.id, aus.created_time, aus.tenant_id, '' as name, '' as additional_info from api_usage_state as aus)"); + entityTableMap.put(EntityType.API_USAGE_STATE, "api_usage_state"); } public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{ diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java index 525483f0a0..8d316aebcd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java @@ -79,7 +79,7 @@ public class EntityKeyMapping { public static final List dashboardEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, TITLE); public static final List labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, ADDITIONAL_INFO); public static final List contactBasedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, EMAIL, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO); - + public static final List apiUsageStateEntityFields = Collections.singletonList(CREATED_TIME); public static final Set commonEntityFieldsSet = new HashSet<>(commonEntityFields); public static final Set relationQueryEntityFieldsSet = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, FIRST_NAME, LAST_NAME, EMAIL, REGION, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO)); @@ -99,6 +99,7 @@ public class EntityKeyMapping { allowedEntityFieldMap.put(EntityType.RULE_NODE, new HashSet<>(commonEntityFields)); allowedEntityFieldMap.put(EntityType.WIDGET_TYPE, new HashSet<>(widgetEntityFields)); allowedEntityFieldMap.put(EntityType.WIDGETS_BUNDLE, new HashSet<>(widgetEntityFields)); + allowedEntityFieldMap.put(EntityType.API_USAGE_STATE, new HashSet<>(apiUsageStateEntityFields)); entityFieldColumnMap.put(CREATED_TIME, ModelConstants.CREATED_TIME_PROPERTY); entityFieldColumnMap.put(ENTITY_TYPE, ModelConstants.ENTITY_TYPE_PROPERTY); diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index ed02bf7726..b525a71eaa 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.usagerecord; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageStateValue; @@ -27,6 +28,7 @@ import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; import org.thingsboard.server.dao.entity.AbstractEntityService; @@ -84,6 +86,17 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A TenantProfile tenantProfile = tenantProfileDao.findById(tenantId, tenant.getTenantProfileId().getId()); TenantProfileConfiguration configuration = tenantProfile.getProfileData().getConfiguration(); List profileThresholds = new ArrayList<>(); + List apiUsageStates = new ArrayList<>(); + apiUsageStates.add(new BasicTsKvEntry(apiUsageState.getCreatedTime(), + new StringDataEntry(ApiFeature.TRANSPORT.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); + apiUsageStates.add(new BasicTsKvEntry(apiUsageState.getCreatedTime(), + new StringDataEntry(ApiFeature.DB.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); + apiUsageStates.add(new BasicTsKvEntry(apiUsageState.getCreatedTime(), + new StringDataEntry(ApiFeature.RE.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); + apiUsageStates.add(new BasicTsKvEntry(apiUsageState.getCreatedTime(), + new StringDataEntry(ApiFeature.JS.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); + tsService.save(tenantId, saved.getId(), apiUsageStates, 0L); + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { profileThresholds.add(new BasicTsKvEntry(saved.getCreatedTime(), new LongDataEntry(key.getApiLimitKey(), configuration.getProfileThreshold(key)))); } diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index c6a94ad8b5..e53c5fb10e 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -47,7 +47,8 @@ export enum EntityType { RULE_NODE = 'RULE_NODE', ENTITY_VIEW = 'ENTITY_VIEW', WIDGETS_BUNDLE = 'WIDGETS_BUNDLE', - WIDGET_TYPE = 'WIDGET_TYPE' + WIDGET_TYPE = 'WIDGET_TYPE', + API_USAGE_STATE = 'API_USAGE_STATE' } export enum AliasEntityType { @@ -238,6 +239,12 @@ export const entityTypeTranslations = new Map Date: Wed, 11 Nov 2020 13:03:22 +0200 Subject: [PATCH 322/463] refactored --- .../thingsboard/server/dao/sql/query/EntityKeyMapping.java | 5 +++-- .../server/dao/usagerecord/ApiUsageStateServiceImpl.java | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java index 8d316aebcd..7332de4b49 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java @@ -79,7 +79,8 @@ public class EntityKeyMapping { public static final List dashboardEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, TITLE); public static final List labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, ADDITIONAL_INFO); public static final List contactBasedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, EMAIL, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO); - public static final List apiUsageStateEntityFields = Collections.singletonList(CREATED_TIME); + + public static final Set apiUsageStateEntityFields = Collections.singleton(CREATED_TIME); public static final Set commonEntityFieldsSet = new HashSet<>(commonEntityFields); public static final Set relationQueryEntityFieldsSet = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, FIRST_NAME, LAST_NAME, EMAIL, REGION, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO)); @@ -99,7 +100,7 @@ public class EntityKeyMapping { allowedEntityFieldMap.put(EntityType.RULE_NODE, new HashSet<>(commonEntityFields)); allowedEntityFieldMap.put(EntityType.WIDGET_TYPE, new HashSet<>(widgetEntityFields)); allowedEntityFieldMap.put(EntityType.WIDGETS_BUNDLE, new HashSet<>(widgetEntityFields)); - allowedEntityFieldMap.put(EntityType.API_USAGE_STATE, new HashSet<>(apiUsageStateEntityFields)); + allowedEntityFieldMap.put(EntityType.API_USAGE_STATE, apiUsageStateEntityFields); entityFieldColumnMap.put(CREATED_TIME, ModelConstants.CREATED_TIME_PROPERTY); entityFieldColumnMap.put(ENTITY_TYPE, ModelConstants.ENTITY_TYPE_PROPERTY); diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index b525a71eaa..12b5b3c01a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -85,7 +85,6 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A Tenant tenant = tenantDao.findById(tenantId, tenantId.getId()); TenantProfile tenantProfile = tenantProfileDao.findById(tenantId, tenant.getTenantProfileId().getId()); TenantProfileConfiguration configuration = tenantProfile.getProfileData().getConfiguration(); - List profileThresholds = new ArrayList<>(); List apiUsageStates = new ArrayList<>(); apiUsageStates.add(new BasicTsKvEntry(apiUsageState.getCreatedTime(), new StringDataEntry(ApiFeature.TRANSPORT.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); @@ -97,6 +96,8 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A new StringDataEntry(ApiFeature.JS.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); tsService.save(tenantId, saved.getId(), apiUsageStates, 0L); + List profileThresholds = new ArrayList<>(); + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { profileThresholds.add(new BasicTsKvEntry(saved.getCreatedTime(), new LongDataEntry(key.getApiLimitKey(), configuration.getProfileThreshold(key)))); } From 91aa144ae53f7598868e6b3f5c73985b6ecb32f9 Mon Sep 17 00:00:00 2001 From: Kalutka Zhenya Date: Wed, 11 Nov 2020 15:13:59 +0200 Subject: [PATCH 323/463] Fix widget margin --- ui-ngx/src/app/core/services/dashboard-utils.service.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ui-ngx/src/app/core/services/dashboard-utils.service.ts b/ui-ngx/src/app/core/services/dashboard-utils.service.ts index 0b21a3f9be..a825b51f72 100644 --- a/ui-ngx/src/app/core/services/dashboard-utils.service.ts +++ b/ui-ngx/src/app/core/services/dashboard-utils.service.ts @@ -422,6 +422,12 @@ export class DashboardUtilsService { widgetLayout.row = row; widgetLayout.col = 0; } + + widgetLayout.sizeX = Math.floor(widgetLayout.sizeX); + widgetLayout.sizeY = Math.floor(widgetLayout.sizeY); + widgetLayout.row = Math.floor(widgetLayout.row); + widgetLayout.col = Math.floor(widgetLayout.col); + layout.widgets[widget.id] = widgetLayout; } From 78c06be350aebd90bfbd1087a22ee726b03c403b Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 11 Nov 2020 16:00:04 +0200 Subject: [PATCH 324/463] change protobuf-dynamic dependency --- common/data/pom.xml | 2 +- pom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/data/pom.xml b/common/data/pom.xml index 6d32a3df53..fc1311ab02 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -76,7 +76,7 @@ wire-schema - com.github.os72 + org.thingsboard protobuf-dynamic diff --git a/pom.xml b/pom.xml index c6fb306f25..66000b3316 100755 --- a/pom.xml +++ b/pom.xml @@ -106,7 +106,7 @@ 3.2.2 1.5.0 1.5.2 - 1.0.2 + 1.0.2TB 3.4.0 @@ -1372,7 +1372,7 @@ ${micrometer.version} - com.github.os72 + org.thingsboard protobuf-dynamic ${protobuf-dynamic.version} From 5de0059eb765631e7ac4862fe4c19bb4de75b98c Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 11 Nov 2020 16:25:25 +0200 Subject: [PATCH 325/463] apiUsageState improvements --- .../server/dao/usagerecord/ApiUsageStateServiceImpl.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index 12b5b3c01a..7836154c62 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -86,13 +86,13 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A TenantProfile tenantProfile = tenantProfileDao.findById(tenantId, tenant.getTenantProfileId().getId()); TenantProfileConfiguration configuration = tenantProfile.getProfileData().getConfiguration(); List apiUsageStates = new ArrayList<>(); - apiUsageStates.add(new BasicTsKvEntry(apiUsageState.getCreatedTime(), + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), new StringDataEntry(ApiFeature.TRANSPORT.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); - apiUsageStates.add(new BasicTsKvEntry(apiUsageState.getCreatedTime(), + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), new StringDataEntry(ApiFeature.DB.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); - apiUsageStates.add(new BasicTsKvEntry(apiUsageState.getCreatedTime(), + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), new StringDataEntry(ApiFeature.RE.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); - apiUsageStates.add(new BasicTsKvEntry(apiUsageState.getCreatedTime(), + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), new StringDataEntry(ApiFeature.JS.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); tsService.save(tenantId, saved.getId(), apiUsageStates, 0L); From b02cbec0ee6b6bd2979b7f8b6e825642effa904d Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 11 Nov 2020 16:41:07 +0200 Subject: [PATCH 326/463] Minor logging improvements --- .../service/queue/processing/AbstractConsumerService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java index 0dfc9c5742..2d9ca3f042 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java @@ -153,6 +153,8 @@ public abstract class AbstractConsumerService Date: Wed, 11 Nov 2020 16:48:28 +0200 Subject: [PATCH 327/463] clean up code --- application/src/main/resources/thingsboard.yml | 2 +- ...ctMqttAttributesUpdatesIntegrationTest.java | 18 +++++++++--------- ...ttAttributesUpdatesJsonIntegrationTest.java | 18 +++++++++--------- ...tAttributesUpdatesProtoIntegrationTest.java | 1 - .../mqtt/session/DeviceSessionCtx.java | 4 +++- .../mqtt/util/MqttDynamicProtoSchemaTest.java | 6 ++---- 6 files changed, 24 insertions(+), 25 deletions(-) rename application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java => common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttDynamicProtoSchemaTest.java (99%) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index c935154b6e..deb28ffd99 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -429,7 +429,7 @@ spring: database-platform: "${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}" datasource: driverClassName: "${SPRING_DRIVER_CLASS_NAME:org.postgresql.Driver}" - url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard_ce_3_2}" + url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard}" username: "${SPRING_DATASOURCE_USERNAME:postgres}" password: "${SPRING_DATASOURCE_PASSWORD:postgres}" hikari: diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java index c40c2f2914..d2febdf357 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java @@ -65,15 +65,15 @@ public abstract class AbstractMqttAttributesUpdatesIntegrationTest extends Abstr processAfterTest(); } -// @Test -// public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { -// processTestSubscribeToAttributesUpdates(); -// } -// -// @Test -// public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { -// processGatewayTestSubscribeToAttributesUpdates(); -// } + @Test + public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { + processTestSubscribeToAttributesUpdates(); + } + + @Test + public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { + processGatewayTestSubscribeToAttributesUpdates(); + } protected void processTestSubscribeToAttributesUpdates() throws Exception { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java index 4f5a5f9369..58379e4016 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java @@ -39,13 +39,13 @@ public abstract class AbstractMqttAttributesUpdatesJsonIntegrationTest extends A processAfterTest(); } -// @Test -// public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { -// processTestSubscribeToAttributesUpdates(); -// } -// -// @Test -// public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { -// processGatewayTestSubscribeToAttributesUpdates(); -// } + @Test + public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { + processTestSubscribeToAttributesUpdates(); + } + + @Test + public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { + processGatewayTestSubscribeToAttributesUpdates(); + } } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java index 16ccc789ee..faf8e1ce4d 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java @@ -19,7 +19,6 @@ import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index 79147c0935..6ecea5a77c 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -80,7 +80,9 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { return telemetryTopicFilter.filter(topicName); } - public boolean isDeviceAttributesTopic(String topicName) { return attributesTopicFilter.filter(topicName); } + public boolean isDeviceAttributesTopic(String topicName) { + return attributesTopicFilter.filter(topicName); + } public MqttTransportAdaptor getPayloadAdaptor() { return payloadType.equals(TransportPayloadType.JSON) ? context.getJsonMqttAdaptor() : context.getProtoMqttAdaptor(); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttDynamicProtoSchemaTest.java similarity index 99% rename from application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java rename to common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttDynamicProtoSchemaTest.java index a5b79310ec..47726ff610 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/proto/DynamicProtoTest.java +++ b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttDynamicProtoSchemaTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.mqtt.proto; +package org.thingsboard.server.transport.mqtt.util; import com.github.os72.protobuf.dynamic.DynamicSchema; import com.google.protobuf.Descriptors; @@ -22,7 +22,6 @@ import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.util.JsonFormat; import com.squareup.wire.schema.internal.parser.ProtoFileElement; import com.squareup.wire.schema.internal.parser.ProtoParser; -import lombok.extern.slf4j.Slf4j; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -38,9 +37,8 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration.LOCATION; -@Slf4j @RunWith(MockitoJUnitRunner.class) -public class DynamicProtoTest { +public class MqttDynamicProtoSchemaTest { private static final String PROTO_SCHEMA_WITH_NESTED_MSG_TYPES = "syntax = \"proto3\";\n" + "\n" + From a52fee3aa1baa5c0945b7f61f05875fbb7fd5d0f Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 11 Nov 2020 16:59:24 +0200 Subject: [PATCH 328/463] fix typos, remove unused imports --- .../mqtt/AbstractMqttIntegrationTest.java | 1 - .../server/mqtt/MqttSqlTestSuite.java | 1 - ...tMqttAttributesRequestIntegrationTest.java | 5 --- ...tAttributesRequestJsonIntegrationTest.java | 5 --- ...AttributesRequestProtoIntegrationTest.java | 2 -- ...tributesRequestJsonSqlIntegrationTest.java | 1 - ...ributesRequestProtoSqlIntegrationTest.java | 1 - ...tMqttAttributesUpdatesIntegrationTest.java | 6 ---- ...tAttributesUpdatesJsonIntegrationTest.java | 5 --- ...AttributesUpdatesProtoIntegrationTest.java | 2 -- ...tributesUpdatesSqlJsonIntegrationTest.java | 1 - ...ributesUpdatesSqlProtoIntegrationTest.java | 1 - .../claim/AbstractMqttClaimDeviceTest.java | 1 - .../AbstractMqttClaimJsonDeviceTest.java | 1 - .../AbstractMqttClaimProtoDeviceTest.java | 1 - .../claim/sql/MqttClaimDeviceJsonSqlTest.java | 1 - .../sql/MqttClaimDeviceProtoSqlTest.java | 1 - ...ttServerSideRpcDefaultIntegrationTest.java | 35 ------------------- ...tractMqttServerSideRpcIntegrationTest.java | 19 ---------- ...tMqttServerSideRpcJsonIntegrationTest.java | 1 - ...MqttServerSideRpcProtoIntegrationTest.java | 4 --- ...actMqttAttributesProtoIntegrationTest.java | 3 -- ...qttAttributesNoSqlJsonIntegrationTest.java | 1 - ...ttAttributesNoSqlProtoIntegrationTest.java | 1 - ...MqttAttributesSqlProtoIntegrationTest.java | 1 - ...AbstractMqttTimeseriesIntegrationTest.java | 1 - ...ractMqttTimeseriesJsonIntegrationTest.java | 1 - ...actMqttTimeseriesProtoIntegrationTest.java | 1 - .../MqttTimeseriesSqlJsonIntegrationTest.java | 1 - ...MqttTimeseriesSqlProtoIntegrationTest.java | 1 - 30 files changed, 106 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java index 4e9186f6f0..de84698dc0 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java @@ -49,7 +49,6 @@ import org.thingsboard.server.gen.transport.TransportProtos; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java index 440c587856..d25a2ea1ab 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java @@ -33,7 +33,6 @@ import java.util.Arrays; "org.thingsboard.server.mqtt.attributes.request.sql.*Test", "org.thingsboard.server.mqtt.claim.sql.*Test", "org.thingsboard.server.mqtt.provision.sql.*Test", - "org.thingsboard.server.mqtt.proto.sql.*Test" }) public class MqttSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java index 438757bfff..51d38e6312 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java @@ -18,14 +18,11 @@ package org.thingsboard.server.mqtt.attributes.request; import com.google.protobuf.InvalidProtocolBufferException; import io.netty.handler.codec.mqtt.MqttQoS; import lombok.extern.slf4j.Slf4j; -import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; -import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.device.profile.MqttTopics; @@ -37,9 +34,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Slf4j diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java index 4b824ea0b5..83ef04f65e 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java @@ -18,14 +18,9 @@ package org.thingsboard.server.mqtt.attributes.request; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - @Slf4j public abstract class AbstractMqttAttributesRequestJsonIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java index e28102e9de..8a8be4d1cc 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.attributes.request; import com.github.os72.protobuf.dynamic.DynamicSchema; -import com.google.gson.Gson; import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; import com.google.protobuf.InvalidProtocolBufferException; @@ -27,7 +26,6 @@ import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java index d3750d49a3..146972bade 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.attributes.request.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestIntegrationTest; import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestJsonIntegrationTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java index f52e79b2ab..52ec27f6f8 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.attributes.request.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestJsonIntegrationTest; import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestProtoIntegrationTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java index d2febdf357..b91739790c 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java @@ -18,11 +18,7 @@ package org.thingsboard.server.mqtt.attributes.updates; import com.google.protobuf.InvalidProtocolBufferException; import io.netty.handler.codec.mqtt.MqttQoS; import lombok.extern.slf4j.Slf4j; -import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; -import org.eclipse.paho.client.mqttv3.MqttCallback; -import org.eclipse.paho.client.mqttv3.MqttException; -import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -33,12 +29,10 @@ import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.mqtt.attributes.AbstractMqttAttributesIntegrationTest; import java.nio.charset.StandardCharsets; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Slf4j diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java index 58379e4016..e41ff83291 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java @@ -21,11 +21,6 @@ import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - @Slf4j public abstract class AbstractMqttAttributesUpdatesJsonIntegrationTest extends AbstractMqttAttributesUpdatesIntegrationTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java index faf8e1ce4d..213540699b 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java @@ -21,11 +21,9 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; -import java.nio.charset.StandardCharsets; import java.util.List; import java.util.stream.Collectors; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java index a8fd4687f7..1841340835 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.attributes.updates.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesIntegrationTest; import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesJsonIntegrationTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java index 723e5e3dcd..7ff542a4ec 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.attributes.updates.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesJsonIntegrationTest; import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesProtoIntegrationTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimDeviceTest.java index dd16c17be2..5a1cbd6356 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimDeviceTest.java @@ -20,7 +20,6 @@ import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.ClaimRequest; import org.thingsboard.server.common.data.Customer; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java index f55cfa57c8..49aa6c995b 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java @@ -18,7 +18,6 @@ package org.thingsboard.server.mqtt.claim; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java index d371c09f37..be0cfc7c81 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java @@ -19,7 +19,6 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.gen.transport.TransportApiProtos; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java b/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java index da794288f4..543d805721 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.claim.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.claim.AbstractMqttClaimDeviceTest; import org.thingsboard.server.mqtt.claim.AbstractMqttClaimJsonDeviceTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java b/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java index a63978e4de..e5d866d6e2 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.claim.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.claim.AbstractMqttClaimJsonDeviceTest; import org.thingsboard.server.mqtt.claim.AbstractMqttClaimProtoDeviceTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java index 23b93f427e..e3de80e813 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java @@ -15,49 +15,14 @@ */ package org.thingsboard.server.mqtt.rpc; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.protobuf.InvalidProtocolBufferException; -import com.nimbusds.jose.util.StandardCharset; import com.datastax.oss.driver.api.core.uuid.Uuids; -import io.netty.handler.codec.mqtt.MqttQoS; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; -import org.eclipse.paho.client.mqttv3.MqttAsyncClient; -import org.eclipse.paho.client.mqttv3.MqttCallback; -import org.eclipse.paho.client.mqttv3.MqttConnectOptions; -import org.eclipse.paho.client.mqttv3.MqttException; -import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; import org.junit.Assert; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.DeviceProfileType; -import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; -import org.thingsboard.server.common.data.device.profile.DeviceProfileData; -import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttTopics; -import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.controller.AbstractControllerTest; -import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.service.security.AccessValidator; -import java.util.Arrays; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java index e08f1665a4..208aa03a48 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java @@ -15,9 +15,7 @@ */ package org.thingsboard.server.mqtt.rpc; -import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.protobuf.InvalidProtocolBufferException; import com.nimbusds.jose.util.StandardCharset; import io.netty.handler.codec.mqtt.MqttQoS; @@ -26,35 +24,18 @@ import org.apache.commons.lang3.StringUtils; import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttCallback; -import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; -import org.junit.After; import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.DeviceProfileType; -import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; -import org.thingsboard.server.common.data.device.profile.DeviceProfileData; -import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.MqttTopics; -import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest; -import org.thingsboard.server.service.security.AccessValidator; import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java index d9ff14e1d2..2bfb0c1f2d 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java @@ -19,7 +19,6 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java index 759a5da912..062a074c0d 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java @@ -22,16 +22,12 @@ import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - @Slf4j public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java index bbceb277f5..f7f9bdef7d 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java @@ -21,8 +21,6 @@ import com.google.protobuf.DynamicMessage; import com.squareup.wire.schema.internal.parser.ProtoFileElement; import lombok.extern.slf4j.Slf4j; import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; @@ -32,7 +30,6 @@ import org.thingsboard.server.gen.transport.TransportProtos; import java.util.Arrays; import java.util.List; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlJsonIntegrationTest.java index 68ab8cff62..53a6f4adde 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlJsonIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.telemetry.attributes.nosql; import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesIntegrationTest; import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesJsonIntegrationTest; @DaoNoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlProtoIntegrationTest.java index d508fd76c5..23b6052272 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlProtoIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.telemetry.attributes.nosql; import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesIntegrationTest; import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesProtoIntegrationTest; @DaoNoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlProtoIntegrationTest.java index 5f486916ae..c5eeb06546 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlProtoIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.telemetry.attributes.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesJsonIntegrationTest; import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesProtoIntegrationTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java index f6294cd990..9076edc546 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java @@ -27,7 +27,6 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java index edab8405cd..181a35fa93 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java @@ -27,7 +27,6 @@ import org.thingsboard.server.common.data.device.profile.MqttTopics; import java.util.Arrays; import java.util.List; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @Slf4j diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java index 384063f55a..d463dc1963 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java @@ -22,7 +22,6 @@ import com.squareup.wire.schema.internal.parser.ProtoFileElement; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.junit.After; - import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java index f313cc5d29..0b494b6bea 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.telemetry.timeseries.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesIntegrationTest; import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesJsonIntegrationTest; /** diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java index 3c7d5e2e2a..2a80483879 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.telemetry.timeseries.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesJsonIntegrationTest; import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesProtoIntegrationTest; /** From 46e2ebe3a49905bf6112870e2c909aa060041b75 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 11 Nov 2020 17:58:45 +0200 Subject: [PATCH 329/463] moved DeviceController proto schema check to DeviceProfileServiceImpl --- .../controller/DeviceProfileController.java | 16 ---------------- .../controller/ControllerSqlTestSuite.java | 2 +- .../dao/device/DeviceProfileServiceImpl.java | 15 +++++++++++++-- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index dba9088264..7f0905b178 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -97,13 +97,6 @@ public class DeviceProfileController extends BaseController { checkEntity(deviceProfile.getId(), deviceProfile, Resource.DEVICE_PROFILE); - DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); - - if (transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration) { - MqttProtoDeviceProfileTransportConfiguration protoTransportConfiguration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; - checkProtoSchemas(protoTransportConfiguration); - } - DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); tbClusterService.onDeviceProfileChange(savedDeviceProfile, null); @@ -212,13 +205,4 @@ public class DeviceProfileController extends BaseController { } } - private void checkProtoSchemas(MqttProtoDeviceProfileTransportConfiguration mqttTransportConfiguration) throws ThingsboardException { - try { - mqttTransportConfiguration.validateTransportProtoSchema(mqttTransportConfiguration.getDeviceAttributesProtoSchema(), MqttProtoDeviceProfileTransportConfiguration.ATTRIBUTES_PROTO_SCHEMA); - mqttTransportConfiguration.validateTransportProtoSchema(mqttTransportConfiguration.getDeviceTelemetryProtoSchema(), MqttProtoDeviceProfileTransportConfiguration.TELEMETRY_PROTO_SCHEMA); - } catch (Exception exception) { - throw new ThingsboardException(exception.getMessage(), ThingsboardErrorCode.GENERAL); - } - } - } diff --git a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java index 64f9b8fddf..dbacf4af64 100644 --- a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java @@ -28,7 +28,7 @@ import java.util.Arrays; @ClasspathSuite.ClassnameFilters({ // "org.thingsboard.server.controller.sql.WebsocketApiSqlTest", // "org.thingsboard.server.controller.sql.EntityQueryControllerSqlTest", - "org.thingsboard.server.controller.sql.*Test", + "org.thingsboard.server.controller.sql.DeviceProfileControllerSqlTest", }) public class ControllerSqlTestSuite { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index f3857cacd5..e5399fc8d4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -29,12 +29,13 @@ import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -48,7 +49,6 @@ import org.thingsboard.server.dao.tenant.TenantDao; import java.util.Arrays; import java.util.Collections; -import java.util.List; import static org.thingsboard.server.common.data.CacheConstants.DEVICE_PROFILE_CACHE; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -310,6 +310,17 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D if (defaultDeviceProfile != null && !defaultDeviceProfile.getId().equals(deviceProfile.getId())) { throw new DataValidationException("Another default device profile is present in scope of current tenant!"); } + } else { + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + if (transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration) { + MqttProtoDeviceProfileTransportConfiguration protoTransportConfiguration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; + try { + protoTransportConfiguration.validateTransportProtoSchema(protoTransportConfiguration.getDeviceAttributesProtoSchema(), MqttProtoDeviceProfileTransportConfiguration.ATTRIBUTES_PROTO_SCHEMA); + protoTransportConfiguration.validateTransportProtoSchema(protoTransportConfiguration.getDeviceTelemetryProtoSchema(), MqttProtoDeviceProfileTransportConfiguration.TELEMETRY_PROTO_SCHEMA); + } catch (Exception exception) { + throw new DataValidationException(exception.getMessage()); + } + } } } From 81585f04c833805c3d05416ee1de46c83aa6af45 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 11 Nov 2020 18:02:10 +0200 Subject: [PATCH 330/463] Improvements --- .../DefaultTbApiUsageStateService.java | 3 +-- .../transport/DefaultTransportApiService.java | 25 +++++++++++-------- docker/README.md | 2 +- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index f5d65b8397..c7206712c3 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -237,7 +237,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @Override public void onTenantUpdate(TenantId tenantId) { - log.info("[{}] On Tenant Update", tenantId); + log.info("[{}] On Tenant Update.", tenantId); TenantProfile tenantProfile = tenantProfileCache.get(tenantId); updateLock.lock(); try { @@ -251,7 +251,6 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } private void updateTenantState(TenantApiUsageState state, TenantProfile profile) { - log.info("[{}] On Tenant Update.", state.getTenantId()); TenantProfileData oldProfileData = state.getTenantProfileData(); state.setTenantProfileId(profile.getId()); state.setTenantProfileData(profile.getProfileData()); diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 3e9e413ceb..c7cbea8200 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -164,22 +164,25 @@ public class DefaultTransportApiService implements TransportApiService { } private ListenableFuture validateCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) { - DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName()); - if (credentials != null) { - if (credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) { - return getDeviceInfo(credentials.getDeviceId(), credentials); - } else if (credentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) { - if (!checkMqttCredentials(mqtt, credentials)) { - credentials = null; + DeviceCredentials credentials = null; + if (mqtt.getUserName() != null) { + credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName()); + if (credentials != null) { + if (credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) { + return getDeviceInfo(credentials.getDeviceId(), credentials); + } else if (credentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) { + if (!checkMqttCredentials(mqtt, credentials)) { + credentials = null; + } } } - } - if (credentials == null) { - credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash("|", mqtt.getClientId(), mqtt.getUserName())); if (credentials == null) { - credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash(mqtt.getClientId())); + credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash("|", mqtt.getClientId(), mqtt.getUserName())); } } + if (credentials == null) { + credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash(mqtt.getClientId())); + } if (credentials != null) { return getDeviceInfo(credentials.getDeviceId(), credentials); } else { diff --git a/docker/README.md b/docker/README.md index a1051c039e..9664edc48e 100644 --- a/docker/README.md +++ b/docker/README.md @@ -58,7 +58,7 @@ In case of any issues you can examine service logs for errors. For example to see ThingsBoard node logs execute the following command: ` -$ docker-compose logs -f tb-core1 tb-rule-engine1 +$ docker-compose logs -f tb-core1 tb-core2 tb-rule-engine1 tb-rule-engine2 tb-mqtt-transport1 tb-mqtt-transport2 ` Or use `docker-compose ps` to see the state of all the containers. From 2ccaa095cb5e255410641bcb11ee3e73b7d4e39e Mon Sep 17 00:00:00 2001 From: Kalutka Zhenya Date: Thu, 24 Sep 2020 14:47:29 +0300 Subject: [PATCH 331/463] Fix marker move --- .../home/components/widget/lib/maps/leaflet-map.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts index 5d6f39850e..14d296436d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts @@ -410,10 +410,16 @@ export default abstract class LeafletMap { } convertToCustomFormat(position: L.LatLng): object { - return { - [this.options.latKeyName]: position.lat % 90, - [this.options.lngKeyName]: position.lng % 180 - }; + if(position.lng > 180){ + position.lng = 180; + }else if(position.lng < -180){ + position.lng = -180; + }; + + return { + [this.options.latKeyName]: position.lat, + [this.options.lngKeyName]: position.lng + }; } convertToPolygonFormat(points: Array): Array { From a06c8354973e361c760fd90b293a7fad41d18a6a Mon Sep 17 00:00:00 2001 From: Kalutka Zhenya Date: Mon, 28 Sep 2020 14:48:47 +0300 Subject: [PATCH 332/463] Fix marker move and marker point --- .../components/widget/lib/maps/leaflet-map.ts | 6 +++-- .../widget/lib/maps/providers/image-map.ts | 25 ++++++++++++++++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts index 14d296436d..742451ec5e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts @@ -131,10 +131,13 @@ export default abstract class LeafletMap { tooltipAnchor: [16, -28], shadowSize: [41, 41] }); + const customLatLng = this.convertToCustomFormat(mousePositionOnMap); + mousePositionOnMap.lat = customLatLng[this.options.latKeyName]; + mousePositionOnMap.lng = customLatLng[this.options.lngKeyName]; + const newMarker = L.marker(mousePositionOnMap, { icon }).addTo(this.map); this.addMarkers.push(newMarker); const datasourcesList = document.createElement('div'); - const customLatLng = this.convertToCustomFormat(mousePositionOnMap); const header = document.createElement('p'); header.appendChild(document.createTextNode('Select entity:')); header.setAttribute('style', 'font-size: 14px; margin: 8px 0'); @@ -415,7 +418,6 @@ export default abstract class LeafletMap { }else if(position.lng < -180){ position.lng = -180; }; - return { [this.options.latKeyName]: position.lat, [this.options.lngKeyName]: position.lng diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts index 1b20ddad01..2c967660e0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts @@ -258,10 +258,29 @@ export class ImageMap extends LeafletMap { } convertToCustomFormat(position: L.LatLng, width = this.width, height = this.height): object { - const point = this.latLngToPoint(position); + let point = this.latLngToPoint(position); + const customX = calculateNewPointCoordinate(point.x, width); + const customY = calculateNewPointCoordinate(point.y, height); + + if(customX === 0){ + point.x = 0; + } else if(customX === 1){ + point.x = width; + } + + if(customY === 0){ + point.y = 0; + } else if(customY === 1){ + point.y = height; + } + + const customLatLng = this.pointToLatLng(point.x,point.y) + return { - [this.options.xPosKeyName]: calculateNewPointCoordinate(point.x, width), - [this.options.yPosKeyName]: calculateNewPointCoordinate(point.y, height) + [this.options.xPosKeyName]: customX, + [this.options.yPosKeyName]: customY, + [this.options.latKeyName]:customLatLng.lat, + [this.options.lngKeyName]:customLatLng.lng }; } From 60de89015acf58d7efc9903d9713661c04f8afed Mon Sep 17 00:00:00 2001 From: Kalutka Zhenya Date: Mon, 28 Sep 2020 15:01:20 +0300 Subject: [PATCH 333/463] Refactoring --- .../modules/home/components/widget/lib/maps/leaflet-map.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts index 742451ec5e..f29ab38f03 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts @@ -132,8 +132,8 @@ export default abstract class LeafletMap { shadowSize: [41, 41] }); const customLatLng = this.convertToCustomFormat(mousePositionOnMap); - mousePositionOnMap.lat = customLatLng[this.options.latKeyName]; - mousePositionOnMap.lng = customLatLng[this.options.lngKeyName]; + mousePositionOnMap.lat = customLatLng[this.options.latKeyName]; + mousePositionOnMap.lng = customLatLng[this.options.lngKeyName]; const newMarker = L.marker(mousePositionOnMap, { icon }).addTo(this.map); this.addMarkers.push(newMarker); From 724b6c5247c97e661b104985fbb5956c88fae0dc Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 11 Nov 2020 18:03:33 +0200 Subject: [PATCH 334/463] Refactoring --- .../components/widget/lib/maps/leaflet-map.ts | 6 +++--- .../home/components/widget/lib/maps/markers.ts | 2 +- .../widget/lib/maps/providers/image-map.ts | 17 ++++++++--------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts index f29ab38f03..0f1f08799c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts @@ -413,11 +413,11 @@ export default abstract class LeafletMap { } convertToCustomFormat(position: L.LatLng): object { - if(position.lng > 180){ + if (position.lng > 180) { position.lng = 180; - }else if(position.lng < -180){ + } else if (position.lng < -180) { position.lng = -180; - }; + } return { [this.options.latKeyName]: position.lat, [this.options.lngKeyName]: position.lng diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts index 4796956fa6..5f9e5e8362 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts @@ -94,7 +94,7 @@ export class Marker { } updateMarkerPosition(position: L.LatLng) { - if (!this.location.equals(position)) { + if (!this.leafletMarker.getLatLng().equals(position)) { this.location = position; this.leafletMarker.setLatLng(position); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts index 2c967660e0..825ace1227 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts @@ -258,29 +258,28 @@ export class ImageMap extends LeafletMap { } convertToCustomFormat(position: L.LatLng, width = this.width, height = this.height): object { - let point = this.latLngToPoint(position); + const point = this.latLngToPoint(position); const customX = calculateNewPointCoordinate(point.x, width); const customY = calculateNewPointCoordinate(point.y, height); - if(customX === 0){ + if (customX === 0) { point.x = 0; - } else if(customX === 1){ + } else if (customX === 1) { point.x = width; } - if(customY === 0){ + if (customY === 0) { point.y = 0; - } else if(customY === 1){ + } else if (customY === 1) { point.y = height; } - - const customLatLng = this.pointToLatLng(point.x,point.y) + const customLatLng = this.pointToLatLng(point.x, point.y); return { [this.options.xPosKeyName]: customX, [this.options.yPosKeyName]: customY, - [this.options.latKeyName]:customLatLng.lat, - [this.options.lngKeyName]:customLatLng.lng + [this.options.latKeyName]: customLatLng.lat, + [this.options.lngKeyName]: customLatLng.lng }; } From 4725ad14514036f773b23670fbce1880a22d8192 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 11 Nov 2020 18:29:16 +0200 Subject: [PATCH 335/463] fix typo --- .../server/controller/DeviceProfileController.java | 6 ------ .../java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index 7f0905b178..fcb34530b5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -29,12 +29,7 @@ import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.audit.ActionType; -import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.page.PageData; @@ -204,5 +199,4 @@ public class DeviceProfileController extends BaseController { throw handleException(e); } } - } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java index d25a2ea1ab..f71e8f37a2 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java @@ -32,7 +32,7 @@ import java.util.Arrays; "org.thingsboard.server.mqtt.attributes.updates.sql.*Test", "org.thingsboard.server.mqtt.attributes.request.sql.*Test", "org.thingsboard.server.mqtt.claim.sql.*Test", - "org.thingsboard.server.mqtt.provision.sql.*Test", + "org.thingsboard.server.mqtt.provision.sql.*Test" }) public class MqttSqlTestSuite { From df21ce8043c03cf6131556abeeceae72534d48c4 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 11 Nov 2020 19:02:01 +0200 Subject: [PATCH 336/463] Fix validate credentials method --- .../server/service/transport/DefaultTransportApiService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index c7cbea8200..5ceb9b3cd6 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -165,7 +165,7 @@ public class DefaultTransportApiService implements TransportApiService { private ListenableFuture validateCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) { DeviceCredentials credentials = null; - if (mqtt.getUserName() != null) { + if (!StringUtils.isEmpty(mqtt.getUserName())) { credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName()); if (credentials != null) { if (credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) { From 671dfa7668a5b6279080030552b7367b9c2a17f6 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 11 Nov 2020 19:53:45 +0200 Subject: [PATCH 337/463] Add validator to unique MQTT device topic filter --- ...e-profile-transport-configuration.component.html | 3 +++ ...ice-profile-transport-configuration.component.ts | 13 +++++++++++-- ui-ngx/src/assets/locale/locale.constant-en_US.json | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html index 00ff4760ab..5b3f3d8abf 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html @@ -63,6 +63,9 @@
+ + {{ 'device-profile.mqtt-device-topic-filters-unique' | translate }} +
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index 18dc1b2bf4..c36d685fec 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -51,7 +51,6 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control mqttTransportPayloadTypeTranslations = mqttTransportPayloadTypeTranslationMap; - mqttDeviceProfileTransportConfigurationFormGroup: FormGroup; private requiredValue: boolean; @@ -87,7 +86,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control deviceAttributesTopic: [null, [Validators.required, this.validationMQTTTopic()]], deviceTelemetryTopic: [null, [Validators.required, this.validationMQTTTopic()]], transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required] - }) + }, {validator: this.uniqueDeviceTopicValidator}) }); this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { this.updateModel(); @@ -147,4 +146,14 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control return null; }; } + + private uniqueDeviceTopicValidator(control: FormGroup): { [key: string]: boolean } | null { + if (control.value) { + const formValue = control.value as MqttDeviceProfileTransportConfiguration; + if (formValue.deviceAttributesTopic === formValue.deviceTelemetryTopic) { + return {unique: true}; + } + } + return null; + } } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index f0d426be66..76799b73fd 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -885,6 +885,7 @@ "no-device-profiles-found": "No device profiles found.", "create-new-device-profile": "Create a new one!", "mqtt-device-topic-filters": "MQTT device topic filters", + "mqtt-device-topic-filters-unique": "MQTT device topic filters need to be unique.", "mqtt-device-payload-type": "MQTT device payload", "mqtt-device-payload-type-json": "JSON", "mqtt-device-payload-type-proto": "Protobuf", From 0dd9450b92a04c230fb83c9b73fabfcfc2df76ee Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 12 Nov 2020 09:55:18 +0200 Subject: [PATCH 338/463] Fix for ApiStats security checks --- .../server/dao/sql/query/DefaultEntityQueryRepository.java | 5 ++++- .../thingsboard/server/dao/sql/query/EntityKeyMapping.java | 2 +- ui-ngx/src/app/core/http/entity.service.ts | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java index db61e6ab05..8edb1917ba 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java @@ -202,6 +202,9 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { " THEN (select additional_info from entity_view where id = entity_id)" + " END as additional_info"; + private static final String SELECT_API_USAGE_STATE = "(select aus.id, aus.created_time, aus.tenant_id, '13814000-1dd2-11b2-8080-808080808080'::uuid as customer_id, " + + "(select title from tenant where id = aus.tenant_id) as name from api_usage_state as aus)"; + static { entityTableMap.put(EntityType.ASSET, "asset"); entityTableMap.put(EntityType.DEVICE, "device"); @@ -210,7 +213,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { entityTableMap.put(EntityType.CUSTOMER, "customer"); entityTableMap.put(EntityType.USER, "tb_user"); entityTableMap.put(EntityType.TENANT, "tenant"); - entityTableMap.put(EntityType.API_USAGE_STATE, "api_usage_state"); + entityTableMap.put(EntityType.API_USAGE_STATE, SELECT_API_USAGE_STATE); } public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{ diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java index 7332de4b49..017306c47c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java @@ -80,7 +80,7 @@ public class EntityKeyMapping { public static final List labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, ADDITIONAL_INFO); public static final List contactBasedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, EMAIL, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO); - public static final Set apiUsageStateEntityFields = Collections.singleton(CREATED_TIME); + public static final Set apiUsageStateEntityFields = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME)); public static final Set commonEntityFieldsSet = new HashSet<>(commonEntityFields); public static final Set relationQueryEntityFieldsSet = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, FIRST_NAME, LAST_NAME, EMAIL, REGION, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO)); diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index acd89e3c1b..9263838ffa 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -630,6 +630,9 @@ export class EntityService { case EntityType.DASHBOARD: entityFieldKeys.push(entityFields.title.keyName); break; + case EntityType.API_USAGE_STATE: + entityFieldKeys.push(entityFields.name.keyName); + break; } return query ? entityFieldKeys.filter((entityField) => entityField.toLowerCase().indexOf(query) === 0) : entityFieldKeys; } From 590f3a0a80ad7918b829545a83b39498afddd7be Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 12 Nov 2020 10:24:08 +0200 Subject: [PATCH 339/463] Improve tenant creation from OAuth mapper --- .../auth/oauth2/AbstractOAuth2ClientMapper.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java index f76521ac48..ea6e7d4f19 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java @@ -35,14 +35,17 @@ import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.oauth2.OAuth2User; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.service.install.InstallScripts; +import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; @@ -76,6 +79,12 @@ public abstract class AbstractOAuth2ClientMapper { @Autowired private InstallScripts installScripts; + @Autowired + protected TbTenantProfileCache tenantProfileCache; + + @Autowired + protected TbClusterService tbClusterService; + private final Lock userCreationLock = new ReentrantLock(); protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2ClientRegistrationInfo clientRegistration) { @@ -162,6 +171,10 @@ public abstract class AbstractOAuth2ClientMapper { tenant.setTitle(tenantName); tenant = tenantService.saveTenant(tenant); installScripts.createDefaultRuleChains(tenant.getId()); + tenantProfileCache.evict(tenant.getId()); + tbClusterService.onTenantChange(tenant, null); + tbClusterService.onEntityStateChange(tenant.getId(), tenant.getId(), + ComponentLifecycleEvent.CREATED); } else { tenant = tenants.get(0); } From 31620008890eb6e8a474c63f8b09fd4e8c55adb9 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 12 Nov 2020 11:37:54 +0200 Subject: [PATCH 340/463] Use baseUrl from system security service for OAuth --- .../oauth2/Oauth2AuthenticationFailureHandler.java | 14 +++++++++++++- .../oauth2/Oauth2AuthenticationSuccessHandler.java | 10 ++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java index 984936874f..27a4962a0f 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java @@ -15,10 +15,15 @@ */ package org.thingsboard.server.service.security.auth.oauth2; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.service.security.system.SystemSecurityService; import org.thingsboard.server.utils.MiscUtils; import javax.servlet.ServletException; @@ -32,11 +37,18 @@ import java.nio.charset.StandardCharsets; @ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true") public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { + private final SystemSecurityService systemSecurityService; + + @Autowired + public Oauth2AuthenticationFailureHandler(final SystemSecurityService systemSecurityService) { + this.systemSecurityService = systemSecurityService; + } + @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { - String baseUrl = MiscUtils.constructBaseUrl(request); + String baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" + URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8.toString())); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index 8a65eadedc..2e2eeeee61 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -22,12 +22,16 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.token.JwtToken; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; +import org.thingsboard.server.service.security.system.SystemSecurityService; import org.thingsboard.server.utils.MiscUtils; import javax.servlet.http.HttpServletRequest; @@ -45,25 +49,27 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS private final OAuth2ClientMapperProvider oauth2ClientMapperProvider; private final OAuth2Service oAuth2Service; private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService; + private final SystemSecurityService systemSecurityService; @Autowired public Oauth2AuthenticationSuccessHandler(final JwtTokenFactory tokenFactory, final RefreshTokenRepository refreshTokenRepository, final OAuth2ClientMapperProvider oauth2ClientMapperProvider, final OAuth2Service oAuth2Service, - final OAuth2AuthorizedClientService oAuth2AuthorizedClientService) { + final OAuth2AuthorizedClientService oAuth2AuthorizedClientService, final SystemSecurityService systemSecurityService) { this.tokenFactory = tokenFactory; this.refreshTokenRepository = refreshTokenRepository; this.oauth2ClientMapperProvider = oauth2ClientMapperProvider; this.oAuth2Service = oAuth2Service; this.oAuth2AuthorizedClientService = oAuth2AuthorizedClientService; + this.systemSecurityService = systemSecurityService; } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { - String baseUrl = MiscUtils.constructBaseUrl(request); + String baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); try { OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; From 7a13083851cc9afe40702d37f9a28982fb92602f Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 12 Nov 2020 13:06:55 +0200 Subject: [PATCH 341/463] BaseUrl check --- .../security/system/DefaultSystemSecurityService.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java index 4219dbc609..9bd22c88f5 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java @@ -202,16 +202,19 @@ public class DefaultSystemSecurityService implements SystemSecurityService { @Override public String getBaseUrl(TenantId tenantId, CustomerId customerId, HttpServletRequest httpServletRequest) { - String baseUrl; + String baseUrl = null; AdminSettings generalSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "general"); JsonNode prohibitDifferentUrl = generalSettings.getJsonValue().get("prohibitDifferentUrl"); if (prohibitDifferentUrl != null && prohibitDifferentUrl.asBoolean()) { baseUrl = generalSettings.getJsonValue().get("baseUrl").asText(); - } else { + } + + if (StringUtils.isEmpty(baseUrl)) { baseUrl = MiscUtils.constructBaseUrl(httpServletRequest); } + return baseUrl; } From eece6418c36e99fa810f4527324519648e7fa24e Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 12 Nov 2020 13:22:32 +0200 Subject: [PATCH 342/463] updated versions --- application/pom.xml | 4 --- pom.xml | 30 +++++++++++----------- rule-engine/rule-engine-components/pom.xml | 5 ++++ tools/pom.xml | 1 - 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index 1730d7a5a5..1783d9f4c5 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -145,10 +145,6 @@ org.springframework.boot spring-boot-starter-websocket - - org.springframework.cloud - spring-cloud-starter-oauth2 - org.springframework.security spring-security-oauth2-client diff --git a/pom.xml b/pom.xml index 335e6c7eba..3495f83971 100755 --- a/pom.xml +++ b/pom.xml @@ -36,11 +36,10 @@ ${project.name} /var/log/${pkg.name} /usr/share/${pkg.name} - 2.2.6.RELEASE - 2.1.2.RELEASE - 5.2.6.RELEASE - 5.2.3.RELEASE - 2.2.4.RELEASE + 2.3.5.RELEASE + 5.3.1 + 5.4.1 + 2.4.1 3.1.0 0.7.0 2.2.0 @@ -51,15 +50,16 @@ 0.10 3.6.0 3.5.0.1 + 3.11.9 1.2.7 28.2-jre 2.6.1 3.4 2.5 1.4 - 2.10.2 - 2.10.2 - 2.10.2 + 2.11.3 + 2.11.3 + 2.11.3 2.2.6 1.0.2 2.6.2 @@ -72,7 +72,7 @@ 1.22.1 1.16.18 1.1.0 - 4.1.49.Final + 4.1.53.Final 1.5.0 4.8.0 2.19.1 @@ -96,7 +96,7 @@ 4.1.1 2.57 2.7.7 - 1.25 + 1.27 1.3.10 1.11.747 1.105.0 @@ -867,11 +867,6 @@ spring-boot-starter-security ${spring-boot.version} - - org.springframework.cloud - spring-cloud-starter-oauth2 - ${spring-oauth2.version} - org.springframework.security spring-security-oauth2-client @@ -1214,6 +1209,11 @@ ${cassandra-unit.version} test + + org.apache.cassandra + cassandra-all + ${cassandra-all.version} + junit junit diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index 00d8101e5a..5bb14db250 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -120,6 +120,11 @@ org.locationtech.jts jts-core + + com.sun.mail + javax.mail + provided + junit junit diff --git a/tools/pom.xml b/tools/pom.xml index 91bf319b0e..10bfa3cc5b 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -54,7 +54,6 @@ org.apache.cassandra cassandra-all - 3.11.6 com.datastax.cassandra From 72a0126e23cd325fe95022911a5dcf592f7b99e1 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 12 Nov 2020 17:07:07 +0200 Subject: [PATCH 343/463] AlarmState improvements --- .../java/org/thingsboard/rule/engine/profile/AlarmState.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index c59dbb75ca..535294d94c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -67,6 +67,7 @@ class AlarmState { initCurrentAlarm(ctx); lastMsgMetaData = msg.getMetaData(); lastMsgQueueName = msg.getQueueName(); + this.dataSnapshot = data; return createOrClearAlarms(ctx, data, update, AlarmRuleState::eval); } @@ -190,7 +191,7 @@ class AlarmState { } } - private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmRuleState ruleState) { + private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmRuleState ruleState) { AlarmSeverity severity = ruleState.getSeverity(); if (currentAlarm != null) { // TODO: In some extremely rare cases, we might miss the event of alarm clear (If one use in-mem queue and restarted the server) or (if one manipulated the rule chain). @@ -230,7 +231,7 @@ class AlarmState { } } - private JsonNode createDetails(AlarmRuleState ruleState) { + private JsonNode createDetails(AlarmRuleState ruleState) { ObjectNode details = JacksonUtil.OBJECT_MAPPER.createObjectNode(); String alarmDetails = ruleState.getAlarmRule().getAlarmDetails(); From 5850fa0cd92ebf8e1deec1bd3875dd3378bae5f8 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 12 Nov 2020 18:58:07 +0200 Subject: [PATCH 344/463] updated jedis version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3495f83971..00968c525a 100755 --- a/pom.xml +++ b/pom.xml @@ -40,7 +40,7 @@ 5.3.1 5.4.1 2.4.1 - 3.1.0 + 3.3.0 0.7.0 2.2.0 4.12 From cb41548b22bd11f64e3965cce5d9cec1108bacde Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 12 Nov 2020 19:32:45 +0200 Subject: [PATCH 345/463] Add cleanup of clear alarm state on non matching condition --- .../data/plugin/ComponentLifecycleEvent.java | 2 +- .../rule/engine/profile/AlarmState.java | 22 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleEvent.java b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleEvent.java index 1f242a0f88..87adc31713 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleEvent.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleEvent.java @@ -21,5 +21,5 @@ import java.io.Serializable; * @author Andrew Shvayka */ public enum ComponentLifecycleEvent implements Serializable { - CREATED, STARTED, ACTIVATED, SUSPENDED, UPDATED, STOPPED, DELETED, ADDED_TO_ALLOW_LIST, ADDED_TO_DENY_LIST + CREATED, STARTED, ACTIVATED, SUSPENDED, UPDATED, STOPPED, DELETED } \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index c59dbb75ca..390a0d22d6 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -90,8 +90,7 @@ class AlarmState { resultState = state; break; } else if (AlarmEvalResult.FALSE.equals(evalResult)) { - state.clear(); - stateUpdate |= state.checkUpdate(); + stateUpdate = clearAlarmState(stateUpdate, state); } } if (resultState != null) { @@ -99,6 +98,7 @@ class AlarmState { if (result != null) { pushMsg(ctx, result); } + stateUpdate = clearAlarmState(stateUpdate, clearState); } else if (currentAlarm != null && clearState != null) { if (!validateUpdate(update, clearState)) { log.debug("[{}] Update is not valid for current clear state", alarmDefinition.getId()); @@ -106,23 +106,26 @@ class AlarmState { } AlarmEvalResult evalResult = evalFunction.apply(clearState, data); if (AlarmEvalResult.TRUE.equals(evalResult)) { - clearState.clear(); - stateUpdate |= clearState.checkUpdate(); + stateUpdate = clearAlarmState(stateUpdate, clearState); for (AlarmRuleState state : createRulesSortedBySeverityDesc) { - state.clear(); - stateUpdate |= state.checkUpdate(); + stateUpdate = clearAlarmState(stateUpdate, state); } ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), JacksonUtil.OBJECT_MAPPER.createObjectNode(), System.currentTimeMillis()); pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm)); currentAlarm = null; } else if (AlarmEvalResult.FALSE.equals(evalResult)) { - clearState.clear(); - stateUpdate |= clearState.checkUpdate(); + stateUpdate = clearAlarmState(stateUpdate, clearState); } } return stateUpdate; } + public boolean clearAlarmState(boolean stateUpdate, AlarmRuleState state) { + state.clear(); + stateUpdate |= state.checkUpdate(); + return stateUpdate; + } + public boolean validateUpdate(SnapshotUpdate update, AlarmRuleState state) { if (update != null) { //Check that the update type and that keys match. @@ -273,8 +276,7 @@ class AlarmState { if (currentAlarm != null && currentAlarm.getId().equals(alarmNf.getId())) { currentAlarm = null; for (AlarmRuleState state : createRulesSortedBySeverityDesc) { - state.clear(); - updated |= state.checkUpdate(); + updated = clearAlarmState(updated, state); } } return updated; From 627c0577b08452308f925cecb3860e35292c649e Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 12 Nov 2020 19:44:49 +0200 Subject: [PATCH 346/463] Fix for ws reconnect issues --- .../service/subscription/DefaultTbLocalSubscriptionService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java index 89233d2f4f..4e62628851 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java @@ -108,7 +108,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer * Since number of subscriptions is usually much less then number of devices that are pushing data. */ subscriptionsBySessionId.values().forEach(map -> map.values() - .forEach(sub -> pushSubscriptionToManagerService(sub, false))); + .forEach(sub -> pushSubscriptionToManagerService(sub, true))); } } From 57c14f1c93274f1f4013f3308363f5307a4dde7e Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 13 Nov 2020 10:17:46 +0200 Subject: [PATCH 347/463] changed spring version to 5.2.10.RELEASE --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 00968c525a..03b815635d 100755 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ /var/log/${pkg.name} /usr/share/${pkg.name} 2.3.5.RELEASE - 5.3.1 + 5.2.10.RELEASE 5.4.1 2.4.1 3.3.0 From ba1b000adbfc9253fd1fc3e984480a2dd7a60fb8 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 13 Nov 2020 11:55:31 +0200 Subject: [PATCH 348/463] added maxCustomers, maxUsers, maxDashboards, maxRuleChains for TenantProfile --- .../DefaultTenantProfileConfiguration.java | 4 ++ .../server/dao/asset/BaseAssetService.java | 2 +- .../server/dao/customer/CustomerDao.java | 2 + .../dao/customer/CustomerServiceImpl.java | 16 +++++++ .../server/dao/dashboard/DashboardDao.java | 1 + .../dao/dashboard/DashboardServiceImpl.java | 20 ++++++++ .../server/dao/device/DeviceServiceImpl.java | 2 +- .../server/dao/rule/BaseRuleChainService.java | 20 ++++++++ .../server/dao/rule/RuleChainDao.java | 2 + .../dao/sql/customer/CustomerRepository.java | 1 + .../dao/sql/customer/JpaCustomerDao.java | 6 +++ .../sql/dashboard/DashboardRepository.java | 2 + .../dao/sql/dashboard/JpaDashboardDao.java | 6 +++ .../server/dao/sql/rule/JpaRuleChainDao.java | 5 ++ .../dao/sql/rule/RuleChainRepository.java | 1 + .../server/dao/sql/user/JpaUserDao.java | 5 ++ .../server/dao/sql/user/UserRepository.java | 1 + .../thingsboard/server/dao/user/UserDao.java | 3 +- .../server/dao/user/UserServiceImpl.java | 20 ++++++++ ...enant-profile-configuration.component.html | 48 +++++++++++++++++++ ...-tenant-profile-configuration.component.ts | 4 ++ .../assets/locale/locale.constant-en_US.json | 12 +++++ 22 files changed, 180 insertions(+), 3 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java index 250f9c75a5..10df66ece5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java @@ -24,6 +24,10 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura private long maxDevices; private long maxAssets; + private long maxCustomers; + private long maxUsers; + private long maxDashboards; + private long maxRuleChains; private String transportTenantMsgRateLimit; private String transportTenantTelemetryMsgRateLimit; diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java index 923baa1df4..97cdb31f5d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java @@ -332,7 +332,7 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ long maxAssets = profileConfiguration.getMaxAssets(); if (maxAssets > 0) { long currentAssetsCount = assetDao.countAssetsByTenantId(tenantId); - if (maxAssets >= currentAssetsCount) { + if (currentAssetsCount >= maxAssets) { throw new DataValidationException("Can't create assets more then " + maxAssets); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java index 11fa5cb7cd..5ecfd2fe62 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java @@ -54,5 +54,7 @@ public interface CustomerDao extends Dao { * @return the optional customer object */ Optional findCustomersByTenantIdAndTitle(UUID tenantId, String title); + + Long countCustomersByTenantId(TenantId tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java index 14d591e7e8..b649200e23 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Tenant; @@ -28,6 +29,7 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.DeviceService; @@ -38,6 +40,7 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.user.UserService; @@ -75,6 +78,10 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom @Autowired private DashboardService dashboardService; + @Autowired + @Lazy + private TbTenantProfileCache tenantProfileCache; + @Override public Customer findCustomerById(TenantId tenantId, CustomerId customerId) { log.trace("Executing findCustomerById [{}]", customerId); @@ -162,6 +169,15 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom @Override protected void validateCreate(TenantId tenantId, Customer customer) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + long maxCustomers = profileConfiguration.getMaxCustomers(); + if (maxCustomers > 0) { + long currentCustomersCount = customerDao.countCustomersByTenantId(tenantId); + if (currentCustomersCount >= maxCustomers) { + throw new DataValidationException("Can't create customers more then " + maxCustomers); + } + } customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent( c -> { throw new DataValidationException("Customer with such title already exists!"); diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java index 0429c6d469..0c013b4d33 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java @@ -32,4 +32,5 @@ public interface DashboardDao extends Dao { */ Dashboard save(TenantId tenantId, Dashboard dashboard); + Long countDashboardsByTenantId(TenantId tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java index 112f998cd4..f9ec7755d4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; @@ -31,12 +32,14 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantDao; import java.util.concurrent.ExecutionException; @@ -61,6 +64,10 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb @Autowired private CustomerDao customerDao; + @Autowired + @Lazy + private TbTenantProfileCache tenantProfileCache; + @Override public Dashboard findDashboardById(TenantId tenantId, DashboardId dashboardId) { log.trace("Executing findDashboardById [{}]", dashboardId); @@ -214,6 +221,19 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb private DataValidator dashboardValidator = new DataValidator() { + @Override + protected void validateCreate(TenantId tenantId, Dashboard data) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + long maxDashboards = profileConfiguration.getMaxDashboards(); + if (maxDashboards > 0) { + long currentDashboardsCount = dashboardDao.countDashboardsByTenantId(tenantId); + if (currentDashboardsCount >= maxDashboards) { + throw new DataValidationException("Can't create dashboards more then " + maxDashboards); + } + } + } + @Override protected void validateDataImpl(TenantId tenantId, Dashboard dashboard) { if (StringUtils.isEmpty(dashboard.getTitle())) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 24c597b4df..5728259ad7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -532,7 +532,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe long maxDevices = profileConfiguration.getMaxDevices(); if (maxDevices > 0) { long currentDevicesCount = deviceDao.countDevicesByTenantId(tenantId); - if (maxDevices >= currentDevicesCount) { + if (currentDevicesCount >= maxDevices) { throw new DataValidationException("Can't create devices more then " + maxDevices); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index 5997839119..aaa5040f6e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -24,6 +24,7 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.EntityType; @@ -44,11 +45,13 @@ import org.thingsboard.server.common.data.rule.RuleChainData; import org.thingsboard.server.common.data.rule.RuleChainImportResult; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantDao; import java.util.ArrayList; @@ -81,6 +84,10 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC @Autowired private TenantDao tenantDao; + @Autowired + @Lazy + private TbTenantProfileCache tenantProfileCache; + @Override public RuleChain saveRuleChain(RuleChain ruleChain) { ruleChainValidator.validate(ruleChain, RuleChain::getTenantId); @@ -580,6 +587,19 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC private DataValidator ruleChainValidator = new DataValidator() { + @Override + protected void validateCreate(TenantId tenantId, RuleChain data) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + long maxRuleChains = profileConfiguration.getMaxRuleChains(); + if (maxRuleChains > 0) { + long currentRuleChainsCount = ruleChainDao.countRuleChainsByTenantId(tenantId); + if (currentRuleChainsCount >= maxRuleChains) { + throw new DataValidationException("Can't create rule chains more then " + maxRuleChains); + } + } + } + @Override protected void validateDataImpl(TenantId tenantId, RuleChain ruleChain) { if (StringUtils.isEmpty(ruleChain.getName())) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java index c3214425fe..9f792200fc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.rule; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleChain; @@ -36,4 +37,5 @@ public interface RuleChainDao extends Dao { */ PageData findRuleChainsByTenantId(UUID tenantId, PageLink pageLink); + Long countRuleChainsByTenantId(TenantId tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java index bf2a845ec0..4b5a8e0791 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java @@ -37,4 +37,5 @@ public interface CustomerRepository extends PagingAndSortingRepository { + + Long countByTenantId(UUID tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java index 8d637f0cbe..a8cf3136ff 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java @@ -19,6 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.dashboard.DashboardDao; import org.thingsboard.server.dao.model.sql.DashboardEntity; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; @@ -43,4 +44,9 @@ public class JpaDashboardDao extends JpaAbstractSearchTextDao getCrudRepository() { return dashboardRepository; } + + @Override + public Long countDashboardsByTenantId(TenantId tenantId) { + return dashboardRepository.countByTenantId(tenantId.getId()); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java index 57d1545a94..051d13f805 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleChain; @@ -56,4 +57,8 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao imple DaoUtil.toPageable(pageLink))); } + + @Override + public Long countUsersByTenantId(TenantId tenantId) { + return userRepository.countByTenantId(tenantId.getId()); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java index 1d512a7944..ceaf09ec59 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java @@ -47,4 +47,5 @@ public interface UserRepository extends PagingAndSortingRepository { * @return the list of user entities */ PageData findCustomerUsers(UUID tenantId, UUID customerId, PageLink pageLink); - + + Long countUsersByTenantId(TenantId tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index 314684c3fe..acf9c7b1b0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -24,6 +24,7 @@ import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Tenant; @@ -36,6 +37,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; @@ -43,6 +45,7 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantDao; import java.util.HashMap; @@ -84,6 +87,10 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic @Autowired private CustomerDao customerDao; + @Autowired + @Lazy + private TbTenantProfileCache tenantProfileCache; + @Override public User findUserByEmail(TenantId tenantId, String email) { log.trace("Executing findUserByEmail [{}]", email); @@ -364,6 +371,19 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic private DataValidator userValidator = new DataValidator() { + @Override + protected void validateCreate(TenantId tenantId, User data) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + long maxUsers = profileConfiguration.getMaxUsers(); + if (maxUsers > 0) { + long currentUsersCount = userDao.countUsersByTenantId(tenantId); + if (currentUsersCount >= maxUsers) { + throw new DataValidationException("Can't create users more then " + maxUsers); + } + } + } + @Override protected void validateDataImpl(TenantId requestTenantId, User user) { if (StringUtils.isEmpty(user.getEmail())) { diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html index 12258ffd5f..ae9fd8ea9c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html @@ -40,6 +40,54 @@ {{ 'tenant-profile.maximum-assets-range' | translate}}
+ + tenant-profile.maximum-customers + + + {{ 'tenant-profile.maximum-customers-required' | translate}} + + + {{ 'tenant-profile.maximum-customers-range' | translate}} + + + + tenant-profile.maximum-users + + + {{ 'tenant-profile.maximum-users-required' | translate}} + + + {{ 'tenant-profile.maximum-users-range' | translate}} + + + + tenant-profile.maximum-dashboards + + + {{ 'tenant-profile.maximum-dashboards-required' | translate}} + + + {{ 'tenant-profile.maximum-dashboards-range' | translate}} + + + + tenant-profile.maximum-rule-chains + + + {{ 'tenant-profile.maximum-rule-chains-required' | translate}} + + + {{ 'tenant-profile.maximum-rule-chains-range' | translate}} + + tenant-profile.max-transport-messages Date: Fri, 13 Nov 2020 12:02:09 +0200 Subject: [PATCH 349/463] Add getEntityTimeseries method to Service Completion --- .../models/ace/service-completion.models.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/ui-ngx/src/app/shared/models/ace/service-completion.models.ts b/ui-ngx/src/app/shared/models/ace/service-completion.models.ts index f9b42f12f5..27a42680e5 100644 --- a/ui-ngx/src/app/shared/models/ace/service-completion.models.ts +++ b/ui-ngx/src/app/shared/models/ace/service-completion.models.ts @@ -56,6 +56,12 @@ export const customerHref = 'Attribute Data'; +export const timeseriesDataHref = 'Timeseries Data'; + +export const aggregationTypeHref = 'Aggregation Type'; + +export const dataSortOrderHref = 'Data Sort Order'; + export const userHref = 'User'; export const entityDataHref = 'Entity data'; @@ -1080,6 +1086,23 @@ export const serviceCompletions: TbEditorCompletions = { ], return: observableReturnTypeVariable('any') }, + getEntityTimeseries: { + description: 'Get entity timeseries', + meta: 'function', + args: [ + {name: 'entityId', type: entityIdHref, description: 'Id of the entity'}, + {name: 'keys', type: `Array<string>`, description: 'Array of the keys'}, + {name: 'startTs', type: 'number', description: 'Start time in milliseconds'}, + {name: 'endTs', type: 'number', description: 'End time in milliseconds'}, + {name: 'limit', type: 'number', description: 'Limit of values to receive for each key'}, + {name: 'agg', type: aggregationTypeHref, description: 'Aggregation type'}, + {name: 'interval', type: 'number', description: 'Aggregation interval'}, + {name: 'orderBy', type: dataSortOrderHref, description: 'Data order by time'}, + {name: 'useStrictDataTypes', type: 'boolean', description: 'If "false" all values will be returned as strings'}, + requestConfigArg + ], + return: observableReturnTypeVariable(timeseriesDataHref) + }, } }, entityService: { From 4eccf7f9165bf03b5905981c0a2bea60ed823b78 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 13 Nov 2020 13:00:33 +0200 Subject: [PATCH 350/463] fix validateCreate from UserServiceImpl --- .../server/dao/user/UserServiceImpl.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index acf9c7b1b0..0f13cdf3c6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -372,14 +372,16 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic private DataValidator userValidator = new DataValidator() { @Override - protected void validateCreate(TenantId tenantId, User data) { - DefaultTenantProfileConfiguration profileConfiguration = - (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); - long maxUsers = profileConfiguration.getMaxUsers(); - if (maxUsers > 0) { - long currentUsersCount = userDao.countUsersByTenantId(tenantId); - if (currentUsersCount >= maxUsers) { - throw new DataValidationException("Can't create users more then " + maxUsers); + protected void validateCreate(TenantId tenantId, User user) { + if (!user.getTenantId().getId().equals(ModelConstants.NULL_UUID)) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + long maxUsers = profileConfiguration.getMaxUsers(); + if (maxUsers > 0) { + long currentUsersCount = userDao.countUsersByTenantId(tenantId); + if (currentUsersCount >= maxUsers) { + throw new DataValidationException("Can't create users more then " + maxUsers); + } } } } From 9f2bae98158e63dd1d3a61a31a0723b05fc63b6a Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 13 Nov 2020 13:29:23 +0200 Subject: [PATCH 351/463] Improvements to API usage statistics collection --- .../transport/service/DefaultTransportService.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 4af723f3a0..9b2e3022a9 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -390,7 +390,8 @@ public class DefaultTransportService implements TransportService { metaData.putValue("deviceName", sessionInfo.getDeviceName()); metaData.putValue("deviceType", sessionInfo.getDeviceType()); metaData.putValue("notifyDevice", "false"); - sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, SessionMsgType.POST_ATTRIBUTES_REQUEST, new TransportTbQueueCallback(callback)); + sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, SessionMsgType.POST_ATTRIBUTES_REQUEST, + new TransportTbQueueCallback(new ApiStatsProxyCallback<>(tenantId, msg.getKvList().size(), callback))); } } @@ -399,7 +400,7 @@ public class DefaultTransportService implements TransportService { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) - .setGetAttributes(msg).build(), callback); + .setGetAttributes(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback)); } } @@ -409,7 +410,7 @@ public class DefaultTransportService implements TransportService { SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); sessionMetaData.setSubscribedToAttributes(!msg.getUnsubscribe()); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) - .setSubscribeToAttributes(msg).build(), callback); + .setSubscribeToAttributes(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback)); } } @@ -419,7 +420,7 @@ public class DefaultTransportService implements TransportService { SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); sessionMetaData.setSubscribedToRPC(!msg.getUnsubscribe()); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) - .setSubscribeToRPC(msg).build(), callback); + .setSubscribeToRPC(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback)); } } @@ -428,7 +429,7 @@ public class DefaultTransportService implements TransportService { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) - .setToDeviceRPCCallResponse(msg).build(), callback); + .setToDeviceRPCCallResponse(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback)); } } @@ -805,7 +806,7 @@ public class DefaultTransportService implements TransportService { @Override public void onFailure(Throwable t) { - callback.onError(t); + DefaultTransportService.this.transportCallbackExecutor.submit(() -> callback.onError(t)); } } From a1d9ce6e45a709104328cc4724590c872e18749c Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 13 Nov 2020 15:32:49 +0200 Subject: [PATCH 352/463] created validateNumberOfEntitiesPerTenant --- .../server/dao/TenantEntityDao.java | 8 ++++++ .../server/dao/asset/AssetDao.java | 5 ++-- .../server/dao/asset/BaseAssetService.java | 7 +---- .../server/dao/customer/CustomerDao.java | 5 ++-- .../dao/customer/CustomerServiceImpl.java | 9 +++---- .../server/dao/dashboard/DashboardDao.java | 5 ++-- .../dao/dashboard/DashboardServiceImpl.java | 8 ++---- .../server/dao/device/DeviceDao.java | 5 ++-- .../server/dao/device/DeviceServiceImpl.java | 7 +---- .../dao/entity/AbstractEntityService.java | 5 ++-- .../server/dao/rule/BaseRuleChainService.java | 7 +---- .../server/dao/rule/RuleChainDao.java | 6 ++--- .../server/dao/service/DataValidator.java | 15 +++++++++++ .../server/dao/sql/asset/JpaAssetDao.java | 3 +-- .../dao/sql/customer/JpaCustomerDao.java | 2 +- .../dao/sql/dashboard/JpaDashboardDao.java | 2 +- .../dao/sql/device/DeviceRepository.java | 26 +++++++++---------- .../server/dao/sql/device/JpaDeviceDao.java | 2 +- .../server/dao/sql/rule/JpaRuleChainDao.java | 2 +- .../server/dao/sql/user/JpaUserDao.java | 2 +- .../thingsboard/server/dao/user/UserDao.java | 9 +++---- .../server/dao/user/UserServiceImpl.java | 8 ++---- 22 files changed, 68 insertions(+), 80 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/TenantEntityDao.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/TenantEntityDao.java b/dao/src/main/java/org/thingsboard/server/dao/TenantEntityDao.java new file mode 100644 index 0000000000..0ac51a0dba --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/TenantEntityDao.java @@ -0,0 +1,8 @@ +package org.thingsboard.server.dao; + +import org.thingsboard.server.common.data.id.TenantId; + +public interface TenantEntityDao { + + Long countByTenantId(TenantId tenantId); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java index 1366df3ac2..ae8bf5706c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.dao.TenantEntityDao; import java.util.List; import java.util.Optional; @@ -32,7 +33,7 @@ import java.util.UUID; * The Interface AssetDao. * */ -public interface AssetDao extends Dao { +public interface AssetDao extends Dao, TenantEntityDao { /** * Find asset info by id. @@ -166,6 +167,4 @@ public interface AssetDao extends Dao { */ ListenableFuture> findTenantAssetTypesAsync(UUID tenantId); - Long countAssetsByTenantId(TenantId tenantId); - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java index 97cdb31f5d..29eaad31fb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java @@ -330,12 +330,7 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ DefaultTenantProfileConfiguration profileConfiguration = (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); long maxAssets = profileConfiguration.getMaxAssets(); - if (maxAssets > 0) { - long currentAssetsCount = assetDao.countAssetsByTenantId(tenantId); - if (currentAssetsCount >= maxAssets) { - throw new DataValidationException("Can't create assets more then " + maxAssets); - } - } + validateNumberOfEntitiesPerTenant(tenantId, assetDao, maxAssets, EntityType.ASSET); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java index 5ecfd2fe62..98622c8b83 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.dao.TenantEntityDao; import java.util.Optional; import java.util.UUID; @@ -27,7 +28,7 @@ import java.util.UUID; /** * The Interface CustomerDao. */ -public interface CustomerDao extends Dao { +public interface CustomerDao extends Dao, TenantEntityDao { /** * Save or update customer object @@ -55,6 +56,4 @@ public interface CustomerDao extends Dao { */ Optional findCustomersByTenantIdAndTitle(UUID tenantId, String title); - Long countCustomersByTenantId(TenantId tenantId); - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java index b649200e23..d97b4e9c34 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java @@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; @@ -172,12 +173,8 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom DefaultTenantProfileConfiguration profileConfiguration = (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); long maxCustomers = profileConfiguration.getMaxCustomers(); - if (maxCustomers > 0) { - long currentCustomersCount = customerDao.countCustomersByTenantId(tenantId); - if (currentCustomersCount >= maxCustomers) { - throw new DataValidationException("Can't create customers more then " + maxCustomers); - } - } + + validateNumberOfEntitiesPerTenant(tenantId, customerDao, maxCustomers, EntityType.CUSTOMER); customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent( c -> { throw new DataValidationException("Customer with such title already exists!"); diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java index 0c013b4d33..0beb89ef06 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java @@ -18,11 +18,12 @@ package org.thingsboard.server.dao.dashboard; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.dao.TenantEntityDao; /** * The Interface DashboardDao. */ -public interface DashboardDao extends Dao { +public interface DashboardDao extends Dao, TenantEntityDao { /** * Save or update dashboard object @@ -31,6 +32,4 @@ public interface DashboardDao extends Dao { * @return saved dashboard object */ Dashboard save(TenantId tenantId, Dashboard dashboard); - - Long countDashboardsByTenantId(TenantId tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java index f9ec7755d4..55bf31e321 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java @@ -24,6 +24,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DashboardInfo; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; @@ -226,12 +227,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb DefaultTenantProfileConfiguration profileConfiguration = (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); long maxDashboards = profileConfiguration.getMaxDashboards(); - if (maxDashboards > 0) { - long currentDashboardsCount = dashboardDao.countDashboardsByTenantId(tenantId); - if (currentDashboardsCount >= maxDashboards) { - throw new DataValidationException("Can't create dashboards more then " + maxDashboards); - } - } + validateNumberOfEntitiesPerTenant(tenantId, dashboardDao, maxDashboards, EntityType.DASHBOARD); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java index 2f321ff499..3e8f6445a2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.dao.TenantEntityDao; import java.util.List; import java.util.Optional; @@ -32,7 +33,7 @@ import java.util.UUID; * The Interface DeviceDao. * */ -public interface DeviceDao extends Dao { +public interface DeviceDao extends Dao, TenantEntityDao { /** * Find device info by id. @@ -203,8 +204,6 @@ public interface DeviceDao extends Dao { */ ListenableFuture findDeviceByTenantIdAndIdAsync(TenantId tenantId, UUID id); - Long countDevicesByTenantId(TenantId tenantId); - Long countDevicesByDeviceProfileId(TenantId tenantId, UUID deviceProfileId); /** diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 5728259ad7..a919f86dfe 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -530,12 +530,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe DefaultTenantProfileConfiguration profileConfiguration = (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); long maxDevices = profileConfiguration.getMaxDevices(); - if (maxDevices > 0) { - long currentDevicesCount = deviceDao.countDevicesByTenantId(tenantId); - if (currentDevicesCount >= maxDevices) { - throw new DataValidationException("Can't create devices more then " + maxDevices); - } - } + validateNumberOfEntitiesPerTenant(tenantId, deviceDao, maxDevices, EntityType.DEVICE); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java index a0f03b2a32..42d02addcd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java @@ -37,12 +37,11 @@ public abstract class AbstractEntityService { protected Optional extractConstraintViolationException(Exception t) { if (t instanceof ConstraintViolationException) { - return Optional.of ((ConstraintViolationException) t); + return Optional.of((ConstraintViolationException) t); } else if (t.getCause() instanceof ConstraintViolationException) { - return Optional.of ((ConstraintViolationException) (t.getCause())); + return Optional.of((ConstraintViolationException) (t.getCause())); } else { return Optional.empty(); } } - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index aaa5040f6e..263f01570e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -592,12 +592,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC DefaultTenantProfileConfiguration profileConfiguration = (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); long maxRuleChains = profileConfiguration.getMaxRuleChains(); - if (maxRuleChains > 0) { - long currentRuleChainsCount = ruleChainDao.countRuleChainsByTenantId(tenantId); - if (currentRuleChainsCount >= maxRuleChains) { - throw new DataValidationException("Can't create rule chains more then " + maxRuleChains); - } - } + validateNumberOfEntitiesPerTenant(tenantId, ruleChainDao, maxRuleChains, EntityType.RULE_CHAIN); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java index 9f792200fc..87ab9d26b6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java @@ -15,18 +15,18 @@ */ package org.thingsboard.server.dao.rule; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.dao.TenantEntityDao; import java.util.UUID; /** * Created by igor on 3/12/18. */ -public interface RuleChainDao extends Dao { +public interface RuleChainDao extends Dao, TenantEntityDao { /** * Find rule chains by tenantId and page link. @@ -36,6 +36,4 @@ public interface RuleChainDao extends Dao { * @return the list of rule chain objects */ PageData findRuleChainsByTenantId(UUID tenantId, PageLink pageLink); - - Long countRuleChainsByTenantId(TenantId tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java index d78654164f..0bd0e7b030 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java @@ -18,7 +18,9 @@ package org.thingsboard.server.dao.service; import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.TenantEntityDao; import org.thingsboard.server.dao.exception.DataValidationException; import java.util.HashSet; @@ -79,6 +81,19 @@ public abstract class DataValidator> { return emailMatcher.matches(); } + protected void validateNumberOfEntitiesPerTenant(TenantId tenantId, + TenantEntityDao tenantEntityDao, + long maxEntities, + EntityType entityType) { + if (maxEntities > 0) { + long currentEntitiesCount = tenantEntityDao.countByTenantId(tenantId); + if (currentEntitiesCount >= maxEntities) { + throw new DataValidationException(String.format("Can't create more then %d %ss!", + maxEntities, entityType.name().toLowerCase().replaceAll("_", " "))); + } + } + } + protected static void validateJsonStructure(JsonNode expectedNode, JsonNode actualNode) { Set expectedFields = new HashSet<>(); Iterator fieldsIterator = expectedNode.fieldNames(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java index 698183ea0a..d2a5167b43 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java @@ -178,8 +178,7 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao im } @Override - public Long countAssetsByTenantId(TenantId tenantId) { + public Long countByTenantId(TenantId tenantId) { return assetRepository.countByTenantId(tenantId.getId()); - } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java index 53c8b66038..fdfc12bf5a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java @@ -65,7 +65,7 @@ public class JpaCustomerDao extends JpaAbstractSearchTextDao findByTenantIdAndProfileId(@Param("tenantId") UUID tenantId, - @Param("profileId") UUID profileId, - @Param("searchText") String searchText, - Pageable pageable); + @Param("profileId") UUID profileId, + @Param("searchText") String searchText, + Pageable pageable); @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " + "FROM DeviceEntity d " + @@ -62,9 +62,9 @@ public interface DeviceRepository extends PagingAndSortingRepository findDeviceInfosByTenantIdAndCustomerId(@Param("tenantId") UUID tenantId, - @Param("customerId") UUID customerId, - @Param("searchText") String searchText, - Pageable pageable); + @Param("customerId") UUID customerId, + @Param("searchText") String searchText, + Pageable pageable); @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId") Page findByTenantId(@Param("tenantId") UUID tenantId, @@ -102,9 +102,9 @@ public interface DeviceRepository extends PagingAndSortingRepository findDeviceInfosByTenantIdAndType(@Param("tenantId") UUID tenantId, - @Param("type") String type, - @Param("textSearch") String textSearch, - Pageable pageable); + @Param("type") String type, + @Param("textSearch") String textSearch, + Pageable pageable); @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " + "FROM DeviceEntity d " + @@ -137,10 +137,10 @@ public interface DeviceRepository extends PagingAndSortingRepository findDeviceInfosByTenantIdAndCustomerIdAndType(@Param("tenantId") UUID tenantId, - @Param("customerId") UUID customerId, - @Param("type") String type, - @Param("textSearch") String textSearch, - Pageable pageable); + @Param("customerId") UUID customerId, + @Param("type") String type, + @Param("textSearch") String textSearch, + Pageable pageable); @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " + "FROM DeviceEntity d " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java index fceefcadf9..7ce59bda0d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java @@ -220,7 +220,7 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao } @Override - public Long countDevicesByTenantId(TenantId tenantId) { + public Long countByTenantId(TenantId tenantId) { return deviceRepository.countByTenantId(tenantId.getId()); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java index 051d13f805..3331f57e00 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java @@ -58,7 +58,7 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao imple } @Override - public Long countUsersByTenantId(TenantId tenantId) { + public Long countByTenantId(TenantId tenantId) { return userRepository.countByTenantId(tenantId.getId()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java index c2b9e7ab6c..4cffadd595 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java @@ -20,10 +20,11 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.dao.TenantEntityDao; import java.util.UUID; -public interface UserDao extends Dao { +public interface UserDao extends Dao, TenantEntityDao { /** * Save or update user object @@ -49,7 +50,7 @@ public interface UserDao extends Dao { * @return the list of user entities */ PageData findByTenantId(UUID tenantId, PageLink pageLink); - + /** * Find tenant admin users by tenantId and page link. * @@ -58,7 +59,7 @@ public interface UserDao extends Dao { * @return the list of user entities */ PageData findTenantAdmins(UUID tenantId, PageLink pageLink); - + /** * Find customer users by tenantId, customerId and page link. * @@ -68,6 +69,4 @@ public interface UserDao extends Dao { * @return the list of user entities */ PageData findCustomerUsers(UUID tenantId, UUID customerId, PageLink pageLink); - - Long countUsersByTenantId(TenantId tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index 0f13cdf3c6..a19ec57f48 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.CustomerId; @@ -377,12 +378,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic DefaultTenantProfileConfiguration profileConfiguration = (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); long maxUsers = profileConfiguration.getMaxUsers(); - if (maxUsers > 0) { - long currentUsersCount = userDao.countUsersByTenantId(tenantId); - if (currentUsersCount >= maxUsers) { - throw new DataValidationException("Can't create users more then " + maxUsers); - } - } + validateNumberOfEntitiesPerTenant(tenantId, userDao, maxUsers, EntityType.USER); } } From 2df72b4ec130f472697032340c32b3cd21b80fef Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 13 Nov 2020 15:51:28 +0200 Subject: [PATCH 353/463] license format --- .../thingsboard/server/dao/TenantEntityDao.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/dao/src/main/java/org/thingsboard/server/dao/TenantEntityDao.java b/dao/src/main/java/org/thingsboard/server/dao/TenantEntityDao.java index 0ac51a0dba..e47dd1e301 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/TenantEntityDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/TenantEntityDao.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.dao; import org.thingsboard.server.common.data.id.TenantId; From 4a1878130493753063101c8d03ec1b13ca015dbe Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 13 Nov 2020 16:19:40 +0200 Subject: [PATCH 354/463] added TransportPayloadTypeConfiguration, fix tests, ui --- .../server/controller/AbstractWebTest.java | 44 +- .../BaseDeviceProfileControllerTest.java | 380 +++++++++++++++- .../mqtt/AbstractMqttIntegrationTest.java | 45 +- ...AttributesRequestProtoIntegrationTest.java | 19 +- ...actMqttAttributesProtoIntegrationTest.java | 19 +- ...actMqttTimeseriesProtoIntegrationTest.java | 29 +- .../DeviceProfileTransportConfiguration.java | 1 + ...=> JsonTransportPayloadConfiguration.java} | 12 +- ...ttDeviceProfileTransportConfiguration.java | 23 +- ...qttTransportConfigurationDeserializer.java | 50 --- ...> ProtoTransportPayloadConfiguration.java} | 122 +----- .../TransportPayloadTypeConfiguration.java | 37 ++ .../mqtt/session/DeviceSessionCtx.java | 18 +- .../mqtt/util/MqttDynamicProtoSchemaTest.java | 406 ------------------ .../dao/device/DeviceProfileServiceImpl.java | 155 ++++++- ...ile-transport-configuration.component.html | 66 +-- ...ofile-transport-configuration.component.ts | 31 +- ui-ngx/src/app/shared/models/device.models.ts | 6 +- 18 files changed, 725 insertions(+), 738 deletions(-) rename common/data/src/main/java/org/thingsboard/server/common/data/device/profile/{MqttJsonDeviceProfileTransportConfiguration.java => JsonTransportPayloadConfiguration.java} (62%) delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java rename common/data/src/main/java/org/thingsboard/server/common/data/device/profile/{MqttProtoDeviceProfileTransportConfiguration.java => ProtoTransportPayloadConfiguration.java} (56%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/TransportPayloadTypeConfiguration.java delete mode 100644 common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttDynamicProtoSchemaTest.java diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index cd4a87d4de..c2bff23192 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.controller; -import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -33,12 +32,7 @@ import org.junit.Rule; import org.junit.rules.TestRule; import org.junit.rules.TestWatcher; import org.junit.runner.Description; -import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootContextLoader; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; @@ -46,10 +40,6 @@ import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.mock.http.MockHttpInputMessage; import org.springframework.mock.http.MockHttpOutputMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; @@ -58,7 +48,6 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilde import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.context.WebApplicationContext; -import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileType; @@ -68,11 +57,13 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; -import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.id.HasId; -import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.security.Authority; @@ -330,7 +321,7 @@ public abstract class AbstractWebTest { } } - protected DeviceProfile createDeviceProfile(String name) { + protected DeviceProfile createDeviceProfile(String name, DeviceProfileTransportConfiguration deviceProfileTransportConfiguration) { DeviceProfile deviceProfile = new DeviceProfile(); deviceProfile.setName(name); deviceProfile.setType(DeviceProfileType.DEFAULT); @@ -338,15 +329,34 @@ public abstract class AbstractWebTest { deviceProfile.setDescription(name + " Test"); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); - DefaultDeviceProfileTransportConfiguration transportConfiguration = new DefaultDeviceProfileTransportConfiguration(); deviceProfileData.setConfiguration(configuration); - deviceProfileData.setTransportConfiguration(transportConfiguration); + if (deviceProfileTransportConfiguration != null) { + deviceProfileData.setTransportConfiguration(deviceProfileTransportConfiguration); + } else { + deviceProfileData.setTransportConfiguration(new DefaultDeviceProfileTransportConfiguration()); + } deviceProfile.setProfileData(deviceProfileData); deviceProfile.setDefault(false); deviceProfile.setDefaultRuleChainId(null); return deviceProfile; } + protected MqttDeviceProfileTransportConfiguration createMqttDeviceProfileTransportConfiguration(TransportPayloadTypeConfiguration transportPayloadTypeConfiguration) { + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = new MqttDeviceProfileTransportConfiguration(); + mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(MqttTopics.DEVICE_TELEMETRY_TOPIC); + mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(MqttTopics.DEVICE_ATTRIBUTES_TOPIC); + mqttDeviceProfileTransportConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration); + return mqttDeviceProfileTransportConfiguration; + } + + protected ProtoTransportPayloadConfiguration createProtoTransportPayloadConfiguration(String deviceAttributesProtoSchema, String deviceTelemetryProtoSchema) { + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = new ProtoTransportPayloadConfiguration(); + protoTransportPayloadConfiguration.setDeviceAttributesProtoSchema(deviceAttributesProtoSchema); + protoTransportPayloadConfiguration.setDeviceTelemetryProtoSchema(deviceTelemetryProtoSchema); + return protoTransportPayloadConfiguration; + } + + protected ResultActions doGet(String urlTemplate, Object... urlVariables) throws Exception { MockHttpServletRequestBuilder getRequest = get(urlTemplate, urlVariables); setJwtToken(getRequest); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java index 3376c16573..841e899473 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java @@ -16,6 +16,12 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -28,7 +34,10 @@ import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; @@ -36,9 +45,13 @@ import org.thingsboard.server.common.data.security.Authority; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public abstract class BaseDeviceProfileControllerTest extends AbstractControllerTest { @@ -78,7 +91,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testSaveDeviceProfile() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Assert.assertNotNull(savedDeviceProfile); Assert.assertNotNull(savedDeviceProfile.getId()); @@ -96,7 +109,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testFindDeviceProfileById() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); Assert.assertNotNull(foundDeviceProfile); @@ -105,7 +118,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testFindDeviceProfileInfoById() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfileInfo foundDeviceProfileInfo = doGet("/api/deviceProfileInfo/"+savedDeviceProfile.getId().getId().toString(), DeviceProfileInfo.class); Assert.assertNotNull(foundDeviceProfileInfo); @@ -127,7 +140,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testSetDefaultDeviceProfile() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile 1"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile 1", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfile defaultDeviceProfile = doPost("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString()+"/default", null, DeviceProfile.class); Assert.assertNotNull(defaultDeviceProfile); @@ -147,19 +160,19 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testSaveDeviceProfileWithSameName() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk()); - DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile", null); doPost("/api/deviceProfile", deviceProfile2).andExpect(status().isBadRequest()) .andExpect(statusReason(containsString("Device profile with such name already exists"))); } @Test public void testSaveDeviceProfileWithSameProvisionDeviceKey() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); deviceProfile.setProvisionDeviceKey("testProvisionDeviceKey"); doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk()); - DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile 2"); + DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile 2", null); deviceProfile2.setProvisionDeviceKey("testProvisionDeviceKey"); doPost("/api/deviceProfile", deviceProfile2).andExpect(status().isBadRequest()) .andExpect(statusReason(containsString("Device profile with such provision device key already exists"))); @@ -168,7 +181,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Ignore @Test public void testChangeDeviceProfileTypeWithExistingDevices() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Device device = new Device(); device.setName("Test device"); @@ -183,7 +196,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testChangeDeviceProfileTransportTypeWithExistingDevices() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Device device = new Device(); device.setName("Test device"); @@ -197,7 +210,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testDeleteDeviceProfileWithExistingDevice() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Device device = new Device(); @@ -214,7 +227,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testDeleteDeviceProfile() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); doDelete("/api/deviceProfile/" + savedDeviceProfile.getId().getId().toString()) @@ -235,7 +248,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController deviceProfiles.addAll(pageData.getData()); for (int i=0;i<28;i++) { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i, null); deviceProfiles.add(doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class)); } @@ -280,7 +293,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController deviceProfiles.addAll(deviceProfilePageData.getData()); for (int i=0;i<28;i++) { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i, null); deviceProfiles.add(doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class)); } @@ -318,4 +331,341 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController Assert.assertEquals(1, pageData.getTotalElements()); } + @Test + public void testSaveProtoDeviceProfileWithInvalidProtoFile() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " required int32 parameter = 1;\n" + + "}", "[Transport Configuration] failed to parse attributes proto schema due to: Syntax error in :6:4: 'required' label forbidden in proto3 field declarations"); + } + + @Test + public void testSaveProtoDeviceProfileWithInvalidProtoSyntax() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto2\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " required int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid schema syntax: proto2 for attributes proto schema provided! Only proto3 allowed!"); + } + + @Test + public void testSaveProtoDeviceProfileOptionsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "option java_package = \"com.test.schemavalidation\";\n" + + "option java_multiple_files = true;\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Schema options don't support!"); + } + + @Test + public void testSaveProtoDeviceProfilePublicImportsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "import public \"oldschema.proto\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Schema public imports don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileImportsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "import \"oldschema.proto\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Schema imports don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileExtendDeclarationsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "extend google.protobuf.MethodOptions {\n" + + " MyMessage my_method_option = 50007;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Schema extend declarations don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileEnumOptionsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "enum testEnum {\n" + + " option allow_alias = true;\n" + + " DEFAULT = 0;\n" + + " STARTED = 1;\n" + + " RUNNING = 2;\n" + + "}\n" + + "\n" + + "message testMessage {\n" + + " int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Enum definitions options are not supported!"); + } + + @Test + public void testSaveProtoDeviceProfileNoOneMessageTypeExists() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "enum testEnum {\n" + + " DEFAULT = 0;\n" + + " STARTED = 1;\n" + + " RUNNING = 2;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! At least one Message definition should exists!"); + } + + @Test + public void testSaveProtoDeviceProfileMessageTypeOptionsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message testMessage {\n" + + " option allow_alias = true;\n" + + " int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Message definition options don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileMessageTypeExtensionsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message TestMessage {\n" + + " extensions 100 to 199;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Message definition extensions don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileMessageTypeReservedElementsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message Foo {\n" + + " reserved 2, 15, 9 to 11;\n" + + " reserved \"foo\", \"bar\";\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Message definition reserved elements don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileMessageTypeGroupsElementsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message TestMessage {\n" + + " repeated group Result = 1 {\n" + + " string url = 2;\n" + + " string title = 3;\n" + + " repeated string snippets = 4;\n" + + " }\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Message definition groups don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileOneOfsGroupsElementsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SampleMessage {\n" + + " oneof test_oneof {\n" + + " string name = 1;\n" + + " group Result = 2 {\n" + + " \tstring url = 3;\n" + + " \tstring title = 4;\n" + + " \trepeated string snippets = 5;\n" + + " }\n" + + " }" + + "}", "[Transport Configuration] invalid attributes proto schema provided! OneOf definition groups don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileWithMessageNestedTypes() throws Exception { + String schema = "syntax = \"proto3\";\n" + + "\n" + + "package testnested;\n" + + "\n" + + "message Outer {\n" + + " message MiddleAA {\n" + + " message Inner {\n" + + " int64 ival = 1;\n" + + " bool booly = 2;\n" + + " }\n" + + " Inner inner = 1;\n" + + " }\n" + + " message MiddleBB {\n" + + " message Inner {\n" + + " int32 ival = 1;\n" + + " bool booly = 2;\n" + + " }\n" + + " Inner inner = 1;\n" + + " }\n" + + " MiddleAA middleAA = 1;\n" + + " MiddleBB middleBB = 2;\n" + + "}"; + DynamicSchema dynamicSchema = getDynamicSchema(schema); + assertNotNull(dynamicSchema); + Set messageTypes = dynamicSchema.getMessageTypes(); + assertEquals(5, messageTypes.size()); + assertTrue(messageTypes.contains("testnested.Outer")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleAA")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleAA.Inner")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleBB")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleBB.Inner")); + + DynamicMessage.Builder middleAAInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); + Descriptors.Descriptor middleAAInnerMsgDescriptor = middleAAInnerMsgBuilder.getDescriptorForType(); + DynamicMessage middleAAInnerMsg = middleAAInnerMsgBuilder + .setField(middleAAInnerMsgDescriptor.findFieldByName("ival"), 1L) + .setField(middleAAInnerMsgDescriptor.findFieldByName("booly"), true) + .build(); + + DynamicMessage.Builder middleAAMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA"); + Descriptors.Descriptor middleAAMsgDescriptor = middleAAMsgBuilder.getDescriptorForType(); + DynamicMessage middleAAMsg = middleAAMsgBuilder + .setField(middleAAMsgDescriptor.findFieldByName("inner"), middleAAInnerMsg) + .build(); + + DynamicMessage.Builder middleBBInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); + Descriptors.Descriptor middleBBInnerMsgDescriptor = middleBBInnerMsgBuilder.getDescriptorForType(); + DynamicMessage middleBBInnerMsg = middleBBInnerMsgBuilder + .setField(middleBBInnerMsgDescriptor.findFieldByName("ival"), 0L) + .setField(middleBBInnerMsgDescriptor.findFieldByName("booly"), false) + .build(); + + DynamicMessage.Builder middleBBMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleBB"); + Descriptors.Descriptor middleBBMsgDescriptor = middleBBMsgBuilder.getDescriptorForType(); + DynamicMessage middleBBMsg = middleBBMsgBuilder + .setField(middleBBMsgDescriptor.findFieldByName("inner"), middleBBInnerMsg) + .build(); + + + DynamicMessage.Builder outerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer"); + Descriptors.Descriptor outerMsgBuilderDescriptor = outerMsgBuilder.getDescriptorForType(); + DynamicMessage outerMsg = outerMsgBuilder + .setField(outerMsgBuilderDescriptor.findFieldByName("middleAA"), middleAAMsg) + .setField(outerMsgBuilderDescriptor.findFieldByName("middleBB"), middleBBMsg) + .build(); + + assertEquals("{\n" + + " \"middleAA\": {\n" + + " \"inner\": {\n" + + " \"ival\": \"1\",\n" + + " \"booly\": true\n" + + " }\n" + + " },\n" + + " \"middleBB\": {\n" + + " \"inner\": {\n" + + " \"ival\": 0,\n" + + " \"booly\": false\n" + + " }\n" + + " }\n" + + "}", dynamicMsgToJson(outerMsgBuilderDescriptor, outerMsg.toByteArray())); + } + + @Test + public void testSaveProtoDeviceProfileWithMessageOneOfs() throws Exception { + String schema = "syntax = \"proto3\";\n" + + "\n" + + "package testoneofs;\n" + + "\n" + + "message SubMessage {\n" + + " repeated string name = 1;\n" + + "}\n" + + "\n" + + "message SampleMessage {\n" + + " oneof testOneOf {\n" + + " string name = 4;\n" + + " SubMessage subMessage = 9;\n" + + " }\n" + + "}"; + DynamicSchema dynamicSchema = getDynamicSchema(schema); + assertNotNull(dynamicSchema); + Set messageTypes = dynamicSchema.getMessageTypes(); + assertEquals(2, messageTypes.size()); + assertTrue(messageTypes.contains("testoneofs.SubMessage")); + assertTrue(messageTypes.contains("testoneofs.SampleMessage")); + + DynamicMessage.Builder sampleMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SampleMessage"); + Descriptors.Descriptor sampleMsgDescriptor = sampleMsgBuilder.getDescriptorForType(); + assertNotNull(sampleMsgDescriptor); + + List fields = sampleMsgDescriptor.getFields(); + assertEquals(2, fields.size()); + DynamicMessage sampleMsg = sampleMsgBuilder + .setField(sampleMsgDescriptor.findFieldByName("name"), "Bob") + .build(); + assertEquals("{\n" + " \"name\": \"Bob\"\n" + "}", dynamicMsgToJson(sampleMsgDescriptor, sampleMsg.toByteArray())); + + DynamicMessage.Builder subMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SubMessage"); + Descriptors.Descriptor subMsgDescriptor = subMsgBuilder.getDescriptorForType(); + DynamicMessage subMsg = subMsgBuilder + .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "Alice") + .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "John") + .build(); + + DynamicMessage sampleMsgWithOneOfSubMessage = sampleMsgBuilder.setField(sampleMsgDescriptor.findFieldByName("subMessage"), subMsg).build(); + assertEquals("{\n" + " \"subMessage\": {\n" + " \"name\": [\"Alice\", \"John\"]\n" + " }\n" + "}", + dynamicMsgToJson(sampleMsgDescriptor, sampleMsgWithOneOfSubMessage.toByteArray())); + } + + private DeviceProfile testSaveDeviceProfileWithProtoPayloadType(String schema) throws Exception { + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = this.createProtoTransportPayloadConfiguration(schema, schema); + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration); + DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); + Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfile.getName()); + return savedDeviceProfile; + } + + private void testSaveDeviceProfileWithInvalidProtoSchema(String schema, String errorMsg) throws Exception { + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = this.createProtoTransportPayloadConfiguration(schema, schema); + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration); + doPost("/api/deviceProfile", deviceProfile).andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(errorMsg))); + } + + private DynamicSchema getDynamicSchema(String schema) throws Exception { + DeviceProfile deviceProfile = testSaveDeviceProfileWithProtoPayloadType(schema); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttDeviceProfileTransportConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement protoFile = protoTransportPayloadConfiguration.getTransportProtoSchema(schema); + return protoTransportPayloadConfiguration.getDynamicSchema(protoFile, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + } + + private String dynamicMsgToJson(Descriptors.Descriptor descriptor, byte[] payload) throws InvalidProtocolBufferException { + DynamicMessage dynamicMessage = DynamicMessage.parseFrom(descriptor, payload); + return JsonFormat.printer().includingDefaultValueFields().print(dynamicMessage); + } + } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java index de84698dc0..715ffeade3 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java @@ -38,9 +38,10 @@ import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileCon import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttJsonDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.controller.AbstractControllerTest; @@ -94,7 +95,7 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest protected Device savedGateway; protected String gatewayAccessToken; - protected MqttDeviceProfileTransportConfiguration transportConfiguration; + protected DeviceProfile deviceProfile; protected void processBeforeTest (String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception { this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, DeviceProfileProvisionType.DISABLED, null, null); @@ -139,11 +140,11 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest if (payloadType != null) { DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic, telemetryProtoSchema, attributesProtoSchema, provisionType, provisionKey, provisionSecret); - DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class); - device.setType(savedDeviceProfile.getName()); - device.setDeviceProfileId(savedDeviceProfile.getId()); - gateway.setType(savedDeviceProfile.getName()); - gateway.setDeviceProfileId(savedDeviceProfile.getId()); + deviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class); + device.setType(deviceProfile.getName()); + device.setDeviceProfileId(deviceProfile.getId()); + gateway.setType(deviceProfile.getName()); + gateway.setDeviceProfileId(deviceProfile.getId()); } savedDevice = doPost("/api/device", device, Device.class); @@ -242,27 +243,30 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest deviceProfile.setDescription(transportPayloadType.name() + " Test"); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = new MqttDeviceProfileTransportConfiguration(); + if (!StringUtils.isEmpty(telemetryTopic)) { + mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(telemetryTopic); + } + if (!StringUtils.isEmpty(attributesTopic)) { + mqttDeviceProfileTransportConfiguration.setDeviceAttributesTopic(attributesTopic); + } + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration; if (TransportPayloadType.JSON.equals(transportPayloadType)) { - transportConfiguration = new MqttJsonDeviceProfileTransportConfiguration(); + transportPayloadTypeConfiguration = new JsonTransportPayloadConfiguration(); } else { - MqttProtoDeviceProfileTransportConfiguration protoTransportConfiguration = new MqttProtoDeviceProfileTransportConfiguration(); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = new ProtoTransportPayloadConfiguration(); if (StringUtils.isEmpty(telemetryProtoSchema)) { telemetryProtoSchema = DEVICE_TELEMETRY_PROTO_SCHEMA; } if (StringUtils.isEmpty(attributesProtoSchema)) { attributesProtoSchema = DEVICE_ATTRIBUTES_PROTO_SCHEMA; } - protoTransportConfiguration.setDeviceTelemetryProtoSchema(telemetryProtoSchema); - protoTransportConfiguration.setDeviceAttributesProtoSchema(attributesProtoSchema); - transportConfiguration = protoTransportConfiguration; + protoTransportPayloadConfiguration.setDeviceTelemetryProtoSchema(telemetryProtoSchema); + protoTransportPayloadConfiguration.setDeviceAttributesProtoSchema(attributesProtoSchema); + transportPayloadTypeConfiguration = protoTransportPayloadConfiguration; } - if (!StringUtils.isEmpty(telemetryTopic)) { - transportConfiguration.setDeviceTelemetryTopic(telemetryTopic); - } - if (!StringUtils.isEmpty(attributesTopic)) { - transportConfiguration.setDeviceAttributesTopic(attributesTopic); - } - deviceProfileData.setTransportConfiguration(transportConfiguration); + mqttDeviceProfileTransportConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration); + deviceProfileData.setTransportConfiguration(mqttDeviceProfileTransportConfiguration); DeviceProfileProvisionConfiguration provisionConfiguration; switch (provisionType) { case ALLOW_CREATE_NEW_DEVICES: @@ -274,6 +278,7 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest case DISABLED: default: provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(provisionSecret); + break; } deviceProfileData.setProvisionConfiguration(provisionConfiguration); deviceProfileData.setConfiguration(configuration); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java index 8a8be4d1cc..0ea53d2801 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java @@ -30,8 +30,11 @@ import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; @@ -82,11 +85,15 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends protected void postAttributesAndSubscribeToTopic(Device savedDevice, MqttAsyncClient client) throws Exception { doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); - assertTrue(transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration); - MqttProtoDeviceProfileTransportConfiguration configuration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; - ProtoFileElement transportProtoSchema = configuration.getTransportProtoSchema(ATTRIBUTES_SCHEMA_STR); - DynamicSchema telemetrySchema = configuration.getDynamicSchema(transportProtoSchema, "attributesSchema"); - DynamicMessage.Builder postAttributesBuilder = telemetrySchema.newMessageBuilder("PostAttributes"); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(ATTRIBUTES_SCHEMA_STR); + DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes"); Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); assertNotNull(postAttributesMsgDescriptor); DynamicMessage postAttributesMsg = postAttributesBuilder diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java index f7f9bdef7d..9cc7c66f55 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java @@ -23,7 +23,10 @@ import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; @@ -47,11 +50,15 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac public void testPushMqttAttributes() throws Exception { super.processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); - assertTrue(transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration); - MqttProtoDeviceProfileTransportConfiguration configuration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; - ProtoFileElement transportProtoSchema = configuration.getTransportProtoSchema(DEVICE_ATTRIBUTES_PROTO_SCHEMA); - DynamicSchema telemetrySchema = configuration.getDynamicSchema(transportProtoSchema, "attributesSchema"); - DynamicMessage.Builder postAttributesBuilder = telemetrySchema.newMessageBuilder("PostAttributes"); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement transportProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_ATTRIBUTES_PROTO_SCHEMA); + DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchemaFile, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes"); Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); assertNotNull(postAttributesMsgDescriptor); DynamicMessage postAttributesMsg = postAttributesBuilder diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java index d463dc1963..f8277b1934 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java @@ -26,8 +26,11 @@ import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; @@ -51,10 +54,14 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac public void testPushMqttTelemetry() throws Exception { super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); - assertTrue(transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration); - MqttProtoDeviceProfileTransportConfiguration configuration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; - ProtoFileElement transportProtoSchema = configuration.getTransportProtoSchema(DEVICE_TELEMETRY_PROTO_SCHEMA); - DynamicSchema telemetrySchema = configuration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_TELEMETRY_PROTO_SCHEMA); + DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); DynamicMessage.Builder postTelemetryBuilder = telemetrySchema.newMessageBuilder("PostTelemetry"); Descriptors.Descriptor postTelemetryMsgDescriptor = postTelemetryBuilder.getDescriptorForType(); assertNotNull(postTelemetryMsgDescriptor); @@ -88,10 +95,14 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac "}"; super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, DeviceProfileProvisionType.DISABLED, null, null); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); - assertTrue(transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration); - MqttProtoDeviceProfileTransportConfiguration configuration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; - ProtoFileElement transportProtoSchema = configuration.getTransportProtoSchema(schemaStr); - DynamicSchema telemetrySchema = configuration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(schemaStr); + DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); DynamicMessage.Builder valuesBuilder = telemetrySchema.newMessageBuilder("Values"); Descriptors.Descriptor valuesDescriptor = valuesBuilder.getDescriptorForType(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java index e685692895..fb337b24f4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M")}) public interface DeviceProfileTransportConfiguration { + @JsonIgnore DeviceTransportType getType(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/JsonTransportPayloadConfiguration.java similarity index 62% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/profile/JsonTransportPayloadConfiguration.java index 3f9144dc8f..f4fd93459b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttJsonDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/JsonTransportPayloadConfiguration.java @@ -15,24 +15,14 @@ */ package org.thingsboard.server.common.data.device.profile; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; -@Slf4j -@EqualsAndHashCode(callSuper = true) @Data -@JsonDeserialize(as = MqttJsonDeviceProfileTransportConfiguration.class) -public class MqttJsonDeviceProfileTransportConfiguration extends MqttDeviceProfileTransportConfiguration{ +public class JsonTransportPayloadConfiguration implements TransportPayloadTypeConfiguration { @Override public TransportPayloadType getTransportPayloadType() { return TransportPayloadType.JSON; } - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java index 66174e3d29..6a5c3c474c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java @@ -15,30 +15,15 @@ */ package org.thingsboard.server.common.data.device.profile; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Data; -import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.DeviceTransportType; -@JsonIgnoreProperties(ignoreUnknown = true) -@JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.PROPERTY, - property = "transportPayloadType") -@JsonSubTypes({ - @JsonSubTypes.Type(value = MqttJsonDeviceProfileTransportConfiguration.class, name = "JSON"), - @JsonSubTypes.Type(value = MqttProtoDeviceProfileTransportConfiguration.class, name = "PROTOBUF")}) -@JsonDeserialize(using = MqttTransportConfigurationDeserializer.class) @Data -public abstract class MqttDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { +public class MqttDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { - public abstract TransportPayloadType getTransportPayloadType(); - - protected String deviceTelemetryTopic = MqttTopics.DEVICE_TELEMETRY_TOPIC; - protected String deviceAttributesTopic = MqttTopics.DEVICE_ATTRIBUTES_TOPIC; + private String deviceTelemetryTopic = MqttTopics.DEVICE_TELEMETRY_TOPIC; + private String deviceAttributesTopic = MqttTopics.DEVICE_ATTRIBUTES_TOPIC; + private TransportPayloadTypeConfiguration transportPayloadTypeConfiguration; @Override public DeviceTransportType getType() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java deleted file mode 100644 index 4754792fb2..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTransportConfigurationDeserializer.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.common.data.device.profile; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.TransportPayloadType; - -import java.io.IOException; - -@Slf4j -public class MqttTransportConfigurationDeserializer extends StdDeserializer { - - public MqttTransportConfigurationDeserializer() { - super(MqttDeviceProfileTransportConfiguration.class); - } - - @Override - public MqttDeviceProfileTransportConfiguration deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { - try { - JsonNode jsonNode = jsonParser.readValueAsTree(); - if (jsonNode.hasNonNull("transportPayloadType") && jsonNode.get("transportPayloadType").asText().equals(TransportPayloadType.PROTOBUF.name())) { - return jsonParser.getCodec().treeToValue(jsonNode, MqttProtoDeviceProfileTransportConfiguration.class); - } else { - return jsonParser.getCodec().treeToValue(jsonNode, MqttJsonDeviceProfileTransportConfiguration.class); - } - } catch (IOException e) { - log.trace("Failed to deserialize JSON content into equivalent tree model during creating {}!", MqttDeviceProfileTransportConfiguration.class.getSimpleName(), e); - throw new RuntimeException("Failed to deserialize JSON content into equivalent tree model during creating " + MqttDeviceProfileTransportConfiguration.class.getSimpleName() + "!", e); - } - } - -} \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java similarity index 56% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java index 158acfaf2a..c5bdf28833 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttProtoDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java @@ -15,16 +15,11 @@ */ package org.thingsboard.server.common.data.device.profile; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.github.os72.protobuf.dynamic.DynamicSchema; import com.github.os72.protobuf.dynamic.EnumDefinition; import com.github.os72.protobuf.dynamic.MessageDefinition; import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; -import com.squareup.wire.Syntax; -import com.squareup.wire.schema.Field; import com.squareup.wire.schema.Location; import com.squareup.wire.schema.internal.parser.EnumConstantElement; import com.squareup.wire.schema.internal.parser.EnumElement; @@ -35,30 +30,22 @@ import com.squareup.wire.schema.internal.parser.ProtoFileElement; import com.squareup.wire.schema.internal.parser.ProtoParser; import com.squareup.wire.schema.internal.parser.TypeElement; import lombok.Data; -import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.TransportPayloadType; - import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @Slf4j -@EqualsAndHashCode(callSuper = true) @Data -@JsonDeserialize(as = MqttProtoDeviceProfileTransportConfiguration.class) -public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProfileTransportConfiguration { +public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeConfiguration { public static final Location LOCATION = new Location("", "", -1, -1); public static final String ATTRIBUTES_PROTO_SCHEMA = "attributes proto schema"; public static final String TELEMETRY_PROTO_SCHEMA = "telemetry proto schema"; - public static String invalidSchemaProvidedMessage(String schemaName) { - return "[Transport Configuration] invalid " + schemaName + " schema provided!"; - } - private String deviceTelemetryProtoSchema; private String deviceAttributesProtoSchema; @@ -67,23 +54,15 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf return TransportPayloadType.PROTOBUF; } - public void validateTransportProtoSchema(String schema, String schemaName) throws IllegalArgumentException { - ProtoParser schemaParser = new ProtoParser(LOCATION, schema.toCharArray()); - ProtoFileElement protoFileElement; - try { - protoFileElement = schemaParser.readProtoFile(); - } catch (Exception e) { - throw new IllegalArgumentException("[Transport Configuration] failed to parse " + schemaName + " due to: " + e.getMessage()); - } - checkProtoFileSyntax(schemaName, protoFileElement); - checkProtoFileCommonSettings(schemaName, protoFileElement.getOptions().isEmpty(), " Schema options don't support!"); - checkProtoFileCommonSettings(schemaName, protoFileElement.getPublicImports().isEmpty(), " Schema public imports don't support!"); - checkProtoFileCommonSettings(schemaName, protoFileElement.getImports().isEmpty(), " Schema imports don't support!"); - checkProtoFileCommonSettings(schemaName, protoFileElement.getExtendDeclarations().isEmpty(), " Schema extend declarations don't support!"); - checkTypeElements(schemaName, protoFileElement); + public Descriptors.Descriptor getTelemetryDynamicMessageDescriptor(String deviceTelemetryProtoSchema) { + return getDescriptor(deviceTelemetryProtoSchema, TELEMETRY_PROTO_SCHEMA); } - public Descriptors.Descriptor getDynamicMessageDescriptor(String protoSchema, String schemaName) { + public Descriptors.Descriptor getAttributesDynamicMessageDescriptor(String deviceAttributesProtoSchema) { + return getDescriptor(deviceAttributesProtoSchema, ATTRIBUTES_PROTO_SCHEMA); + } + + private Descriptors.Descriptor getDescriptor(String protoSchema, String schemaName) { try { ProtoFileElement protoFileElement = getTransportProtoSchema(protoSchema); DynamicSchema dynamicSchema = getDynamicSchema(protoFileElement, schemaName); @@ -125,91 +104,6 @@ public class MqttProtoDeviceProfileTransportConfiguration extends MqttDeviceProf } } - private void checkProtoFileSyntax(String schemaName, ProtoFileElement protoFileElement) { - if (protoFileElement.getSyntax() == null || !protoFileElement.getSyntax().equals(Syntax.PROTO_3)) { - throw new IllegalArgumentException("[Transport Configuration] invalid schema syntax: " + protoFileElement.getSyntax() + - " for: " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!"); - } - } - - private void checkProtoFileCommonSettings(String schemaName, boolean isEmptySettings, String invalidSettingsMessage) { - if (!isEmptySettings) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + invalidSettingsMessage); - } - } - - private void checkTypeElements(String schemaName, ProtoFileElement protoFileElement) { - List types = protoFileElement.getTypes(); - if (!types.isEmpty()) { - if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " At least one Message definition should exists!"); - } else { - checkEnumElements(schemaName, getEnumElements(types)); - checkMessageElements(schemaName, getMessageTypes(types)); - } - } else { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Type elements is empty!"); - } - } - - private void checkFieldElements(String schemaName, List fieldElements) { - if (!fieldElements.isEmpty()) { - boolean hasRequiredLabel = fieldElements.stream().anyMatch(fieldElement -> { - Field.Label label = fieldElement.getLabel(); - return label != null && label.equals(Field.Label.REQUIRED); - }); - if (hasRequiredLabel) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Required labels are not supported!"); - } - boolean hasDefaultValue = fieldElements.stream().anyMatch(fieldElement -> fieldElement.getDefaultValue() != null); - if (hasDefaultValue) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Default values are not supported!"); - } - } - } - - private void checkEnumElements(String schemaName, List enumTypes) { - if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getNestedTypes().isEmpty())) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Nested types in Enum definitions are not supported!"); - } - if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getOptions().isEmpty())) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Enum definitions options are not supported!"); - } - } - - private void checkMessageElements(String schemaName, List messageElementsList) { - if (!messageElementsList.isEmpty()) { - messageElementsList.forEach(messageElement -> { - checkProtoFileCommonSettings(schemaName, messageElement.getGroups().isEmpty(), - " Message definition groups don't support!"); - checkProtoFileCommonSettings(schemaName, messageElement.getOptions().isEmpty(), - " Message definition options don't support!"); - checkProtoFileCommonSettings(schemaName, messageElement.getExtensions().isEmpty(), - " Message definition extensions don't support!"); - checkProtoFileCommonSettings(schemaName, messageElement.getReserveds().isEmpty(), - " Message definition reserved elements don't support!"); - checkFieldElements(schemaName, messageElement.getFields()); - List oneOfs = messageElement.getOneOfs(); - if (!oneOfs.isEmpty()) { - oneOfs.forEach(oneOfElement -> { - checkProtoFileCommonSettings(schemaName, oneOfElement.getGroups().isEmpty(), - " OneOf definition groups don't support!"); - checkFieldElements(schemaName, oneOfElement.getFields()); - }); - } - List nestedTypes = messageElement.getNestedTypes(); - if (!nestedTypes.isEmpty()) { - List nestedEnumTypes = getEnumElements(nestedTypes); - if (!nestedEnumTypes.isEmpty()) { - checkEnumElements(schemaName, nestedEnumTypes); - } - List nestedMessageTypes = getMessageTypes(nestedTypes); - checkMessageElements(schemaName, nestedMessageTypes); - } - }); - } - } - public ProtoFileElement getTransportProtoSchema(String protoSchema) { return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/TransportPayloadTypeConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/TransportPayloadTypeConfiguration.java new file mode 100644 index 0000000000..5a6bbd5b25 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/TransportPayloadTypeConfiguration.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.profile; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.thingsboard.server.common.data.TransportPayloadType; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "transportPayloadType") +@JsonSubTypes({ + @JsonSubTypes.Type(value = JsonTransportPayloadConfiguration.class, name = "JSON"), + @JsonSubTypes.Type(value = ProtoTransportPayloadConfiguration.class, name = "PROTOBUF")}) +public interface TransportPayloadTypeConfiguration { + + @JsonIgnore + TransportPayloadType getTransportPayloadType(); + +} diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index 6ecea5a77c..fb4512a59b 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -25,7 +25,8 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.transport.mqtt.MqttTransportContext; import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.util.MqttTopicFilter; @@ -118,11 +119,12 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { if (transportConfiguration.getType().equals(DeviceTransportType.MQTT) && transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { MqttDeviceProfileTransportConfiguration mqttConfig = (MqttDeviceProfileTransportConfiguration) transportConfiguration; - payloadType = mqttConfig.getTransportPayloadType(); + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttConfig.getTransportPayloadTypeConfiguration(); + payloadType = transportPayloadTypeConfiguration.getTransportPayloadType(); telemetryTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceTelemetryTopic()); attributesTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesTopic()); - if (mqttConfig instanceof MqttProtoDeviceProfileTransportConfiguration) { - updateDynamicMessageDescriptors(mqttConfig); + if (transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration) { + updateDynamicMessageDescriptors(transportPayloadTypeConfiguration); } } else { telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); @@ -130,9 +132,9 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { } } - private void updateDynamicMessageDescriptors(MqttDeviceProfileTransportConfiguration mqttConfig) { - MqttProtoDeviceProfileTransportConfiguration protoMqttConfig = (MqttProtoDeviceProfileTransportConfiguration) mqttConfig; - telemetryDynamicMessageDescriptor = protoMqttConfig.getDynamicMessageDescriptor(protoMqttConfig.getDeviceTelemetryProtoSchema(), "telemetrySchema"); - attributesDynamicMessageDescriptor = protoMqttConfig.getDynamicMessageDescriptor(protoMqttConfig.getDeviceAttributesProtoSchema(), "attributesSchema"); + private void updateDynamicMessageDescriptors(TransportPayloadTypeConfiguration transportPayloadTypeConfiguration) { + ProtoTransportPayloadConfiguration protoTransportPayloadConfig = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + telemetryDynamicMessageDescriptor = protoTransportPayloadConfig.getTelemetryDynamicMessageDescriptor(protoTransportPayloadConfig.getDeviceTelemetryProtoSchema()); + attributesDynamicMessageDescriptor = protoTransportPayloadConfig.getAttributesDynamicMessageDescriptor(protoTransportPayloadConfig.getDeviceAttributesProtoSchema()); } } diff --git a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttDynamicProtoSchemaTest.java b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttDynamicProtoSchemaTest.java deleted file mode 100644 index 47726ff610..0000000000 --- a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttDynamicProtoSchemaTest.java +++ /dev/null @@ -1,406 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.mqtt.util; - -import com.github.os72.protobuf.dynamic.DynamicSchema; -import com.google.protobuf.Descriptors; -import com.google.protobuf.DynamicMessage; -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.util.JsonFormat; -import com.squareup.wire.schema.internal.parser.ProtoFileElement; -import com.squareup.wire.schema.internal.parser.ProtoParser; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; - -import java.util.List; -import java.util.Set; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration.LOCATION; - -@RunWith(MockitoJUnitRunner.class) -public class MqttDynamicProtoSchemaTest { - - private static final String PROTO_SCHEMA_WITH_NESTED_MSG_TYPES = "syntax = \"proto3\";\n" + - "\n" + - "package testnested;\n" + - "\n" + - "message Outer {\n" + - " message MiddleAA {\n" + - " message Inner {\n" + - " int64 ival = 1;\n" + - " bool booly = 2;\n" + - " }\n" + - " Inner inner = 1;\n" + - " }\n" + - " message MiddleBB {\n" + - " message Inner {\n" + - " int32 ival = 1;\n" + - " bool booly = 2;\n" + - " }\n" + - " Inner inner = 1;\n" + - " }\n" + - " MiddleAA middleAA = 1;\n" + - " MiddleBB middleBB = 2;\n" + - "}\n"; - - private static final String PROTO_SCHEMA_WITH_ONE_OFS = "syntax = \"proto3\";\n" + - "\n" + - "package testoneofs;\n" + - "\n" + - "message SubMessage {\n" + - " repeated string name = 1;\n" + - "}\n" + - "\n" + - "message SampleMessage {\n" + - " oneof testOneOf {\n" + - " string name = 4;\n" + - " SubMessage subMessage = 9;\n" + - " }\n" + - "}"; - - private static final String IVALID_PROTO_SCHEMA_REQUIRED_FIELD_EXISTS = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message SchemaValidationTest {\n" + - " required int32 parameter = 1;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_NOT_VALID_SYNTAX = "syntax = \"proto2\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message SchemaValidationTest {\n" + - " required int32 parameter = 1;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_OPTIONS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "option java_package = \"com.test.schemavalidation\";\n" + - "option java_multiple_files = true;\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message SchemaValidationTest {\n" + - " int32 parameter = 1;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_PUBLIC_IMPORTS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "import public \"oldschema.proto\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message SchemaValidationTest {\n" + - " int32 parameter = 1;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_IMPORTS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "import \"oldschema.proto\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message SchemaValidationTest {\n" + - " int32 parameter = 1;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_EXTEND_DECLARATION_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "extend google.protobuf.MethodOptions {\n" + - " MyMessage my_method_option = 50007;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_ENUM_OPTIONS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "enum testEnum {\n" + - " option allow_alias = true;\n" + - " DEFAULT = 0;\n" + - " STARTED = 1;\n" + - " RUNNING = 2;\n" + - "}\n" + - "\n" + - "message testMessage {\n" + - " int32 parameter = 1;\n" + - "}\n"; - - private static final String INVALID_PROTO_SCHEMA_NO_MESSAGE_TYPES_EXISTS = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "enum testEnum {\n" + - " DEFAULT = 0;\n" + - " STARTED = 1;\n" + - " RUNNING = 2;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_MESSAGE_OPTIONS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message testMessage {\n" + - " option allow_alias = true;\n" + - " int32 parameter = 1;\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_MESSAGE_EXTENSIONS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message TestMessage {\n" + - " extensions 100 to 199;\n" + - "}\n"; - - private static final String INVALID_PROTO_SCHEMA_MESSAGE_GROUPS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message TestMessage {\n" + - " repeated group Result = 1 {\n" + - " string url = 2;\n" + - " string title = 3;\n" + - " repeated string snippets = 4;\n" + - " }\n" + - "}\n"; - - private static final String INVALID_PROTO_SCHEMA_MESSAGE_RESERVED_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message Foo {\n" + - " reserved 2, 15, 9 to 11;\n" + - " reserved \"foo\", \"bar\";\n" + - "}"; - - private static final String INVALID_PROTO_SCHEMA_ONE_OFS_GROUPS_NOT_SUPPORTED = "syntax = \"proto3\";\n" + - "\n" + - "package schemavalidation;\n" + - "\n" + - "message SampleMessage {\n" + - " oneof test_oneof {\n" + - " string name = 1;\n" + - " group Result = 2 {\n" + - " \tstring url = 3;\n" + - " \tstring title = 4;\n" + - " \trepeated string snippets = 5;\n" + - " }\n" + - " }\n" + - "}"; - - private static final MqttProtoDeviceProfileTransportConfiguration mqttProtoDeviceProfileTransportConfiguration = new MqttProtoDeviceProfileTransportConfiguration(); - - private static void validateTransportProtoSchema(String schema, String schemaName) { - mqttProtoDeviceProfileTransportConfiguration.validateTransportProtoSchema(schema, schemaName); - } - - private static DynamicSchema getDynamicSchema(String schema, String schemaName) { - ProtoFileElement protoFileElement = getTransportProtoSchema(schema); - return mqttProtoDeviceProfileTransportConfiguration.getDynamicSchema(protoFileElement, schemaName); - } - - @Rule - public ExpectedException exceptionRule = ExpectedException.none(); - - @Test - public void testDynamicSchemaProtoFileValidation() { - processValidation("[Transport Configuration] failed to parse testParseToProtoFile due to: Syntax error in :6:4: 'required' label forbidden in proto3 field declarations", IVALID_PROTO_SCHEMA_REQUIRED_FIELD_EXISTS, "testParseToProtoFile"); - } - - @Test - public void testDynamicSchemaSyntaxValidation() { - processValidation("[Transport Configuration] invalid schema syntax: proto2 for: testSyntaxValidation provided! Only proto3 allowed!", INVALID_PROTO_SCHEMA_NOT_VALID_SYNTAX, "testSyntaxValidation"); - } - - @Test - public void testDynamicSchemaOptionsValidation() { - processValidation("[Transport Configuration] invalid testOptionsValidation schema provided! Schema options don't support!", INVALID_PROTO_SCHEMA_OPTIONS_NOT_SUPPORTED, "testOptionsValidation"); - } - - @Test - public void testDynamicSchemaPublicImportsValidation() { - processValidation("[Transport Configuration] invalid testPublicImportsValidation schema provided! Schema public imports don't support!", INVALID_PROTO_SCHEMA_PUBLIC_IMPORTS_NOT_SUPPORTED, "testPublicImportsValidation"); - } - - @Test - public void testDynamicSchemaImportsValidation() { - processValidation("[Transport Configuration] invalid testImportsValidation schema provided! Schema imports don't support!", INVALID_PROTO_SCHEMA_IMPORTS_NOT_SUPPORTED, "testImportsValidation"); - } - - @Test - public void testDynamicSchemaExtendDeclarationsValidation() { - processValidation("[Transport Configuration] invalid testExtendDeclarationsValidation schema provided! Schema extend declarations don't support!", INVALID_PROTO_SCHEMA_EXTEND_DECLARATION_NOT_SUPPORTED, "testExtendDeclarationsValidation"); - } - - @Test - public void testDynamicSchemaEnumOptionsValidation() { - processValidation("[Transport Configuration] invalid testEnumOptionsValidation schema provided! Enum definitions options are not supported!", INVALID_PROTO_SCHEMA_ENUM_OPTIONS_NOT_SUPPORTED, "testEnumOptionsValidation"); - } - - @Test - public void testDynamicSchemaNoOneMessageTypeExistsValidation() { - processValidation("[Transport Configuration] invalid noOneMessageTypeExists schema provided! At least one Message definition should exists!", INVALID_PROTO_SCHEMA_NO_MESSAGE_TYPES_EXISTS, "noOneMessageTypeExists"); - } - - @Test - public void testDynamicSchemaMessageTypeOptionsValidation() { - processValidation("[Transport Configuration] invalid messageTypeOptions schema provided! Message definition options don't support!", INVALID_PROTO_SCHEMA_MESSAGE_OPTIONS_NOT_SUPPORTED, "messageTypeOptions"); - } - - @Test - public void testDynamicSchemaMessageTypeExtensionsValidation() { - processValidation("[Transport Configuration] invalid messageTypeExtensions schema provided! Message definition extensions don't support!", INVALID_PROTO_SCHEMA_MESSAGE_EXTENSIONS_NOT_SUPPORTED, "messageTypeExtensions"); - } - - @Test - public void testDynamicSchemaMessageTypeReservedElementsValidation() { - processValidation("[Transport Configuration] invalid messageTypeReservedElements schema provided! Message definition reserved elements don't support!", INVALID_PROTO_SCHEMA_MESSAGE_RESERVED_NOT_SUPPORTED, "messageTypeReservedElements"); - } - - @Test - public void testDynamicSchemaMessageTypeGroupsElementsValidation() { - processValidation("[Transport Configuration] invalid messageTypeGroupsElements schema provided! Message definition groups don't support!", INVALID_PROTO_SCHEMA_MESSAGE_GROUPS_NOT_SUPPORTED, "messageTypeGroupsElements"); - } - - @Test - public void testDynamicSchemaOneOfsTypeGroupsElementsValidation() { - processValidation("[Transport Configuration] invalid oneOfsTypeGroupsElements schema provided! OneOf definition groups don't support!", INVALID_PROTO_SCHEMA_ONE_OFS_GROUPS_NOT_SUPPORTED, "oneOfsTypeGroupsElements"); - } - - private void processValidation(String expectedMessage, String schema, String schemaName) { - exceptionRule.expect(IllegalArgumentException.class); - exceptionRule.expectMessage(expectedMessage); - validateTransportProtoSchema(schema, schemaName); - } - - @Test - public void testDynamicSchemaCreationWithMessageNestedTypes() throws Exception { - String testNestedTypesProtoSchema = "testNestedTypesProtoSchema"; - validateTransportProtoSchema(PROTO_SCHEMA_WITH_NESTED_MSG_TYPES, testNestedTypesProtoSchema); - DynamicSchema dynamicSchema = getDynamicSchema(PROTO_SCHEMA_WITH_NESTED_MSG_TYPES, testNestedTypesProtoSchema); - assertNotNull(dynamicSchema); - Set messageTypes = dynamicSchema.getMessageTypes(); - assertEquals(5, messageTypes.size()); - assertTrue(messageTypes.contains("testnested.Outer")); - assertTrue(messageTypes.contains("testnested.Outer.MiddleAA")); - assertTrue(messageTypes.contains("testnested.Outer.MiddleAA.Inner")); - assertTrue(messageTypes.contains("testnested.Outer.MiddleBB")); - assertTrue(messageTypes.contains("testnested.Outer.MiddleBB.Inner")); - - DynamicMessage.Builder middleAAInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); - Descriptors.Descriptor middleAAInnerMsgDescriptor = middleAAInnerMsgBuilder.getDescriptorForType(); - DynamicMessage middleAAInnerMsg = middleAAInnerMsgBuilder - .setField(middleAAInnerMsgDescriptor.findFieldByName("ival"), 1L) - .setField(middleAAInnerMsgDescriptor.findFieldByName("booly"), true) - .build(); - - DynamicMessage.Builder middleAAMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA"); - Descriptors.Descriptor middleAAMsgDescriptor = middleAAMsgBuilder.getDescriptorForType(); - DynamicMessage middleAAMsg = middleAAMsgBuilder - .setField(middleAAMsgDescriptor.findFieldByName("inner"), middleAAInnerMsg) - .build(); - - DynamicMessage.Builder middleBBInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); - Descriptors.Descriptor middleBBInnerMsgDescriptor = middleBBInnerMsgBuilder.getDescriptorForType(); - DynamicMessage middleBBInnerMsg = middleBBInnerMsgBuilder - .setField(middleBBInnerMsgDescriptor.findFieldByName("ival"), 0L) - .setField(middleBBInnerMsgDescriptor.findFieldByName("booly"), false) - .build(); - - DynamicMessage.Builder middleBBMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleBB"); - Descriptors.Descriptor middleBBMsgDescriptor = middleBBMsgBuilder.getDescriptorForType(); - DynamicMessage middleBBMsg = middleBBMsgBuilder - .setField(middleBBMsgDescriptor.findFieldByName("inner"), middleBBInnerMsg) - .build(); - - - DynamicMessage.Builder outerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer"); - Descriptors.Descriptor outerMsgBuilderDescriptor = outerMsgBuilder.getDescriptorForType(); - DynamicMessage outerMsg = outerMsgBuilder - .setField(outerMsgBuilderDescriptor.findFieldByName("middleAA"), middleAAMsg) - .setField(outerMsgBuilderDescriptor.findFieldByName("middleBB"), middleBBMsg) - .build(); - - assertEquals("{\n" + - " \"middleAA\": {\n" + - " \"inner\": {\n" + - " \"ival\": \"1\",\n" + - " \"booly\": true\n" + - " }\n" + - " },\n" + - " \"middleBB\": {\n" + - " \"inner\": {\n" + - " \"ival\": 0,\n" + - " \"booly\": false\n" + - " }\n" + - " }\n" + - "}", dynamicMsgToJson(outerMsgBuilderDescriptor, outerMsg.toByteArray())); - } - - @Test - public void testDynamicSchemaCreationWithMessageOneOfs() throws Exception { - String testOneOfsProtoSchema = "testOneOfsProtoSchema"; - validateTransportProtoSchema(PROTO_SCHEMA_WITH_ONE_OFS, testOneOfsProtoSchema); - DynamicSchema dynamicSchema = getDynamicSchema(PROTO_SCHEMA_WITH_ONE_OFS, testOneOfsProtoSchema); - assertNotNull(dynamicSchema); - Set messageTypes = dynamicSchema.getMessageTypes(); - assertEquals(2, messageTypes.size()); - assertTrue(messageTypes.contains("testoneofs.SubMessage")); - assertTrue(messageTypes.contains("testoneofs.SampleMessage")); - - DynamicMessage.Builder sampleMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SampleMessage"); - Descriptors.Descriptor sampleMsgDescriptor = sampleMsgBuilder.getDescriptorForType(); - assertNotNull(sampleMsgDescriptor); - - List fields = sampleMsgDescriptor.getFields(); - assertEquals(2, fields.size()); - DynamicMessage sampleMsg = sampleMsgBuilder - .setField(sampleMsgDescriptor.findFieldByName("name"), "Bob") - .build(); - assertEquals("{\n" + " \"name\": \"Bob\"\n" + "}", dynamicMsgToJson(sampleMsgDescriptor, sampleMsg.toByteArray())); - - DynamicMessage.Builder subMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SubMessage"); - Descriptors.Descriptor subMsgDescriptor = subMsgBuilder.getDescriptorForType(); - DynamicMessage subMsg = subMsgBuilder - .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "Alice") - .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "John") - .build(); - - DynamicMessage sampleMsgWithOneOfSubMessage = sampleMsgBuilder.setField(sampleMsgDescriptor.findFieldByName("subMessage"), subMsg).build(); - assertEquals("{\n" + " \"subMessage\": {\n" + " \"name\": [\"Alice\", \"John\"]\n" + " }\n" + "}", - dynamicMsgToJson(sampleMsgDescriptor, sampleMsgWithOneOfSubMessage.toByteArray())); - - } - - private String dynamicMsgToJson(Descriptors.Descriptor descriptor, byte[] payload) throws InvalidProtocolBufferException { - DynamicMessage dynamicMessage = DynamicMessage.parseFrom(descriptor, payload); - return JsonFormat.printer().includingDefaultValueFields().print(dynamicMessage); - } - - private static ProtoFileElement getTransportProtoSchema(String protoSchema) { - return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index e5399fc8d4..d1a7820df4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -15,6 +15,16 @@ */ package org.thingsboard.server.dao.device; +import com.squareup.wire.Syntax; +import com.squareup.wire.schema.Field; +import com.squareup.wire.schema.Location; +import com.squareup.wire.schema.internal.parser.EnumElement; +import com.squareup.wire.schema.internal.parser.FieldElement; +import com.squareup.wire.schema.internal.parser.MessageElement; +import com.squareup.wire.schema.internal.parser.OneOfElement; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; +import com.squareup.wire.schema.internal.parser.ProtoParser; +import com.squareup.wire.schema.internal.parser.TypeElement; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.hibernate.exception.ConstraintViolationException; @@ -35,7 +45,8 @@ import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTra import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttProtoDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -49,6 +60,8 @@ import org.thingsboard.server.dao.tenant.TenantDao; import java.util.Arrays; import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; import static org.thingsboard.server.common.data.CacheConstants.DEVICE_PROFILE_CACHE; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -61,6 +74,14 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D private static final String INCORRECT_DEVICE_PROFILE_ID = "Incorrect deviceProfileId "; private static final String INCORRECT_DEVICE_PROFILE_NAME = "Incorrect deviceProfileName "; + private static final Location LOCATION = new Location("", "", -1, -1); + private static final String ATTRIBUTES_PROTO_SCHEMA = "attributes proto schema"; + private static final String TELEMETRY_PROTO_SCHEMA = "telemetry proto schema"; + + private static String invalidSchemaProvidedMessage(String schemaName) { + return "[Transport Configuration] invalid " + schemaName + " provided!"; + } + @Autowired private DeviceProfileDao deviceProfileDao; @@ -312,13 +333,16 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D } } else { DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); - if (transportConfiguration instanceof MqttProtoDeviceProfileTransportConfiguration) { - MqttProtoDeviceProfileTransportConfiguration protoTransportConfiguration = (MqttProtoDeviceProfileTransportConfiguration) transportConfiguration; - try { - protoTransportConfiguration.validateTransportProtoSchema(protoTransportConfiguration.getDeviceAttributesProtoSchema(), MqttProtoDeviceProfileTransportConfiguration.ATTRIBUTES_PROTO_SCHEMA); - protoTransportConfiguration.validateTransportProtoSchema(protoTransportConfiguration.getDeviceTelemetryProtoSchema(), MqttProtoDeviceProfileTransportConfiguration.TELEMETRY_PROTO_SCHEMA); - } catch (Exception exception) { - throw new DataValidationException(exception.getMessage()); + if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + if (mqttDeviceProfileTransportConfiguration.getTransportPayloadTypeConfiguration() instanceof ProtoTransportPayloadConfiguration) { + ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration = (ProtoTransportPayloadConfiguration) mqttDeviceProfileTransportConfiguration.getTransportPayloadTypeConfiguration(); + try { + validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceAttributesProtoSchema(), ATTRIBUTES_PROTO_SCHEMA); + validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceTelemetryProtoSchema(), TELEMETRY_PROTO_SCHEMA); + } catch (Exception exception) { + throw new DataValidationException(exception.getMessage()); + } } } } @@ -345,6 +369,121 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D } } } + + private void validateTransportProtoSchema(String schema, String schemaName) throws IllegalArgumentException { + ProtoParser schemaParser = new ProtoParser(LOCATION, schema.toCharArray()); + ProtoFileElement protoFileElement; + try { + protoFileElement = schemaParser.readProtoFile(); + } catch (Exception e) { + throw new IllegalArgumentException("[Transport Configuration] failed to parse " + schemaName + " due to: " + e.getMessage()); + } + checkProtoFileSyntax(schemaName, protoFileElement); + checkProtoFileCommonSettings(schemaName, protoFileElement.getOptions().isEmpty(), " Schema options don't support!"); + checkProtoFileCommonSettings(schemaName, protoFileElement.getPublicImports().isEmpty(), " Schema public imports don't support!"); + checkProtoFileCommonSettings(schemaName, protoFileElement.getImports().isEmpty(), " Schema imports don't support!"); + checkProtoFileCommonSettings(schemaName, protoFileElement.getExtendDeclarations().isEmpty(), " Schema extend declarations don't support!"); + checkTypeElements(schemaName, protoFileElement); + } + + private void checkProtoFileSyntax(String schemaName, ProtoFileElement protoFileElement) { + if (protoFileElement.getSyntax() == null || !protoFileElement.getSyntax().equals(Syntax.PROTO_3)) { + throw new IllegalArgumentException("[Transport Configuration] invalid schema syntax: " + protoFileElement.getSyntax() + + " for " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!"); + } + } + private void checkProtoFileCommonSettings(String schemaName, boolean isEmptySettings, String invalidSettingsMessage) { + if (!isEmptySettings) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + invalidSettingsMessage); + } + } + + private void checkTypeElements(String schemaName, ProtoFileElement protoFileElement) { + List types = protoFileElement.getTypes(); + if (!types.isEmpty()) { + if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " At least one Message definition should exists!"); + } else { + checkEnumElements(schemaName, getEnumElements(types)); + checkMessageElements(schemaName, getMessageTypes(types)); + } + } else { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Type elements is empty!"); + } + } + + private void checkFieldElements(String schemaName, List fieldElements) { + if (!fieldElements.isEmpty()) { + boolean hasRequiredLabel = fieldElements.stream().anyMatch(fieldElement -> { + Field.Label label = fieldElement.getLabel(); + return label != null && label.equals(Field.Label.REQUIRED); + }); + if (hasRequiredLabel) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Required labels are not supported!"); + } + boolean hasDefaultValue = fieldElements.stream().anyMatch(fieldElement -> fieldElement.getDefaultValue() != null); + if (hasDefaultValue) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Default values are not supported!"); + } + } + } + + private void checkEnumElements(String schemaName, List enumTypes) { + if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getNestedTypes().isEmpty())) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Nested types in Enum definitions are not supported!"); + } + if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getOptions().isEmpty())) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Enum definitions options are not supported!"); + } + } + + private void checkMessageElements(String schemaName, List messageElementsList) { + if (!messageElementsList.isEmpty()) { + messageElementsList.forEach(messageElement -> { + checkProtoFileCommonSettings(schemaName, messageElement.getGroups().isEmpty(), + " Message definition groups don't support!"); + checkProtoFileCommonSettings(schemaName, messageElement.getOptions().isEmpty(), + " Message definition options don't support!"); + checkProtoFileCommonSettings(schemaName, messageElement.getExtensions().isEmpty(), + " Message definition extensions don't support!"); + checkProtoFileCommonSettings(schemaName, messageElement.getReserveds().isEmpty(), + " Message definition reserved elements don't support!"); + checkFieldElements(schemaName, messageElement.getFields()); + List oneOfs = messageElement.getOneOfs(); + if (!oneOfs.isEmpty()) { + oneOfs.forEach(oneOfElement -> { + checkProtoFileCommonSettings(schemaName, oneOfElement.getGroups().isEmpty(), + " OneOf definition groups don't support!"); + checkFieldElements(schemaName, oneOfElement.getFields()); + }); + } + List nestedTypes = messageElement.getNestedTypes(); + if (!nestedTypes.isEmpty()) { + List nestedEnumTypes = getEnumElements(nestedTypes); + if (!nestedEnumTypes.isEmpty()) { + checkEnumElements(schemaName, nestedEnumTypes); + } + List nestedMessageTypes = getMessageTypes(nestedTypes); + checkMessageElements(schemaName, nestedMessageTypes); + } + }); + } + } + + private List getMessageTypes(List types) { + return types.stream() + .filter(typeElement -> typeElement instanceof MessageElement) + .map(typeElement -> (MessageElement) typeElement) + .collect(Collectors.toList()); + } + + private List getEnumElements(List types) { + return types.stream() + .filter(typeElement -> typeElement instanceof EnumElement) + .map(typeElement -> (EnumElement) typeElement) + .collect(Collectors.toList()); + } + }; private PaginatedRemover tenantDeviceProfilesRemover = diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html index c83066bbae..cedf147bc1 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html @@ -57,40 +57,42 @@
-
- device-profile.mqtt-device-payload-type -
- - - - {{mqttTransportPayloadTypeTranslations.get(type) | translate}} - - - - {{ 'device-profile.mqtt-payload-type-required' | translate }} - - -
- - device-profile.telemetry-proto-schema - - - {{ 'device-profile.telemetry-proto-schema-required' | translate}} - - - - device-profile.attributes-proto-schema - - - {{ 'device-profile.attributes-proto-schema-required' | translate}} +
+
+ device-profile.mqtt-device-payload-type +
+ + + + {{mqttTransportPayloadTypeTranslations.get(type) | translate}} + + + + {{ 'device-profile.mqtt-payload-type-required' | translate }} +
+ + device-profile.telemetry-proto-schema + + + {{ 'device-profile.telemetry-proto-schema-required' | translate}} + + + + device-profile.attributes-proto-schema + + + {{ 'device-profile.attributes-proto-schema-required' | translate}} + + +
-
-
+ + diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index 5569a0b530..d16ad840f1 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -87,13 +87,14 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control configuration: this.fb.group({ deviceAttributesTopic: [null, [Validators.required, this.validationMQTTTopic()]], deviceTelemetryTopic: [null, [Validators.required, this.validationMQTTTopic()]], - transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required] + transportPayloadTypeConfiguration: this.fb.group({ + transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required] + }) }) }); - let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; - configurationFormGroup.get('transportPayloadType').valueChanges.subscribe(payloadType => { - this.updateTransportPayloadBasedControls(payloadType, configurationFormGroup); - this.mqttDeviceProfileTransportConfigurationFormGroup.updateValueAndValidity(); + this.mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration.transportPayloadType').valueChanges.subscribe(payloadType => { + this.updateTransportPayloadBasedControls(payloadType); + this.mqttDeviceProfileTransportConfigurationFormGroup.updateValueAndValidity(); }); this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { this.updateModel(); @@ -109,15 +110,14 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control } } - protoPayloadType(): boolean { - let configuration = this.mqttDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration; - return configuration.transportPayloadType === MqttTransportPayloadType.PROTOBUF; + get protoPayloadType(): boolean { + let transportPayloadType = this.mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration.transportPayloadType').value; + return transportPayloadType === MqttTransportPayloadType.PROTOBUF; } writeValue(value: MqttDeviceProfileTransportConfiguration | null): void { if (isDefinedAndNotNull(value)) { - let configurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.controls.configuration as FormGroup; - this.updateTransportPayloadBasedControls(value.transportPayloadType, configurationFormGroup); + this.updateTransportPayloadBasedControls(value.transportPayloadTypeConfiguration.transportPayloadType); this.mqttDeviceProfileTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); } } @@ -131,13 +131,14 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control this.propagateChange(configuration); } - private updateTransportPayloadBasedControls(type: MqttTransportPayloadType, configurationFormGroup: FormGroup) { + private updateTransportPayloadBasedControls(type: MqttTransportPayloadType) { + const transportPayloadTypeConfigurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration') as FormGroup; if (type === MqttTransportPayloadType.PROTOBUF) { - configurationFormGroup.registerControl('deviceTelemetryProtoSchema', this.fb.control(null, Validators.required)); - configurationFormGroup.registerControl('deviceAttributesProtoSchema', this.fb.control(null, Validators.required)); + transportPayloadTypeConfigurationFormGroup.registerControl('deviceTelemetryProtoSchema', this.fb.control(null, Validators.required)); + transportPayloadTypeConfigurationFormGroup.registerControl('deviceAttributesProtoSchema', this.fb.control(null, Validators.required)); } else { - configurationFormGroup.removeControl('deviceTelemetryProtoSchema'); - configurationFormGroup.removeControl('deviceAttributesProtoSchema'); + transportPayloadTypeConfigurationFormGroup.removeControl('deviceTelemetryProtoSchema'); + transportPayloadTypeConfigurationFormGroup.removeControl('deviceAttributesProtoSchema'); } } diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 4ffe4af922..ffeae2f56d 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -148,7 +148,9 @@ export interface DefaultDeviceProfileTransportConfiguration { export interface MqttDeviceProfileTransportConfiguration { deviceTelemetryTopic?: string; deviceAttributesTopic?: string; - transportPayloadType?: MqttTransportPayloadType; + transportPayloadTypeConfiguration?: { + transportPayloadType?: MqttTransportPayloadType; + }; [key: string]: any; } @@ -208,7 +210,7 @@ export function createDeviceProfileTransportConfiguration(type: DeviceTransportT const mqttTransportConfiguration: MqttDeviceProfileTransportConfiguration = { deviceTelemetryTopic: 'v1/devices/me/telemetry', deviceAttributesTopic: 'v1/devices/me/attributes', - transportPayloadType: MqttTransportPayloadType.JSON + transportPayloadTypeConfiguration: {transportPayloadType: MqttTransportPayloadType.JSON} }; transportConfiguration = {...mqttTransportConfiguration, type: DeviceTransportType.MQTT}; break; From 4b4e2b702e7edd93b7d7bf3fba1dec47dc5f53a8 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 13 Nov 2020 16:21:14 +0200 Subject: [PATCH 355/463] DefaultTenantProfileConfiguration improvements --- ui-ngx/src/app/shared/models/tenant.model.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ui-ngx/src/app/shared/models/tenant.model.ts b/ui-ngx/src/app/shared/models/tenant.model.ts index ad946efdb7..ce22c27fac 100644 --- a/ui-ngx/src/app/shared/models/tenant.model.ts +++ b/ui-ngx/src/app/shared/models/tenant.model.ts @@ -26,6 +26,10 @@ export enum TenantProfileType { export interface DefaultTenantProfileConfiguration { maxDevices: number; maxAssets: number; + maxCustomers: number; + maxUsers: number; + maxDashboards: number; + maxRuleChains: number; transportTenantMsgRateLimit?: string; transportTenantTelemetryMsgRateLimit?: string; @@ -56,6 +60,10 @@ export function createTenantProfileConfiguration(type: TenantProfileType): Tenan const defaultConfiguration: DefaultTenantProfileConfiguration = { maxDevices: 0, maxAssets: 0, + maxCustomers: 0, + maxUsers: 0, + maxDashboards: 0, + maxRuleChains: 0, maxTransportMessages: 0, maxTransportDataPoints: 0, maxREExecutions: 0, From 34c8294e9d11c2d26f9b92a788deb3a25e8715fa Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 13 Nov 2020 20:28:55 +0200 Subject: [PATCH 356/463] Fixed fit bounds marker is disabled covered all markers --- .../modules/home/components/widget/lib/maps/leaflet-map.ts | 3 ++- .../app/modules/home/components/widget/lib/maps/map-models.ts | 2 +- .../home/components/widget/lib/maps/providers/google-map.ts | 4 ++-- .../home/components/widget/lib/maps/providers/here-map.ts | 4 ++-- .../components/widget/lib/maps/providers/openstreet-map.ts | 4 ++-- .../home/components/widget/lib/maps/providers/tencent-map.ts | 4 ++-- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts index 0f1f08799c..6b903101ad 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts @@ -487,7 +487,8 @@ export default abstract class LeafletMap { } const mapBounds = this.map.getBounds(); - if (bounds.isValid() && (!this.bounds || !this.bounds.isValid() || !this.bounds.equals(bounds) && !mapBounds.contains(bounds))) { + if (bounds.isValid() && (!this.bounds || !this.bounds.isValid() || !this.bounds.equals(bounds) + && this.options.fitMapBounds ? !mapBounds.contains(bounds) : false)) { this.bounds = bounds; this.fitBounds(bounds); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts index 63fe097b98..ed238dcfe2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts @@ -20,6 +20,7 @@ import { EntityType } from '@shared/models/entity-type.models'; import tinycolor from 'tinycolor2'; export const DEFAULT_MAP_PAGE_SIZE = 16384; +export const DEFAULT_ZOOM_LEVEL = 8; export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string; export type MarkerImageFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string; @@ -229,7 +230,6 @@ export const defaultSettings: any = { strokeWeight: 2, strokeOpacity: 1.0, initCallback: () => { }, - defaultZoomLevel: 8, disableScrollZooming: false, minZoomLevel: 16, credentials: '', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts index 94fa1a09ac..20d28afbd1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts @@ -17,7 +17,7 @@ import L from 'leaflet'; import LeafletMap from '../leaflet-map'; -import { UnitedMapSettings } from '../map-models'; +import { DEFAULT_ZOOM_LEVEL, UnitedMapSettings } from '../map-models'; import 'leaflet.gridlayer.googlemutant'; import { ResourcesService } from '@core/services/resources.service'; import { WidgetContext } from '@home/models/widget-component.models'; @@ -39,7 +39,7 @@ export class GoogleMap extends LeafletMap { const map = L.map($container, { attributionControl: false, editable: !!options.editablePolygon - }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); + }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL); (L.gridLayer as any).googleMutant({ type: options?.gmDefaultMapType || 'roadmap' }).addTo(map); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts index a6418ee557..7a0d12a03e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts @@ -16,7 +16,7 @@ import L from 'leaflet'; import LeafletMap from '../leaflet-map'; -import { UnitedMapSettings } from '../map-models'; +import { DEFAULT_ZOOM_LEVEL, UnitedMapSettings } from '../map-models'; import { WidgetContext } from '@home/models/widget-component.models'; export class HEREMap extends LeafletMap { @@ -24,7 +24,7 @@ export class HEREMap extends LeafletMap { super(ctx, $container, options); const map = L.map($container, { editable: !!options.editablePolygon - }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); + }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL); const tileLayer = (L.tileLayer as any).provider(options.mapProviderHere || 'HERE.normalDay', options.credentials); tileLayer.addTo(map); super.initSettings(options); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts index f4f10a0dac..7cfb77fd42 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts @@ -16,7 +16,7 @@ import L from 'leaflet'; import LeafletMap from '../leaflet-map'; -import { UnitedMapSettings } from '../map-models'; +import { DEFAULT_ZOOM_LEVEL, UnitedMapSettings } from '../map-models'; import { WidgetContext } from '@home/models/widget-component.models'; export class OpenStreetMap extends LeafletMap { @@ -24,7 +24,7 @@ export class OpenStreetMap extends LeafletMap { super(ctx, $container, options); const map = L.map($container, { editable: !!options.editablePolygon - }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); + }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL); let tileLayer; if (options.useCustomProvider) { tileLayer = L.tileLayer(options.customProviderTileUrl); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts index 498a6053a1..29074501ab 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts @@ -17,7 +17,7 @@ import L from 'leaflet'; import LeafletMap from '../leaflet-map'; -import { UnitedMapSettings } from '../map-models'; +import { DEFAULT_ZOOM_LEVEL, UnitedMapSettings } from '../map-models'; import { WidgetContext } from '@home/models/widget-component.models'; export class TencentMap extends LeafletMap { @@ -26,7 +26,7 @@ export class TencentMap extends LeafletMap { const txUrl = 'http://rt{s}.map.gtimg.com/realtimerender?z={z}&x={x}&y={y}&type=vector&style=0'; const map = L.map($container, { editable: !!options.editablePolygon - }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); + }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL); const txLayer = L.tileLayer(txUrl, { subdomains: '0123', tms: true, From a87a3d684e4082b14c17595792a25e558f922dd7 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 16 Nov 2020 10:52:18 +0200 Subject: [PATCH 357/463] Improvement to handling of subscription updates --- .../DefaultTbLocalSubscriptionService.java | 23 +++++-------------- .../assets/locale/locale.constant-en_US.json | 4 ++-- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java index 4e62628851..cd85c44cbe 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java @@ -21,12 +21,6 @@ import org.springframework.context.annotation.Lazy; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.EntityViewId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.ClusterTopologyChangeEvent; import org.thingsboard.server.queue.discovery.PartitionChangeEvent; @@ -36,7 +30,6 @@ import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.queue.TbClusterService; -import org.thingsboard.server.service.telemetry.DefaultTelemetryWebSocketService; import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; @@ -49,7 +42,6 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.stream.Collectors; @Slf4j @TbCoreComponent @@ -59,9 +51,6 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer private final Set currentPartitions = ConcurrentHashMap.newKeySet(); private final Map> subscriptionsBySessionId = new ConcurrentHashMap<>(); - @Autowired - private EntityViewService entityViewService; - @Autowired private PartitionService partitionService; @@ -72,17 +61,17 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer @Lazy private SubscriptionManagerService subscriptionManagerService; - private ExecutorService wsCallBackExecutor; + private ExecutorService subscriptionUpdateExecutor; @PostConstruct public void initExecutor() { - wsCallBackExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("ws-sub-callback")); + subscriptionUpdateExecutor = Executors.newWorkStealingPool(20); } @PreDestroy public void shutdownExecutor() { - if (wsCallBackExecutor != null) { - wsCallBackExecutor.shutdownNow(); + if (subscriptionUpdateExecutor != null) { + subscriptionUpdateExecutor.shutdownNow(); } } @@ -148,7 +137,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer update.getLatestValues().forEach((key, value) -> attrSub.getKeyStates().put(key, value)); break; } - subscription.getUpdateConsumer().accept(sessionId, update); + subscriptionUpdateExecutor.submit(() -> subscription.getUpdateConsumer().accept(sessionId, update)); } callback.onSuccess(); } @@ -158,7 +147,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer TbSubscription subscription = subscriptionsBySessionId .getOrDefault(sessionId, Collections.emptyMap()).get(update.getSubscriptionId()); if (subscription != null && subscription.getType() == TbSubscriptionType.ALARMS) { - subscription.getUpdateConsumer().accept(sessionId, update); + subscriptionUpdateExecutor.submit(() -> subscription.getUpdateConsumer().accept(sessionId, update)); } callback.onSuccess(); } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 76799b73fd..9acd10ef95 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -941,8 +941,8 @@ "provision-strategy": "Provision strategy", "provision-strategy-required": "Provision strategy is required.", "provision-strategy-disabled": "Disabled", - "provision-strategy-created-new": "Allow create new devices", - "provision-strategy-check-pre-provisioned": "Check pre provisioned devices", + "provision-strategy-created-new": "Allow to create new devices", + "provision-strategy-check-pre-provisioned": "Check for pre-provisioned devices", "provision-device-key": "Provision device key", "provision-device-key-required": "Provision device key is required.", "provision-device-secret": "Provision device secret", From 699b2ef9bef480e512355d4efe03dac65320ab81 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 16 Nov 2020 12:22:13 +0200 Subject: [PATCH 358/463] changed schema for testPushMqttTelemetryWithTs: set Values as inner object --- ...actMqttTimeseriesProtoIntegrationTest.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java index f8277b1934..b69b53856f 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java @@ -80,16 +80,17 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac String schemaStr = "syntax =\"proto3\";\n" + "\n" + "package test;\n" + - "\n" + - "message Values {\n" + - " string key1 = 1;\n" + - " bool key2 = 2;\n" + - " double key3 = 3;\n" + - " int32 key4 = 4;\n" + - " string key5 = 5;\n" + - "}\n" + " \n" + "message PostTelemetry {\n" + + "\n" + + " message Values {\n" + + " string key1 = 1;\n" + + " bool key2 = 2;\n" + + " double key3 = 3;\n" + + " int32 key4 = 4;\n" + + " string key5 = 5;\n" + + " }\n" + + "\n" + " int64 ts = 1;\n" + " Values values = 2;\n" + "}"; @@ -104,7 +105,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(schemaStr); DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); - DynamicMessage.Builder valuesBuilder = telemetrySchema.newMessageBuilder("Values"); + DynamicMessage.Builder valuesBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.Values"); Descriptors.Descriptor valuesDescriptor = valuesBuilder.getDescriptorForType(); assertNotNull(valuesDescriptor); From 150b1bb809b222fb3fbcad1db5e3736362071fba Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 16 Nov 2020 11:46:17 +0200 Subject: [PATCH 359/463] AlarmState improvements (used createDetails for update or clear alarm). --- .../rule/engine/profile/AlarmState.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index ce43ca260f..30a861134a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -111,7 +111,7 @@ class AlarmState { for (AlarmRuleState state : createRulesSortedBySeverityDesc) { stateUpdate = clearAlarmState(stateUpdate, state); } - ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), JacksonUtil.OBJECT_MAPPER.createObjectNode(), System.currentTimeMillis()); + ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), createDetails(clearState), System.currentTimeMillis()); pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm)); currentAlarm = null; } else if (AlarmEvalResult.FALSE.equals(evalResult)) { @@ -203,6 +203,7 @@ class AlarmState { AlarmSeverity oldSeverity = currentAlarm.getSeverity(); // Skip update if severity is decreased. if (severity.ordinal() <= oldSeverity.ordinal()) { + currentAlarm.setDetails(createDetails(ruleState)); if (!oldSeverity.equals(severity)) { currentAlarm.setSeverity(severity); currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm); @@ -235,19 +236,24 @@ class AlarmState { } private JsonNode createDetails(AlarmRuleState ruleState) { - ObjectNode details = JacksonUtil.OBJECT_MAPPER.createObjectNode(); - String alarmDetails = ruleState.getAlarmRule().getAlarmDetails(); + JsonNode alarmDetails; + String alarmDetailsStr = ruleState.getAlarmRule().getAlarmDetails(); - if (alarmDetails != null) { + if (alarmDetailsStr != null) { for (KeyFilter keyFilter : ruleState.getAlarmRule().getCondition().getCondition()) { EntityKeyValue entityKeyValue = dataSnapshot.getValue(keyFilter.getKey()); - alarmDetails = alarmDetails.replaceAll(String.format("\\$\\{%s}", keyFilter.getKey().getKey()), getValueAsString(entityKeyValue)); + alarmDetailsStr = alarmDetailsStr.replaceAll(String.format("\\$\\{%s}", keyFilter.getKey().getKey()), getValueAsString(entityKeyValue)); } - - details.put("data", alarmDetails); + ObjectNode newDetails = JacksonUtil.OBJECT_MAPPER.createObjectNode(); + newDetails.put("data", alarmDetailsStr); + alarmDetails = newDetails; + } else if (currentAlarm != null) { + alarmDetails = currentAlarm.getDetails(); + } else { + alarmDetails = JacksonUtil.OBJECT_MAPPER.createObjectNode(); } - return details; + return alarmDetails; } private static String getValueAsString(EntityKeyValue entityKeyValue) { From 076f5483587d5ea1a7e0cd1d79f16a70e0fd6c84 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 16 Nov 2020 12:40:12 +0200 Subject: [PATCH 360/463] refactored AlarmState --- .../org/thingsboard/rule/engine/profile/AlarmState.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 30a861134a..81622b6b93 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.Data; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.thingsboard.rule.engine.action.TbAlarmResult; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.profile.state.PersistedAlarmRuleState; @@ -239,18 +240,18 @@ class AlarmState { JsonNode alarmDetails; String alarmDetailsStr = ruleState.getAlarmRule().getAlarmDetails(); - if (alarmDetailsStr != null) { + if (StringUtils.isNotEmpty(alarmDetailsStr)) { for (KeyFilter keyFilter : ruleState.getAlarmRule().getCondition().getCondition()) { EntityKeyValue entityKeyValue = dataSnapshot.getValue(keyFilter.getKey()); alarmDetailsStr = alarmDetailsStr.replaceAll(String.format("\\$\\{%s}", keyFilter.getKey().getKey()), getValueAsString(entityKeyValue)); } - ObjectNode newDetails = JacksonUtil.OBJECT_MAPPER.createObjectNode(); + ObjectNode newDetails = JacksonUtil.newObjectNode(); newDetails.put("data", alarmDetailsStr); alarmDetails = newDetails; } else if (currentAlarm != null) { alarmDetails = currentAlarm.getDetails(); } else { - alarmDetails = JacksonUtil.OBJECT_MAPPER.createObjectNode(); + alarmDetails = JacksonUtil.newObjectNode(); } return alarmDetails; From 4fc7668a727045614672b1aa12b4bcf73971c709 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 16 Nov 2020 14:12:49 +0200 Subject: [PATCH 361/463] added default mqtt proto schemas --- ...ofile-transport-configuration.component.ts | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index be54cff29a..35feb53545 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -133,8 +133,32 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control private updateTransportPayloadBasedControls(type: MqttTransportPayloadType) { const transportPayloadTypeConfigurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration') as FormGroup; if (type === MqttTransportPayloadType.PROTOBUF) { - transportPayloadTypeConfigurationFormGroup.registerControl('deviceTelemetryProtoSchema', this.fb.control(null, Validators.required)); - transportPayloadTypeConfigurationFormGroup.registerControl('deviceAttributesProtoSchema', this.fb.control(null, Validators.required)); + const defaultTelemetrySchema = "syntax =\"proto3\";\n" + + "package telemetry;\n" + + "\n" + + "message SensorDataReading {\n" + + "\n" + + " double temperature = 1;\n" + + " double humidity = 2;\n" + + " InnerObject innerObject = 3;\n" + + "\n" + + " message InnerObject {\n" + + " string key1 = 1;\n" + + " bool key2 = 2;\n" + + " double key3 = 3;\n" + + " int32 key4 = 4;\n" + + " string key5 = 5;\n" + + " }\n" + + "}\n"; + const defaultAttributesSchema = "syntax =\"proto3\";\n" + + "package attributes;\n" + + "\n" + + "message SensorDataReading {\n" + + " string firmwareVersion = 1;\n" + + " string serialNumber = 2;\n" + + "}"; + transportPayloadTypeConfigurationFormGroup.registerControl('deviceTelemetryProtoSchema', this.fb.control(defaultTelemetrySchema, Validators.required)); + transportPayloadTypeConfigurationFormGroup.registerControl('deviceAttributesProtoSchema', this.fb.control(defaultAttributesSchema, Validators.required)); } else { transportPayloadTypeConfigurationFormGroup.removeControl('deviceTelemetryProtoSchema'); transportPayloadTypeConfigurationFormGroup.removeControl('deviceAttributesProtoSchema'); From abfb6893ba7dd67abba3e42f2c3f4f47b3b4a6d2 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 16 Nov 2020 16:48:10 +0200 Subject: [PATCH 362/463] UI: Added unique alarm type validation in alarm rule --- .../alarm/device-profile-alarm.component.html | 3 +++ .../profile/alarm/device-profile-alarm.component.ts | 13 +++++++++++++ .../components/profile/device-profile.component.ts | 10 +++++----- ui-ngx/src/assets/locale/locale.constant-en_US.json | 1 + 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html index cb1f7afb06..3468442b50 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html @@ -41,6 +41,9 @@ {{ 'device-profile.alarm-type-required' | translate }} + + {{ 'device-profile.alarm-type-unique' | translate }} +
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts index cf4a55e6d8..fc0cee9639 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts @@ -133,6 +133,19 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit } public validate(c: FormControl) { + if (c.parent) { + const alarmType = c.value.alarmType; + const profileAlarmsType = []; + c.parent.getRawValue().forEach((alarm: DeviceProfileAlarm) => { + profileAlarmsType.push(alarm.alarmType); + } + ); + if (profileAlarmsType.filter(profileAlarmType => profileAlarmType === alarmType).length > 1) { + this.alarmFormGroup.get('alarmType').setErrors({ + unique: true + }); + } + } return (this.alarmFormGroup.valid) ? null : { alarm: { valid: false, diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts index d30c8b47b9..8c521dcd77 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts @@ -38,7 +38,7 @@ import { } from '@shared/models/device.models'; import { EntityType } from '@shared/models/entity-type.models'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; -import {ServiceType} from "@shared/models/queue.models"; +import { ServiceType } from '@shared/models/queue.models'; @Component({ selector: 'tb-device-profile', @@ -176,10 +176,10 @@ export class DeviceProfileComponent extends EntityComponent { transportConfiguration: entity.profileData?.transportConfiguration, alarms: entity.profileData?.alarms, provisionConfiguration: deviceProvisionConfiguration - }}); - this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}); - this.entityForm.patchValue({defaultQueueName: entity.defaultQueueName}); - this.entityForm.patchValue({description: entity.description}); + }}, {emitEvent: false}); + this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}, {emitEvent: false}); + this.entityForm.patchValue({defaultQueueName: entity.defaultQueueName}, {emitEvent: false}); + this.entityForm.patchValue({description: entity.description}, {emitEvent: false}); } prepareFormValue(formValue: any): any { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 0b6ee1deeb..9275adcb6a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -909,6 +909,7 @@ "edit-alarm-rule": "Edit alarm rule", "alarm-type": "Alarm type", "alarm-type-required": "Alarm type is required.", + "alarm-type-unique": "Alarm type needs to be unique.", "alarm-type-pattern-hint": "Alarm type pattern, use ${metaKeyName} to substitute variables from metadata", "create-alarm-pattern": "Create {{alarmType}} alarm", "create-alarm-rules": "Create alarm rules", From 9c14088cade430bcea819253d3093f0db2cbe159 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 11 Nov 2020 16:22:57 +0200 Subject: [PATCH 363/463] added # filter topic handling --- .../transport/mqtt/util/MqttTopicFilterFactory.java | 13 +++++++++---- .../mqtt/util/MqttTopicFilterFactoryTest.java | 11 ++++++++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java index 4d5a9a7c2b..9893f8cbef 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java @@ -34,10 +34,15 @@ public class MqttTopicFilterFactory { } return filters.computeIfAbsent(topicFilter, filter -> { if (filter.contains("+") || filter.contains("#")) { - String regex = filter - .replace("\\", "\\\\") - .replace("+", "[^/]+") - .replace("/#", "($|/.*)"); + String regex; + if (filter.equals("#")) { + regex = filter.replace("#", "^(?!/).+"); + } else { + regex = filter + .replace("\\", "\\\\") + .replace("+", "[^/]+") + .replace("/#", "($|/.*)"); + } log.debug("Converting [{}] to [{}]", filter, regex); return new RegexTopicFilter(regex); } else { diff --git a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java index 0b854d51ef..f3a65bda14 100644 --- a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java +++ b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java @@ -20,7 +20,6 @@ import org.junit.runner.RunWith; import org.mockito.runners.MockitoJUnitRunner; import javax.script.ScriptException; -import java.util.regex.Pattern; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -32,6 +31,9 @@ public class MqttTopicFilterFactoryTest { private static String TEST_STR_2 = "Sensor/Temperature"; private static String TEST_STR_3 = "Sensor/Temperature2/House/48"; + private static String TEST_STR_4 = String.format("%s%n%s", "/Sensor/Temperature", "/House/48"); + private static String TEST_STR_5 = "/" + TEST_STR_1; + @Test public void metadataCanBeUpdated() throws ScriptException { MqttTopicFilter filter = MqttTopicFilterFactory.toFilter("Sensor/Temperature/House/+"); @@ -51,6 +53,13 @@ public class MqttTopicFilterFactoryTest { assertTrue(filter.filter(TEST_STR_1)); assertTrue(filter.filter(TEST_STR_2)); assertFalse(filter.filter(TEST_STR_3)); + + filter = MqttTopicFilterFactory.toFilter("#"); + assertTrue(filter.filter(TEST_STR_1)); + assertTrue(filter.filter(TEST_STR_2)); + assertTrue(filter.filter(TEST_STR_3)); + assertFalse(filter.filter(TEST_STR_4)); + assertFalse(filter.filter(TEST_STR_5)); } } From fde0e7824de10157f38f40e2d10bc535978e49c2 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 11 Nov 2020 19:31:21 +0200 Subject: [PATCH 364/463] change regex for # filter --- .../transport/mqtt/util/MqttTopicFilterFactory.java | 2 +- .../mqtt/util/MqttTopicFilterFactoryTest.java | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java index 9893f8cbef..98e472ba5c 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java @@ -36,7 +36,7 @@ public class MqttTopicFilterFactory { if (filter.contains("+") || filter.contains("#")) { String regex; if (filter.equals("#")) { - regex = filter.replace("#", "^(?!/).+"); + regex = filter.replace("#", "\\S+"); } else { regex = filter .replace("\\", "\\\\") diff --git a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java index f3a65bda14..2ec05fac78 100644 --- a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java +++ b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java @@ -30,9 +30,10 @@ public class MqttTopicFilterFactoryTest { private static String TEST_STR_1 = "Sensor/Temperature/House/48"; private static String TEST_STR_2 = "Sensor/Temperature"; private static String TEST_STR_3 = "Sensor/Temperature2/House/48"; - - private static String TEST_STR_4 = String.format("%s%n%s", "/Sensor/Temperature", "/House/48"); - private static String TEST_STR_5 = "/" + TEST_STR_1; + private static String TEST_STR_4 = "/Sensor/Temperature2/House/48"; + private static String TEST_STR_5 = String.format("%s%n%s", "/Sensor/Temperature", "/House/48"); + private static String TEST_STR_6 = ""; + private static String TEST_STR_7 = " "; @Test public void metadataCanBeUpdated() throws ScriptException { @@ -58,8 +59,10 @@ public class MqttTopicFilterFactoryTest { assertTrue(filter.filter(TEST_STR_1)); assertTrue(filter.filter(TEST_STR_2)); assertTrue(filter.filter(TEST_STR_3)); - assertFalse(filter.filter(TEST_STR_4)); + assertTrue(filter.filter(TEST_STR_4)); assertFalse(filter.filter(TEST_STR_5)); + assertFalse(filter.filter(TEST_STR_6)); + assertFalse(filter.filter(TEST_STR_7)); } } From 7482d73a9ba47954860c02ae206294e01afc9a27 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 16 Nov 2020 16:52:24 +0200 Subject: [PATCH 365/463] added AlwaysTrueTopicFilter --- .../mqtt/util/AlwaysTrueTopicFilter.java | 27 +++++++++++++++++++ .../mqtt/util/MqttTopicFilterFactory.java | 2 +- .../mqtt/util/MqttTopicFilterFactoryTest.java | 13 ++++----- 3 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/AlwaysTrueTopicFilter.java diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/AlwaysTrueTopicFilter.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/AlwaysTrueTopicFilter.java new file mode 100644 index 0000000000..9952c4b507 --- /dev/null +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/AlwaysTrueTopicFilter.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.mqtt.util; + +import lombok.Data; + +@Data +public class AlwaysTrueTopicFilter implements MqttTopicFilter { + + @Override + public boolean filter(String topic) { + return true; + } +} diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java index 98e472ba5c..f2b4ef27e2 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java @@ -36,7 +36,7 @@ public class MqttTopicFilterFactory { if (filter.contains("+") || filter.contains("#")) { String regex; if (filter.equals("#")) { - regex = filter.replace("#", "\\S+"); + return new AlwaysTrueTopicFilter(); } else { regex = filter .replace("\\", "\\\\") diff --git a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java index 2ec05fac78..fac2e5c01d 100644 --- a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java +++ b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java @@ -31,9 +31,8 @@ public class MqttTopicFilterFactoryTest { private static String TEST_STR_2 = "Sensor/Temperature"; private static String TEST_STR_3 = "Sensor/Temperature2/House/48"; private static String TEST_STR_4 = "/Sensor/Temperature2/House/48"; - private static String TEST_STR_5 = String.format("%s%n%s", "/Sensor/Temperature", "/House/48"); - private static String TEST_STR_6 = ""; - private static String TEST_STR_7 = " "; + private static String TEST_STR_5 = "Sensor/ Temperature"; + private static String TEST_STR_6 = "/"; @Test public void metadataCanBeUpdated() throws ScriptException { @@ -60,9 +59,11 @@ public class MqttTopicFilterFactoryTest { assertTrue(filter.filter(TEST_STR_2)); assertTrue(filter.filter(TEST_STR_3)); assertTrue(filter.filter(TEST_STR_4)); - assertFalse(filter.filter(TEST_STR_5)); - assertFalse(filter.filter(TEST_STR_6)); - assertFalse(filter.filter(TEST_STR_7)); + assertTrue(filter.filter(TEST_STR_5)); + assertTrue(filter.filter(TEST_STR_6)); + + filter = MqttTopicFilterFactory.toFilter("Sensor/Temperature#"); + assertFalse(filter.filter(TEST_STR_2)); } } From 13a731ecb38203d4b17b187519dd98fb93c755cb Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 16 Nov 2020 16:55:24 +0200 Subject: [PATCH 366/463] fix MqttTopicFilterFactory toFilter --- .../mqtt/util/MqttTopicFilterFactory.java | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java index f2b4ef27e2..0c3b497591 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java @@ -33,16 +33,13 @@ public class MqttTopicFilterFactory { throw new IllegalArgumentException("Topic filter can't be empty!"); } return filters.computeIfAbsent(topicFilter, filter -> { - if (filter.contains("+") || filter.contains("#")) { - String regex; - if (filter.equals("#")) { - return new AlwaysTrueTopicFilter(); - } else { - regex = filter - .replace("\\", "\\\\") - .replace("+", "[^/]+") - .replace("/#", "($|/.*)"); - } + if (filter.equals("#")) { + return new AlwaysTrueTopicFilter(); + } else if (filter.contains("+") || filter.contains("#")) { + String regex = filter + .replace("\\", "\\\\") + .replace("+", "[^/]+") + .replace("/#", "($|/.*)"); log.debug("Converting [{}] to [{}]", filter, regex); return new RegexTopicFilter(regex); } else { From 7dc793e777a623c682f68bde03637de898cc22ed Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 16 Nov 2020 17:12:27 +0200 Subject: [PATCH 367/463] UI: Change translate --- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 9275adcb6a..27e504d6ee 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -909,7 +909,7 @@ "edit-alarm-rule": "Edit alarm rule", "alarm-type": "Alarm type", "alarm-type-required": "Alarm type is required.", - "alarm-type-unique": "Alarm type needs to be unique.", + "alarm-type-unique": "Alarm type must be unique within the device profile alarm rules.", "alarm-type-pattern-hint": "Alarm type pattern, use ${metaKeyName} to substitute variables from metadata", "create-alarm-pattern": "Create {{alarmType}} alarm", "create-alarm-rules": "Create alarm rules", From 706fd01d7619dc5c3d4802e57cf2f6af348e8293 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 16 Nov 2020 17:18:24 +0200 Subject: [PATCH 368/463] Device Profile Proto Improvement --- .../server/mqtt/MqttSqlTestSuite.java | 12 ++--- ...AbstractMqttAttributesIntegrationTest.java | 1 - ...tMqttAttributesRequestIntegrationTest.java | 2 +- ...AttributesRequestProtoIntegrationTest.java | 4 +- ...ttDeviceProfileTransportConfiguration.java | 9 ++++ .../ProtoTransportPayloadConfiguration.java | 3 +- .../mqtt/session/DeviceSessionCtx.java | 2 +- .../transport/adaptor/JsonConverter.java | 51 ++++--------------- .../dao/device/DeviceProfileServiceImpl.java | 25 ++++----- ...ofile-transport-configuration.component.ts | 2 +- 10 files changed, 46 insertions(+), 65 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java index f71e8f37a2..ec47db14f1 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java @@ -26,13 +26,13 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({ - "org.thingsboard.server.mqtt.rpc.sql.*Test", - "org.thingsboard.server.mqtt.telemetry.timeseries.sql.*Test", - "org.thingsboard.server.mqtt.telemetry.attributes.sql.*Test", - "org.thingsboard.server.mqtt.attributes.updates.sql.*Test", +// "org.thingsboard.server.mqtt.rpc.sql.*Test", +// "org.thingsboard.server.mqtt.telemetry.timeseries.sql.*Test", +// "org.thingsboard.server.mqtt.telemetry.attributes.sql.*Test", +// "org.thingsboard.server.mqtt.attributes.updates.sql.*Test", "org.thingsboard.server.mqtt.attributes.request.sql.*Test", - "org.thingsboard.server.mqtt.claim.sql.*Test", - "org.thingsboard.server.mqtt.provision.sql.*Test" +// "org.thingsboard.server.mqtt.claim.sql.*Test", +// "org.thingsboard.server.mqtt.provision.sql.*Test" }) public class MqttSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java index 32488c8eb0..d1b0c482f8 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java @@ -56,7 +56,6 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt return tsKvProtoList; } - protected TransportProtos.TsKvProto getTsKvProto(String key, String value, TransportProtos.KeyValueType keyValueType) { TransportProtos.TsKvProto.Builder tsKvProtoBuilder = TransportProtos.TsKvProto.newBuilder(); TransportProtos.KeyValueProto keyValueProto = getKeyValueProto(key, value, keyValueType); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java index 51d38e6312..2f33b8dcf8 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java @@ -122,7 +122,7 @@ public abstract class AbstractMqttAttributesRequestIntegrationTest extends Abstr client.publish(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX + "1", mqttMessage); latch.await(3, TimeUnit.SECONDS); assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); - String expectedRequestPayload = "{\"client\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}},\"shared\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}"; + String expectedRequestPayload = "{\"client\":{\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}},\"attribute4\":73,\"attribute1\":\"value1\",\"attribute3\":42.0,\"attribute2\":true},\"shared\":{\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}},\"attribute4\":73,\"attribute1\":\"value1\",\"attribute3\":42.0,\"attribute2\":true}}"; assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8))); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java index 0ea53d2801..2df69c5fc4 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java @@ -72,11 +72,11 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends @Test public void testRequestAttributesValuesFromTheServer() throws Exception { - super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, DeviceProfileProvisionType.DISABLED, null, null); + super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", + TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, DeviceProfileProvisionType.DISABLED, null, null); processTestRequestAttributesValuesFromTheServer(); } - @Test public void testRequestAttributesValuesFromTheServerGateway() throws Exception { super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", TransportPayloadType.PROTOBUF, null, null); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java index 6a5c3c474c..c49b7bd356 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java @@ -30,4 +30,13 @@ public class MqttDeviceProfileTransportConfiguration implements DeviceProfileTra return DeviceTransportType.MQTT; } + public TransportPayloadTypeConfiguration getTransportPayloadTypeConfiguration() { + if (transportPayloadTypeConfiguration != null) { + return transportPayloadTypeConfiguration; + } else { + return new JsonTransportPayloadConfiguration(); + } + } + + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java index c5bdf28833..30bc7b9237 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.data.device.profile; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.github.os72.protobuf.dynamic.DynamicSchema; import com.github.os72.protobuf.dynamic.EnumDefinition; import com.github.os72.protobuf.dynamic.MessageDefinition; @@ -135,7 +136,7 @@ public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeC addMessageFieldsToTheMessageDefinition(messageElementFields, messageDefinitionBuilder); } if (!oneOfs.isEmpty()) { - for (OneOfElement oneOfelement: oneOfs) { + for (OneOfElement oneOfelement : oneOfs) { MessageDefinition.OneofBuilder oneofBuilder = messageDefinitionBuilder.addOneof(oneOfelement.getName()); addMessageFieldsToTheOneOfDefinition(oneOfelement.getFields(), oneofBuilder); } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index fb4512a59b..b45dc5add5 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -123,7 +123,7 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { payloadType = transportPayloadTypeConfiguration.getTransportPayloadType(); telemetryTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceTelemetryTopic()); attributesTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesTopic()); - if (transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration) { + if (TransportPayloadType.PROTOBUF.equals(payloadType)) { updateDynamicMessageDescriptors(transportPayloadTypeConfiguration); } } else { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 65467c69a5..63c7277ac2 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -19,7 +19,6 @@ import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSyntaxException; @@ -194,62 +193,34 @@ public class JsonConverter { String message = String.format("String value length [%d] for key [%s] is greater than maximum allowed [%d]", value.getAsString().length(), valueEntry.getKey(), maxStringValueLength); throw new JsonSyntaxException(message); } - if (isTypeCastEnabled) { - if (NumberUtils.isParsable(value.getAsString())) { - try { - result.add(buildNumericKeyValueProto(value, valueEntry.getKey())); - } catch (RuntimeException th) { - result.add(buildStringKVProto(valueEntry, value)); - } - } else { - try { - JsonElement jsonElement = JSON_PARSER.parse(value.getAsString()); - if (jsonElement.isJsonObject() || jsonElement.isJsonArray()) { - result.add(buildJsonKVProto(valueEntry, jsonElement)); - } else { - result.add(buildStringKVProto(valueEntry, value)); - } - } catch (JsonParseException e) { - result.add(buildStringKVProto(valueEntry, value)); - } + if (isTypeCastEnabled && NumberUtils.isParsable(value.getAsString())) { + try { + result.add(buildNumericKeyValueProto(value, valueEntry.getKey())); + } catch (RuntimeException th) { + result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.STRING_V) + .setStringV(value.getAsString()).build()); } } else { - result.add(buildStringKVProto(valueEntry, value)); + result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.STRING_V) + .setStringV(value.getAsString()).build()); } } else if (value.isBoolean()) { result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.BOOLEAN_V) .setBoolV(value.getAsBoolean()).build()); } else if (value.isNumber()) { result.add(buildNumericKeyValueProto(value, valueEntry.getKey())); - } else { + } else if (!value.isJsonNull()) { throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value); } } else if (element.isJsonObject() || element.isJsonArray()) { - result.add(buildJsonKVProto(valueEntry, element)); - } else { + result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.JSON_V).setJsonV(element.toString()).build()); + } else if (!element.isJsonNull()) { throw new JsonSyntaxException(CAN_T_PARSE_VALUE + element); } } return result; } - private static KeyValueProto buildStringKVProto(Entry valueEntry, JsonPrimitive value) { - return KeyValueProto.newBuilder() - .setKey(valueEntry.getKey()) - .setType(KeyValueType.STRING_V) - .setStringV(value.getAsString()) - .build(); - } - - private static KeyValueProto buildJsonKVProto(Entry valueEntry, JsonElement jsonElement) { - return KeyValueProto - .newBuilder() - .setKey(valueEntry.getKey()) - .setType(KeyValueType.JSON_V) - .setJsonV(jsonElement.toString()) - .build(); - } - private static KeyValueProto buildNumericKeyValueProto(JsonPrimitive value, String key) { if (value.getAsString().contains(".")) { return KeyValueProto.newBuilder() diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index d1a7820df4..09da415a84 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -331,18 +331,19 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D if (defaultDeviceProfile != null && !defaultDeviceProfile.getId().equals(deviceProfile.getId())) { throw new DataValidationException("Another default device profile is present in scope of current tenant!"); } - } else { - DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); - if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { - MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; - if (mqttDeviceProfileTransportConfiguration.getTransportPayloadTypeConfiguration() instanceof ProtoTransportPayloadConfiguration) { - ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration = (ProtoTransportPayloadConfiguration) mqttDeviceProfileTransportConfiguration.getTransportPayloadTypeConfiguration(); - try { - validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceAttributesProtoSchema(), ATTRIBUTES_PROTO_SCHEMA); - validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceTelemetryProtoSchema(), TELEMETRY_PROTO_SCHEMA); - } catch (Exception exception) { - throw new DataValidationException(exception.getMessage()); - } + } + + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { + MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + if (mqttTransportConfiguration.getTransportPayloadTypeConfiguration() instanceof ProtoTransportPayloadConfiguration) { + ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration = + (ProtoTransportPayloadConfiguration) mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); + try { + validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceAttributesProtoSchema(), ATTRIBUTES_PROTO_SCHEMA); + validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceTelemetryProtoSchema(), TELEMETRY_PROTO_SCHEMA); + } catch (Exception exception) { + throw new DataValidationException(exception.getMessage()); } } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index 35feb53545..a5d4ced861 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -153,7 +153,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control const defaultAttributesSchema = "syntax =\"proto3\";\n" + "package attributes;\n" + "\n" + - "message SensorDataReading {\n" + + "message SensorConfiguration {\n" + " string firmwareVersion = 1;\n" + " string serialNumber = 2;\n" + "}"; From aa61db22f2ec63efe3c167b0a012978bc95690ba Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 16 Nov 2020 18:20:23 +0200 Subject: [PATCH 369/463] added validations for DeviceProfileAlarm type --- .../dao/device/DeviceProfileServiceImpl.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index f3857cacd5..60be6137ce 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -23,16 +23,17 @@ import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -48,7 +49,9 @@ import org.thingsboard.server.dao.tenant.TenantDao; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import static org.thingsboard.server.common.data.CacheConstants.DEVICE_PROFILE_CACHE; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -115,7 +118,7 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_profile_name_unq_key")) { throw new DataValidationException("Device profile with such name already exists!"); } else if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_provision_key_unq_key")) { - throw new DataValidationException("Device profile with such provision device key already exists!"); + throw new DataValidationException("Device profile with such provision device key already exists!"); } else { throw t; } @@ -311,6 +314,21 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D throw new DataValidationException("Another default device profile is present in scope of current tenant!"); } } + List profileAlarms = deviceProfile.getProfileData().getAlarms(); + + if (!CollectionUtils.isEmpty(profileAlarms)) { + Set alarmTypes = new HashSet<>(); + for (DeviceProfileAlarm alarm : profileAlarms) { + String alarmType = alarm.getAlarmType(); + if (StringUtils.isEmpty(alarmType)) { + throw new DataValidationException("Alarm rule type should be specified!"); + } + if (!alarmTypes.add(alarmType)) { + throw new DataValidationException(String.format("Can't create device profile with the same alarm rule types: \"%s\"!", alarmType)); + } + } + + } } @Override From 3c5bce7d20509113efd5ae304eb298ea85016d71 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 16 Nov 2020 17:44:11 +0200 Subject: [PATCH 370/463] Updated thermostat demo --- .../json/demo/dashboards/thermostats.json | 88 +-------- .../demo/rule_chains/thermostat_alarms.json | 137 +++++++------ .../DefaultSystemDataLoaderService.java | 182 +++++++++++++++--- 3 files changed, 234 insertions(+), 173 deletions(-) diff --git a/application/src/main/data/json/demo/dashboards/thermostats.json b/application/src/main/data/json/demo/dashboards/thermostats.json index 06edbcbef0..07b542c3aa 100644 --- a/application/src/main/data/json/demo/dashboards/thermostats.json +++ b/application/src/main/data/json/demo/dashboards/thermostats.json @@ -219,8 +219,8 @@ "defaultPageSize": 10, "defaultSortOrder": "-createdTime", "enableSelectColumnDisplay": false, - "enableStatusFilter": true, - "alarmsTitle": "Alarms" + "alarmsTitle": "Alarms", + "enableFilter": true }, "title": "New Alarms table", "dropShadow": true, @@ -234,6 +234,9 @@ "showLegend": false, "alarmSource": { "type": "entity", + "name": "alarms", + "entityAliasId": "68a058e1-fdda-8482-715b-3ae4a488568e", + "filterId": null, "dataKeys": [ { "name": "createdTime", @@ -275,9 +278,7 @@ "settings": {}, "_hash": 0.7977920750136249 } - ], - "entityAliasId": "ce27a9d0-93bf-b7a4-054d-d0369a8cf813", - "name": "alarms" + ] }, "alarmSearchStatus": "ANY", "alarmsPollingInterval": 5, @@ -1031,7 +1032,8 @@ "markerImageFunction": "var res;\nif(dsData[dsIndex].active !== \"true\"){\n\tvar res = {\n\t url: images[0],\n\t size: 48\n\t}\n} else {\n var res = {\n\t url: images[1],\n\t size: 48\n\t}\n}\nreturn res;", "useLabelFunction": true, "provider": "openstreet-map", - "draggableMarker": true + "draggableMarker": true, + "editablePolygon": true }, "title": "New Markers Placement - OpenStreetMap", "dropShadow": true, @@ -1062,61 +1064,6 @@ "displayTimewindow": true }, "id": "0a430429-9078-9ae6-2b67-e4a15a2bf8bf" - }, - "f4bb2f2d-0164-60bc-f3e8-9b1e7b5a59b3": { - "isSystemType": true, - "bundleAlias": "input_widgets", - "typeAlias": "update_double_timeseries", - "type": "latest", - "title": "New widget", - "sizeX": 7.5, - "sizeY": 3, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547", - "dataKeys": [ - { - "name": "temperature", - "type": "timeseries", - "label": "temperature", - "color": "#2196f3", - "settings": {}, - "_hash": 0.4164505192982848 - } - ] - } - ], - "timewindow": { - "realtime": { - "timewindowMs": 60000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "showResultMessage": true, - "showLabel": true - }, - "title": "New Update double timeseries", - "dropShadow": true, - "enableFullscreen": false, - "widgetStyle": {}, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "useDashboardTimewindow": true, - "showLegend": false, - "actions": {} - }, - "row": 0, - "col": 0, - "id": "f4bb2f2d-0164-60bc-f3e8-9b1e7b5a59b3" } }, "states": { @@ -1215,12 +1162,6 @@ "sizeY": 6, "row": 6, "col": 0 - }, - "f4bb2f2d-0164-60bc-f3e8-9b1e7b5a59b3": { - "sizeX": 7.5, - "sizeY": 3, - "row": 12, - "col": 0 } }, "gridSettings": { @@ -1257,16 +1198,6 @@ "stateEntityParamName": null, "defaultStateEntity": null } - }, - "ce27a9d0-93bf-b7a4-054d-d0369a8cf813": { - "id": "ce27a9d0-93bf-b7a4-054d-d0369a8cf813", - "alias": "Thermostat-alarm", - "filter": { - "type": "entityName", - "resolveMultiple": false, - "entityType": "ASSET", - "entityNameFilter": "Thermostat Alarms" - } } }, "timewindow": { @@ -1301,7 +1232,8 @@ "showDashboardTimewindow": true, "showDashboardExport": true, "toolbarAlwaysOpen": true - } + }, + "filters": {} }, "name": "Thermostats" } \ No newline at end of file diff --git a/application/src/main/data/json/demo/rule_chains/thermostat_alarms.json b/application/src/main/data/json/demo/rule_chains/thermostat_alarms.json index d67052cbc5..30812d6003 100644 --- a/application/src/main/data/json/demo/rule_chains/thermostat_alarms.json +++ b/application/src/main/data/json/demo/rule_chains/thermostat_alarms.json @@ -1,6 +1,8 @@ { "ruleChain": { - "additionalInfo": null, + "additionalInfo": { + "description": "" + }, "name": "Thermostat Alarms", "firstRuleNodeId": null, "root": false, @@ -8,131 +10,126 @@ "configuration": null }, "metadata": { - "firstNodeIndex": 5, + "firstNodeIndex": 6, "nodes": [ { "additionalInfo": { - "layoutX": 929, - "layoutY": 67 + "layoutX": 822, + "layoutY": 294 }, - "type": "org.thingsboard.rule.engine.action.TbCreateAlarmNode", - "name": "Create Temp Alarm", + "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", + "name": "Save Timeseries", "debugMode": false, "configuration": { - "alarmType": "High Temperature", - "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\ndetails.triggerValue = msg.temperature;\nreturn details;", - "severity": "MAJOR", - "propagate": true, - "useMessageAlarmData": false, - "relationTypes": [ - "ToAlarmPropagationAsset" - ] + "defaultTTL": 0 } }, { "additionalInfo": { - "layoutX": 930, - "layoutY": 201 + "description": null, + "layoutX": 824, + "layoutY": 221 }, - "type": "org.thingsboard.rule.engine.action.TbClearAlarmNode", - "name": "Clear Temp Alarm", + "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", + "name": "Save Client Attributes", "debugMode": false, "configuration": { - "alarmType": "High Temperature", - "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\nreturn details;" + "scope": "SERVER_SCOPE", + "notifyDevice": null } }, { "additionalInfo": { - "layoutX": 930, - "layoutY": 131 + "layoutX": 494, + "layoutY": 309 }, - "type": "org.thingsboard.rule.engine.action.TbCreateAlarmNode", - "name": "Create Humidity Alarm", + "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", + "name": "Message Type Switch", "debugMode": false, "configuration": { - "alarmType": "Low Humidity", - "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\ndetails.triggerValue = msg.humidity;\nreturn details;", - "severity": "MINOR", - "propagate": true, - "useMessageAlarmData": false, - "relationTypes": [ - "ToAlarmPropagationAsset" - ] + "version": 0 } }, { "additionalInfo": { - "layoutX": 929, - "layoutY": 275 + "layoutX": 824, + "layoutY": 383 }, - "type": "org.thingsboard.rule.engine.action.TbClearAlarmNode", - "name": "Clear Humidity Alarm", + "type": "org.thingsboard.rule.engine.action.TbLogNode", + "name": "Log RPC from Device", "debugMode": false, "configuration": { - "alarmType": "Low Humidity", - "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\nreturn details;" + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" } }, { "additionalInfo": { - "layoutX": 586, - "layoutY": 148 + "layoutX": 823, + "layoutY": 444 }, - "type": "org.thingsboard.rule.engine.filter.TbJsSwitchNode", - "name": "Check Alarms", + "type": "org.thingsboard.rule.engine.action.TbLogNode", + "name": "Log Other", "debugMode": false, "configuration": { - "jsScript": "var relations = [];\nif(metadata[\"ss_alarmTemperature\"] === \"true\"){\n if(msg.temperature > metadata[\"ss_thresholdTemperature\"]){\n relations.push(\"NewTempAlarm\");\n } else {\n relations.push(\"ClearTempAlarm\");\n }\n}\nif(metadata[\"ss_alarmHumidity\"] === \"true\"){\n if(msg.humidity < metadata[\"ss_thresholdHumidity\"]){\n relations.push(\"NewHumidityAlarm\");\n } else {\n relations.push(\"ClearHumidityAlarm\");\n }\n}\n\nreturn relations;" + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" } }, { "additionalInfo": { - "layoutX": 321, - "layoutY": 149 + "layoutX": 822, + "layoutY": 507 }, - "type": "org.thingsboard.rule.engine.metadata.TbGetAttributesNode", - "name": "Fetch Configuration", + "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode", + "name": "RPC Call Request", "debugMode": false, "configuration": { - "clientAttributeNames": [], - "sharedAttributeNames": [], - "serverAttributeNames": [ - "alarmTemperature", - "thresholdTemperature", - "alarmHumidity", - "thresholdHumidity" - ], - "latestTsKeyNames": [], - "tellFailureIfAbsent": false, - "getLatestValueWithTs": false + "timeoutInSeconds": 60 + } + }, + { + "additionalInfo": { + "description": "", + "layoutX": 209, + "layoutY": 307 + }, + "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode", + "name": "Device Profile Node", + "debugMode": false, + "configuration": { + "persistAlarmRulesState": false, + "fetchAlarmRulesStateOnStart": false } } ], "connections": [ { - "fromIndex": 4, - "toIndex": 0, - "type": "NewTempAlarm" + "fromIndex": 2, + "toIndex": 4, + "type": "Other" }, { - "fromIndex": 4, + "fromIndex": 2, "toIndex": 1, - "type": "ClearTempAlarm" + "type": "Post attributes" }, { - "fromIndex": 4, - "toIndex": 2, - "type": "NewHumidityAlarm" + "fromIndex": 2, + "toIndex": 0, + "type": "Post telemetry" }, { - "fromIndex": 4, + "fromIndex": 2, "toIndex": 3, - "type": "ClearHumidityAlarm" + "type": "RPC Request from Device" }, { - "fromIndex": 5, - "toIndex": 4, + "fromIndex": 2, + "toIndex": 5, + "type": "RPC Request to Device" + }, + { + "fromIndex": 6, + "toIndex": 2, "type": "Success" } ], diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 6c7678f023..ef25eeefdb 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -28,12 +28,21 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.DeviceProfileType; +import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; -import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import org.thingsboard.server.common.data.device.profile.AlarmCondition; +import org.thingsboard.server.common.data.device.profile.AlarmRule; +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -42,19 +51,29 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; -import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.query.BooleanFilterPredicate; +import org.thingsboard.server.common.data.query.DynamicValue; +import org.thingsboard.server.common.data.query.DynamicValueSourceType; +import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.common.data.query.EntityKeyType; +import org.thingsboard.server.common.data.query.EntityKeyValueType; +import org.thingsboard.server.common.data.query.FilterPredicateValue; +import org.thingsboard.server.common.data.query.KeyFilter; +import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.UserCredentials; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.widget.WidgetsBundle; -import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.relation.RelationService; +import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; @@ -62,6 +81,8 @@ import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetsBundleService; import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; @Service @Profile("install") @@ -96,12 +117,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Autowired private CustomerService customerService; - @Autowired - private RelationService relationService; - - @Autowired - private AssetService assetService; - @Autowired private DeviceService deviceService; @@ -114,6 +129,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Autowired private DeviceCredentialsService deviceCredentialsService; + @Autowired + private RuleChainService ruleChainService; + @Bean protected BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); @@ -245,19 +263,133 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { createDevice(demoTenant.getId(), null, defaultDeviceProfile.getId(), "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " + "Raspberry Pi GPIO control sample application"); - Asset thermostatAlarms = new Asset(); - thermostatAlarms.setTenantId(demoTenant.getId()); - thermostatAlarms.setName("Thermostat Alarms"); - thermostatAlarms.setType("AlarmPropagationAsset"); - thermostatAlarms = assetService.saveAsset(thermostatAlarms); - - DeviceProfile thermostatDeviceProfile = this.deviceProfileService.findOrCreateDeviceProfile(demoTenant.getId(), "thermostat"); - - DeviceId t1Id = createDevice(demoTenant.getId(), null, thermostatDeviceProfile.getId(), "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId(); - DeviceId t2Id = createDevice(demoTenant.getId(), null, thermostatDeviceProfile.getId(), "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId(); - - relationService.saveRelation(thermostatAlarms.getTenantId(), new EntityRelation(thermostatAlarms.getId(), t1Id, "ToAlarmPropagationAsset")); - relationService.saveRelation(thermostatAlarms.getTenantId(), new EntityRelation(thermostatAlarms.getId(), t2Id, "ToAlarmPropagationAsset")); + DeviceProfile thermostatDeviceProfile = new DeviceProfile(); + thermostatDeviceProfile.setTenantId(demoTenant.getId()); + thermostatDeviceProfile.setDefault(false); + thermostatDeviceProfile.setName("thermostat"); + thermostatDeviceProfile.setType(DeviceProfileType.DEFAULT); + thermostatDeviceProfile.setTransportType(DeviceTransportType.DEFAULT); + thermostatDeviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); + thermostatDeviceProfile.setDescription("Thermostat device profile"); + thermostatDeviceProfile.setDefaultRuleChainId(ruleChainService.findTenantRuleChains( + demoTenant.getId(), new PageLink(1, 0, "Thermostat Alarms")).getData().get(0).getId()); + + DeviceProfileData deviceProfileData = new DeviceProfileData(); + DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); + DefaultDeviceProfileTransportConfiguration transportConfiguration = new DefaultDeviceProfileTransportConfiguration(); + DisabledDeviceProfileProvisionConfiguration provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(null); + deviceProfileData.setConfiguration(configuration); + deviceProfileData.setTransportConfiguration(transportConfiguration); + deviceProfileData.setProvisionConfiguration(provisionConfiguration); + thermostatDeviceProfile.setProfileData(deviceProfileData); + + DeviceProfileAlarm highTemperature = new DeviceProfileAlarm(); + highTemperature.setId("highTemperatureAlarmID"); + highTemperature.setAlarmType("High Temperature"); + AlarmRule temperatureRule = new AlarmRule(); + AlarmCondition temperatureCondition = new AlarmCondition(); + temperatureCondition.setSpec(new SimpleAlarmConditionSpec()); + + KeyFilter alarmTemperatureAttributeFilter = new KeyFilter(); + alarmTemperatureAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "alarmTemperature")); + alarmTemperatureAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN); + BooleanFilterPredicate alarmTemperatureAttributePredicate = new BooleanFilterPredicate(); + alarmTemperatureAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL); + alarmTemperatureAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE)); + alarmTemperatureAttributeFilter.setPredicate(alarmTemperatureAttributePredicate); + + KeyFilter temperatureTimeseriesFilter = new KeyFilter(); + temperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); + temperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate temperatureTimeseriesFilterPredicate = new NumericFilterPredicate(); + temperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); + FilterPredicateValue temperatureTimeseriesPredicateValue = + new FilterPredicateValue<>(25.0, null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdTemperature")); + temperatureTimeseriesFilterPredicate.setValue(temperatureTimeseriesPredicateValue); + temperatureTimeseriesFilter.setPredicate(temperatureTimeseriesFilterPredicate); + temperatureCondition.setCondition(Arrays.asList(alarmTemperatureAttributeFilter, temperatureTimeseriesFilter)); + temperatureRule.setAlarmDetails("Current temperature = ${temperature}"); + temperatureRule.setCondition(temperatureCondition); + highTemperature.setCreateRules(new LinkedHashMap<>(Collections.singletonMap(AlarmSeverity.MAJOR, temperatureRule))); + + AlarmRule clearTemperatureRule = new AlarmRule(); + AlarmCondition clearTemperatureCondition = new AlarmCondition(); + clearTemperatureCondition.setSpec(new SimpleAlarmConditionSpec()); + + KeyFilter clearTemperatureTimeseriesFilter = new KeyFilter(); + clearTemperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); + clearTemperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate clearTemperatureTimeseriesFilterPredicate = new NumericFilterPredicate(); + clearTemperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS_OR_EQUAL); + FilterPredicateValue clearTemperatureTimeseriesPredicateValue = + new FilterPredicateValue<>(25.0, null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdTemperature")); + + clearTemperatureTimeseriesFilterPredicate.setValue(clearTemperatureTimeseriesPredicateValue); + clearTemperatureTimeseriesFilter.setPredicate(clearTemperatureTimeseriesFilterPredicate); + clearTemperatureCondition.setCondition(Collections.singletonList(clearTemperatureTimeseriesFilter)); + clearTemperatureRule.setCondition(clearTemperatureCondition); + clearTemperatureRule.setAlarmDetails("Current temperature = ${temperature}"); + highTemperature.setClearRule(clearTemperatureRule); + + DeviceProfileAlarm lowHumidity = new DeviceProfileAlarm(); + lowHumidity.setId("lowHumidityAlarmID"); + lowHumidity.setAlarmType("Low Humidity"); + AlarmRule humidityRule = new AlarmRule(); + AlarmCondition humidityCondition = new AlarmCondition(); + humidityCondition.setSpec(new SimpleAlarmConditionSpec()); + + KeyFilter alarmHumidityAttributeFilter = new KeyFilter(); + alarmHumidityAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "alarmHumidity")); + alarmHumidityAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN); + BooleanFilterPredicate alarmHumidityAttributePredicate = new BooleanFilterPredicate(); + alarmHumidityAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL); + alarmHumidityAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE)); + alarmHumidityAttributeFilter.setPredicate(alarmHumidityAttributePredicate); + + KeyFilter humidityTimeseriesFilter = new KeyFilter(); + humidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity")); + humidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate humidityTimeseriesFilterPredicate = new NumericFilterPredicate(); + humidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS); + FilterPredicateValue humidityTimeseriesPredicateValue = + new FilterPredicateValue<>(60.0, null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdHumidity")); + humidityTimeseriesFilterPredicate.setValue(humidityTimeseriesPredicateValue); + humidityTimeseriesFilter.setPredicate(humidityTimeseriesFilterPredicate); + humidityCondition.setCondition(Arrays.asList(alarmHumidityAttributeFilter, humidityTimeseriesFilter)); + + humidityRule.setCondition(humidityCondition); + humidityRule.setAlarmDetails("Current humidity = ${humidity}"); + lowHumidity.setCreateRules(new LinkedHashMap<>(Collections.singletonMap(AlarmSeverity.MINOR, humidityRule))); + + AlarmRule clearHumidityRule = new AlarmRule(); + AlarmCondition clearHumidityCondition = new AlarmCondition(); + clearHumidityCondition.setSpec(new SimpleAlarmConditionSpec()); + + KeyFilter clearHumidityTimeseriesFilter = new KeyFilter(); + clearHumidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity")); + clearHumidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate clearHumidityTimeseriesFilterPredicate = new NumericFilterPredicate(); + clearHumidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER_OR_EQUAL); + FilterPredicateValue clearHumidityTimeseriesPredicateValue = + new FilterPredicateValue<>(60.0, null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdHumidity")); + + clearHumidityTimeseriesFilterPredicate.setValue(clearHumidityTimeseriesPredicateValue); + clearHumidityTimeseriesFilter.setPredicate(clearHumidityTimeseriesFilterPredicate); + clearHumidityCondition.setCondition(Collections.singletonList(clearHumidityTimeseriesFilter)); + clearHumidityRule.setCondition(clearHumidityCondition); + clearHumidityRule.setAlarmDetails("Current humidity = ${humidity}"); + lowHumidity.setClearRule(clearHumidityRule); + + deviceProfileData.setAlarms(Arrays.asList(highTemperature, lowHumidity)); + + DeviceProfile savedThermostatDeviceProfile = deviceProfileService.saveDeviceProfile(thermostatDeviceProfile); + + DeviceId t1Id = createDevice(demoTenant.getId(), null, savedThermostatDeviceProfile.getId(), "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId(); + DeviceId t2Id = createDevice(demoTenant.getId(), null, savedThermostatDeviceProfile.getId(), "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId(); attributesService.save(demoTenant.getId(), t1Id, DataConstants.SERVER_SCOPE, Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.3948)), From eecc6aa1a69a9b558c76dd59399df68ebbc49d39 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 16 Nov 2020 19:08:55 +0200 Subject: [PATCH 371/463] fix dynamic schemas, tests --- .../mqtt/AbstractMqttIntegrationTest.java | 28 ++++++-- .../server/mqtt/MqttSqlTestSuite.java | 12 ++-- ...tMqttAttributesRequestIntegrationTest.java | 2 +- ...AttributesRequestProtoIntegrationTest.java | 30 ++++++++- ...AbstractMqttAttributesIntegrationTest.java | 14 ++-- ...actMqttAttributesProtoIntegrationTest.java | 19 +++++- ...actMqttTimeseriesProtoIntegrationTest.java | 66 +++++++++++++++---- .../ProtoTransportPayloadConfiguration.java | 22 +++---- 8 files changed, 145 insertions(+), 48 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java index 715ffeade3..a1e4bc0e63 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java @@ -62,19 +62,28 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest private static final AtomicInteger atomicInteger = new AtomicInteger(2); - protected static final String DEVICE_TELEMETRY_PROTO_SCHEMA = "syntax =\"proto3\";\n" + + public static final String DEVICE_TELEMETRY_PROTO_SCHEMA = "syntax =\"proto3\";\n" + "\n" + "package test;\n" + - " \n" + + "\n" + "message PostTelemetry {\n" + " string key1 = 1;\n" + " bool key2 = 2;\n" + " double key3 = 3;\n" + " int32 key4 = 4;\n" + - " string key5 = 5;\n" + + " JsonObject key5 = 5;\n" + + "\n" + + " message JsonObject {\n" + + " int32 someNumber = 6;\n" + + " repeated int32 someArray = 7;\n" + + " NestedJsonObject someNestedObject = 8;\n" + + " message NestedJsonObject {\n" + + " string key = 9;\n" + + " }\n" + + " }\n" + "}"; - protected static final String DEVICE_ATTRIBUTES_PROTO_SCHEMA = "syntax =\"proto3\";\n" + + public static final String DEVICE_ATTRIBUTES_PROTO_SCHEMA = "syntax =\"proto3\";\n" + "\n" + "package test;\n" + "\n" + @@ -83,7 +92,16 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest " bool key2 = 2;\n" + " double key3 = 3;\n" + " int32 key4 = 4;\n" + - " string key5 = 5;\n" + + " JsonObject key5 = 5;\n" + + "\n" + + " message JsonObject {\n" + + " int32 someNumber = 6;\n" + + " repeated int32 someArray = 7;\n" + + " NestedJsonObject someNestedObject = 8;\n" + + " message NestedJsonObject {\n" + + " string key = 9;\n" + + " }\n" + + " }\n" + "}"; protected Tenant savedTenant; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java index ec47db14f1..f71e8f37a2 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java @@ -26,13 +26,13 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({ -// "org.thingsboard.server.mqtt.rpc.sql.*Test", -// "org.thingsboard.server.mqtt.telemetry.timeseries.sql.*Test", -// "org.thingsboard.server.mqtt.telemetry.attributes.sql.*Test", -// "org.thingsboard.server.mqtt.attributes.updates.sql.*Test", + "org.thingsboard.server.mqtt.rpc.sql.*Test", + "org.thingsboard.server.mqtt.telemetry.timeseries.sql.*Test", + "org.thingsboard.server.mqtt.telemetry.attributes.sql.*Test", + "org.thingsboard.server.mqtt.attributes.updates.sql.*Test", "org.thingsboard.server.mqtt.attributes.request.sql.*Test", -// "org.thingsboard.server.mqtt.claim.sql.*Test", -// "org.thingsboard.server.mqtt.provision.sql.*Test" + "org.thingsboard.server.mqtt.claim.sql.*Test", + "org.thingsboard.server.mqtt.provision.sql.*Test" }) public class MqttSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java index 2f33b8dcf8..51d38e6312 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java @@ -122,7 +122,7 @@ public abstract class AbstractMqttAttributesRequestIntegrationTest extends Abstr client.publish(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX + "1", mqttMessage); latch.await(3, TimeUnit.SECONDS); assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); - String expectedRequestPayload = "{\"client\":{\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}},\"attribute4\":73,\"attribute1\":\"value1\",\"attribute3\":42.0,\"attribute2\":true},\"shared\":{\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}},\"attribute4\":73,\"attribute1\":\"value1\",\"attribute3\":42.0,\"attribute2\":true}}"; + String expectedRequestPayload = "{\"client\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}},\"shared\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}"; assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8))); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java index 2df69c5fc4..9e112c98be 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java @@ -62,7 +62,16 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends " bool attribute2 = 2;\n" + " double attribute3 = 3;\n" + " int32 attribute4 = 4;\n" + - " string attribute5 = 5;\n" + + " JsonObject attribute5 = 5;\n" + + "\n" + + " message JsonObject {\n" + + " int32 someNumber = 6;\n" + + " repeated int32 someArray = 7;\n" + + " NestedJsonObject someNestedObject = 8;\n" + + " message NestedJsonObject {\n" + + " string key = 9;\n" + + " }\n" + + " }\n" + "}"; @After @@ -93,6 +102,23 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(ATTRIBUTES_SCHEMA_STR); DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + + DynamicMessage.Builder nestedJsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject.NestedJsonObject"); + Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); + assertNotNull(nestedJsonObjectBuilderDescriptor); + DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build(); + + DynamicMessage.Builder jsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject"); + Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType(); + assertNotNull(jsonObjectBuilderDescriptor); + DynamicMessage jsonObject = jsonObjectBuilder + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3) + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject) + .build(); + DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes"); Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); assertNotNull(postAttributesMsgDescriptor); @@ -101,7 +127,7 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends .setField(postAttributesMsgDescriptor.findFieldByName("attribute2"), true) .setField(postAttributesMsgDescriptor.findFieldByName("attribute3"), 42.0) .setField(postAttributesMsgDescriptor.findFieldByName("attribute4"), 73) - .setField(postAttributesMsgDescriptor.findFieldByName("attribute5"), "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}") + .setField(postAttributesMsgDescriptor.findFieldByName("attribute5"), jsonObject) .build(); byte[] payload = postAttributesMsg.toByteArray(); client.publish(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, new MqttMessage(payload)); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java index 308983f070..0c8e963d1b 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java @@ -163,16 +163,10 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt break; case "key5": assertNotNull(value); - LinkedHashMap valueMap; - if (value instanceof String) { - valueMap = mapper.readValue((String) value, LinkedHashMap.class); - } else { - valueMap = (LinkedHashMap) value; - } - assertEquals(3, valueMap.size()); - assertEquals(42, valueMap.get("someNumber")); - assertEquals(Arrays.asList(1, 2, 3), valueMap.get("someArray")); - LinkedHashMap someNestedObject = (LinkedHashMap) valueMap.get("someNestedObject"); + assertEquals(3, ((LinkedHashMap) value).size()); + assertEquals(42, ((LinkedHashMap) value).get("someNumber")); + assertEquals(Arrays.asList(1, 2, 3), ((LinkedHashMap) value).get("someArray")); + LinkedHashMap someNestedObject = (LinkedHashMap) ((LinkedHashMap) value).get("someNestedObject"); assertEquals("value", someNestedObject.get("key")); break; } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java index 9cc7c66f55..1e14541d23 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java @@ -58,6 +58,23 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; ProtoFileElement transportProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_ATTRIBUTES_PROTO_SCHEMA); DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchemaFile, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + + DynamicMessage.Builder nestedJsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject.NestedJsonObject"); + Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); + assertNotNull(nestedJsonObjectBuilderDescriptor); + DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build(); + + DynamicMessage.Builder jsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject"); + Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType(); + assertNotNull(jsonObjectBuilderDescriptor); + DynamicMessage jsonObject = jsonObjectBuilder + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3) + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject) + .build(); + DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes"); Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); assertNotNull(postAttributesMsgDescriptor); @@ -66,7 +83,7 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac .setField(postAttributesMsgDescriptor.findFieldByName("key2"), true) .setField(postAttributesMsgDescriptor.findFieldByName("key3"), 3.0) .setField(postAttributesMsgDescriptor.findFieldByName("key4"), 4) - .setField(postAttributesMsgDescriptor.findFieldByName("key5"), "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}") + .setField(postAttributesMsgDescriptor.findFieldByName("key5"), jsonObject) .build(); processAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, expectedKeys, postAttributesMsg.toByteArray()); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java index b69b53856f..4cb2f0271c 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java @@ -62,6 +62,23 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_TELEMETRY_PROTO_SCHEMA); DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); + + DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); + Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); + assertNotNull(nestedJsonObjectBuilderDescriptor); + DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build(); + + DynamicMessage.Builder jsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject"); + Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType(); + assertNotNull(jsonObjectBuilderDescriptor); + DynamicMessage jsonObject = jsonObjectBuilder + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3) + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject) + .build(); + DynamicMessage.Builder postTelemetryBuilder = telemetrySchema.newMessageBuilder("PostTelemetry"); Descriptors.Descriptor postTelemetryMsgDescriptor = postTelemetryBuilder.getDescriptorForType(); assertNotNull(postTelemetryMsgDescriptor); @@ -70,7 +87,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac .setField(postTelemetryMsgDescriptor.findFieldByName("key2"), true) .setField(postTelemetryMsgDescriptor.findFieldByName("key3"), 3.0) .setField(postTelemetryMsgDescriptor.findFieldByName("key4"), 4) - .setField(postTelemetryMsgDescriptor.findFieldByName("key5"), "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}") + .setField(postTelemetryMsgDescriptor.findFieldByName("key5"), jsonObject) .build(); processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, postTelemetryMsg.toByteArray(), false); } @@ -80,19 +97,27 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac String schemaStr = "syntax =\"proto3\";\n" + "\n" + "package test;\n" + - " \n" + - "message PostTelemetry {\n" + - "\n" + - " message Values {\n" + - " string key1 = 1;\n" + - " bool key2 = 2;\n" + - " double key3 = 3;\n" + - " int32 key4 = 4;\n" + - " string key5 = 5;\n" + - " }\n" + "\n" + + "message PostTelemetry {\n" + " int64 ts = 1;\n" + " Values values = 2;\n" + + " \n" + + " message Values {\n" + + " string key1 = 3;\n" + + " bool key2 = 4;\n" + + " double key3 = 5;\n" + + " int32 key4 = 6;\n" + + " JsonObject key5 = 7;\n" + + " }\n" + + " \n" + + " message JsonObject {\n" + + " int32 someNumber = 8;\n" + + " repeated int32 someArray = 9;\n" + + " NestedJsonObject someNestedObject = 10;\n" + + " message NestedJsonObject {\n" + + " string key = 11;\n" + + " }\n" + + " }\n" + "}"; super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, DeviceProfileProvisionType.DISABLED, null, null); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); @@ -105,6 +130,23 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(schemaStr); DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); + DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); + Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); + assertNotNull(nestedJsonObjectBuilderDescriptor); + DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build(); + + DynamicMessage.Builder jsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject"); + Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType(); + assertNotNull(jsonObjectBuilderDescriptor); + DynamicMessage jsonObject = jsonObjectBuilder + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3) + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject) + .build(); + + DynamicMessage.Builder valuesBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.Values"); Descriptors.Descriptor valuesDescriptor = valuesBuilder.getDescriptorForType(); assertNotNull(valuesDescriptor); @@ -114,7 +156,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac .setField(valuesDescriptor.findFieldByName("key2"), true) .setField(valuesDescriptor.findFieldByName("key3"), 3.0) .setField(valuesDescriptor.findFieldByName("key4"), 4) - .setField(valuesDescriptor.findFieldByName("key5"), "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}") + .setField(valuesDescriptor.findFieldByName("key5"), jsonObject) .build(); DynamicMessage.Builder postTelemetryBuilder = telemetrySchema.newMessageBuilder("PostTelemetry"); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java index 30bc7b9237..08c846d905 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java @@ -128,19 +128,8 @@ public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeC List messageDefinitions = new ArrayList<>(); messageElementsList.forEach(messageElement -> { MessageDefinition.Builder messageDefinitionBuilder = MessageDefinition.newBuilder(messageElement.getName()); - List messageElementFields = messageElement.getFields(); - List oneOfs = messageElement.getOneOfs(); List nestedTypes = messageElement.getNestedTypes(); - if (!messageElementFields.isEmpty()) { - addMessageFieldsToTheMessageDefinition(messageElementFields, messageDefinitionBuilder); - } - if (!oneOfs.isEmpty()) { - for (OneOfElement oneOfelement : oneOfs) { - MessageDefinition.OneofBuilder oneofBuilder = messageDefinitionBuilder.addOneof(oneOfelement.getName()); - addMessageFieldsToTheOneOfDefinition(oneOfelement.getFields(), oneofBuilder); - } - } if (!nestedTypes.isEmpty()) { List nestedEnumTypes = getEnumElements(nestedTypes); if (!nestedEnumTypes.isEmpty()) { @@ -153,6 +142,17 @@ public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeC List nestedMessageDefinitions = getMessageDefinitions(nestedMessageTypes); nestedMessageDefinitions.forEach(messageDefinitionBuilder::addMessageDefinition); } + List messageElementFields = messageElement.getFields(); + List oneOfs = messageElement.getOneOfs(); + if (!oneOfs.isEmpty()) { + for (OneOfElement oneOfelement : oneOfs) { + MessageDefinition.OneofBuilder oneofBuilder = messageDefinitionBuilder.addOneof(oneOfelement.getName()); + addMessageFieldsToTheOneOfDefinition(oneOfelement.getFields(), oneofBuilder); + } + } + if (!messageElementFields.isEmpty()) { + addMessageFieldsToTheMessageDefinition(messageElementFields, messageDefinitionBuilder); + } messageDefinitions.add(messageDefinitionBuilder.build()); }); return messageDefinitions; From be094fc62a7b0df444344d3c5796f61b57c1caf9 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 16 Nov 2020 19:15:34 +0200 Subject: [PATCH 372/463] fix license --- .../server/dao/device/DeviceProfileServiceImpl.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index 6b54c7c60a..51de9a1bc9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -1,12 +1,12 @@ /** * Copyright © 2016-2020 The Thingsboard Authors - *

+ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

+ * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. From 8046f7ef45365a5bf0703dcc62d11f3687e614bd Mon Sep 17 00:00:00 2001 From: zbeacon Date: Fri, 13 Nov 2020 16:04:15 +0200 Subject: [PATCH 373/463] Changed device provisioning response and added ability to provision devices without deviceName --- .../device/DeviceProvisionServiceImpl.java | 6 +++ .../transport/DefaultTransportApiService.java | 38 ++++++++++--------- .../AbstractMqttProvisionJsonDeviceTest.java | 27 ++++--------- .../AbstractMqttProvisionProtoDeviceTest.java | 35 ++++++----------- common/queue/src/main/proto/queue.proto | 5 ++- .../transport/adaptor/JsonConverter.java | 18 ++++----- 6 files changed, 57 insertions(+), 72 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index d372b878f7..583139f05c 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -22,6 +22,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -188,6 +189,11 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName()); try { if (device == null) { + if (StringUtils.isEmpty(provisionRequest.getDeviceName())) { + String newDeviceName = RandomStringUtils.randomAlphanumeric(20); + log.info("Device name not found in provision request. Generated name is: {}", newDeviceName); + provisionRequest.setDeviceName(newDeviceName); + } Device savedDevice = deviceService.saveDevice(provisionRequest, profile); deviceStateService.onDeviceAdded(savedDevice); diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 5ceb9b3cd6..6006e2269e 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -54,12 +54,15 @@ import org.thingsboard.server.dao.device.provision.ProvisionResponse; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.DeviceCredentialsProto; import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsgOrBuilder; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; @@ -292,30 +295,29 @@ public class DefaultTransportApiService implements TransportApiService { requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret())))); } catch (ProvisionFailedException e) { return Futures.immediateFuture(getTransportApiResponseMsg( - TransportProtos.DeviceCredentialsProto.getDefaultInstance(), + new DeviceCredentials(), TransportProtos.ProvisionResponseStatus.valueOf(e.getMessage()))); } - return Futures.transform(provisionResponseFuture, provisionResponse -> getTransportApiResponseMsg( - getDeviceCredentials(provisionResponse.getDeviceCredentials()), TransportProtos.ProvisionResponseStatus.SUCCESS), + return Futures.transform(provisionResponseFuture, provisionResponse -> getTransportApiResponseMsg(provisionResponse.getDeviceCredentials(), TransportProtos.ProvisionResponseStatus.SUCCESS), dbCallbackExecutorService); } - private TransportApiResponseMsg getTransportApiResponseMsg(TransportProtos.DeviceCredentialsProto deviceCredentials, TransportProtos.ProvisionResponseStatus status) { - return TransportApiResponseMsg.newBuilder() - .setProvisionDeviceResponseMsg(TransportProtos.ProvisionDeviceResponseMsg.newBuilder() - .setDeviceCredentials(deviceCredentials) - .setProvisionResponseStatus(status) - .build()) - .build(); - } + private TransportApiResponseMsg getTransportApiResponseMsg(DeviceCredentials deviceCredentials, TransportProtos.ProvisionResponseStatus status) { + TransportProtos.ProvisionDeviceResponseMsg.Builder provisionResponse = TransportProtos.ProvisionDeviceResponseMsg.newBuilder() + .setCredentialsType(TransportProtos.CredentialsType.valueOf(deviceCredentials.getCredentialsType().name())) + .setStatus(status); + switch (deviceCredentials.getCredentialsType()){ + case ACCESS_TOKEN: + provisionResponse.setCredentialsValue(deviceCredentials.getCredentialsId()); + break; + case MQTT_BASIC: + case X509_CERTIFICATE: + provisionResponse.setCredentialsValue(deviceCredentials.getCredentialsValue()); + break; + } - private TransportProtos.DeviceCredentialsProto getDeviceCredentials(DeviceCredentials deviceCredentials) { - return TransportProtos.DeviceCredentialsProto.newBuilder() - .setDeviceIdMSB(deviceCredentials.getDeviceId().getId().getMostSignificantBits()) - .setDeviceIdLSB(deviceCredentials.getDeviceId().getId().getLeastSignificantBits()) - .setCredentialsType(TransportProtos.CredentialsType.valueOf(deviceCredentials.getCredentialsType().name())) - .setCredentialsId(deviceCredentials.getCredentialsId()) - .setCredentialsValue(deviceCredentials.getCredentialsValue() != null ? deviceCredentials.getCredentialsValue() : "") + return TransportApiResponseMsg.newBuilder() + .setProvisionDeviceResponseMsg(provisionResponse.build()) .build(); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java index 9f69df0307..a964ca29e3 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java @@ -98,7 +98,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); - Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("provisionDeviceStatus").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("status").getAsString()); } @@ -110,13 +110,11 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); } @@ -129,15 +127,13 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "ACCESS_TOKEN"); - Assert.assertEquals(deviceCredentials.getCredentialsId(), "test_token"); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsValue(), "test_token"); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); } @@ -150,12 +146,10 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "X509_CERTIFICATE"); String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); @@ -164,7 +158,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash); Assert.assertEquals(deviceCredentials.getCredentialsValue(), "testHash"); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); } @@ -177,12 +171,10 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "MQTT_BASIC"); Assert.assertEquals(deviceCredentials.getCredentialsId(), EncryptionUtil.getSha3Hash("|", "test_clientId", "test_username")); @@ -192,21 +184,18 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn mqttCredentials.setPassword("test_password"); Assert.assertEquals(deviceCredentials.getCredentialsValue(), JacksonUtil.toString(mqttCredentials)); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); } protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); - Assert.assertEquals(savedDevice.getId().toString(), response.get("deviceId").getAsString()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); } protected void processTestProvisioningWithBadKeyDevice() throws Exception { @@ -214,7 +203,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); - Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("provisionDeviceStatus").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("status").getAsString()); } protected TestMqttCallback createMqttClientAndPublish() throws Exception { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java index 76b27ffd23..9f74946cad 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java @@ -105,7 +105,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.DISABLED, null, null); ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); Assert.assertNotNull(result); - Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getProvisionResponseStatus().toString()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getStatus().toString()); } protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { @@ -115,13 +115,11 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); - Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); } protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { @@ -133,15 +131,13 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); - Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.ACCESS_TOKEN); Assert.assertEquals(deviceCredentials.getCredentialsId(), "test_token"); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); } protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception { @@ -153,12 +149,10 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); - Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.X509_CERTIFICATE); String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); @@ -167,7 +161,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash); Assert.assertEquals(deviceCredentials.getCredentialsValue(), "testHash"); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); } protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception { @@ -185,12 +179,10 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); - Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.MQTT_BASIC); Assert.assertEquals(deviceCredentials.getCredentialsId(), EncryptionUtil.getSha3Hash("|", "test_clientId", "test_username")); @@ -200,26 +192,23 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI mqttCredentials.setPassword("test_password"); Assert.assertEquals(deviceCredentials.getCredentialsValue(), JacksonUtil.toString(mqttCredentials)); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); } protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); - Assert.assertEquals(savedDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId()); - Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); } protected void processTestProvisioningWithBadKeyDevice() throws Exception { super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); - Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getProvisionResponseStatus().toString()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getStatus().toString()); } protected TestMqttCallback createMqttClientAndPublish() throws Exception { diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 4b5a6028e6..325a517988 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -269,8 +269,9 @@ message ProvisionDeviceCredentialsMsg { } message ProvisionDeviceResponseMsg { - DeviceCredentialsProto deviceCredentials = 1; - ProvisionResponseStatus provisionResponseStatus = 2; + ProvisionResponseStatus status = 1; + CredentialsType credentialsType = 2; + string credentialsValue = 3; } enum ProvisionResponseStatus { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 63c7277ac2..2fa3fd6a88 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -408,22 +408,20 @@ public class JsonConverter { private static JsonObject toJson(ProvisionDeviceResponseMsg payload, boolean toGateway, int requestId) { JsonObject result = new JsonObject(); - if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.NOT_FOUND) { + if (payload.getStatus() == TransportProtos.ProvisionResponseStatus.NOT_FOUND) { result.addProperty("errorMsg", "Provision data was not found!"); - result.addProperty("provisionDeviceStatus", ProvisionResponseStatus.NOT_FOUND.name()); - } else if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.FAILURE) { + result.addProperty("status", ProvisionResponseStatus.NOT_FOUND.name()); + } else if (payload.getStatus() == TransportProtos.ProvisionResponseStatus.FAILURE) { result.addProperty("errorMsg", "Failed to provision device!"); - result.addProperty("provisionDeviceStatus", ProvisionResponseStatus.FAILURE.name()); + result.addProperty("status", ProvisionResponseStatus.FAILURE.name()); } else { if (toGateway) { result.addProperty("id", requestId); } - result.addProperty("deviceId", new UUID(payload.getDeviceCredentials().getDeviceIdMSB(), payload.getDeviceCredentials().getDeviceIdLSB()).toString()); - result.addProperty("credentialsType", payload.getDeviceCredentials().getCredentialsType().name()); - result.addProperty("credentialsId", payload.getDeviceCredentials().getCredentialsId()); + result.addProperty("credentialsType", payload.getCredentialsType().name()); result.addProperty("credentialsValue", - StringUtils.isEmpty(payload.getDeviceCredentials().getCredentialsValue()) ? null : payload.getDeviceCredentials().getCredentialsValue()); - result.addProperty("provisionDeviceStatus", ProvisionResponseStatus.SUCCESS.name()); + StringUtils.isEmpty(payload.getCredentialsValue()) ? null : payload.getCredentialsValue()); + result.addProperty("status", ProvisionResponseStatus.SUCCESS.name()); } return result; } @@ -551,7 +549,7 @@ public class JsonConverter { private static TransportProtos.ProvisionDeviceRequestMsg buildProvisionRequestMsg(JsonObject jo) { return TransportProtos.ProvisionDeviceRequestMsg.newBuilder() - .setDeviceName(getStrValue(jo, DataConstants.DEVICE_NAME, true)) + .setDeviceName(getStrValue(jo, DataConstants.DEVICE_NAME, false)) .setCredentialsType(jo.get(DataConstants.CREDENTIALS_TYPE) != null ? TransportProtos.CredentialsType.valueOf(getStrValue(jo, DataConstants.CREDENTIALS_TYPE, false)) : CredentialsType.ACCESS_TOKEN) .setCredentialsDataProto(TransportProtos.CredentialsDataProto.newBuilder() .setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken(getStrValue(jo, DataConstants.TOKEN, false)).build()) From 16e1340c642afd6bccecbcfc1fd76ee3fc6f94dc Mon Sep 17 00:00:00 2001 From: zbeacon Date: Fri, 13 Nov 2020 16:21:46 +0200 Subject: [PATCH 374/463] Added correct response when provisioning failed --- .../server/service/transport/DefaultTransportApiService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 6006e2269e..d446b32e41 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -63,6 +63,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResp import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsgOrBuilder; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionResponseStatus; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; @@ -303,6 +304,9 @@ public class DefaultTransportApiService implements TransportApiService { } private TransportApiResponseMsg getTransportApiResponseMsg(DeviceCredentials deviceCredentials, TransportProtos.ProvisionResponseStatus status) { + if (!status.equals(ProvisionResponseStatus.SUCCESS)) { + return TransportApiResponseMsg.newBuilder().setProvisionDeviceResponseMsg(TransportProtos.ProvisionDeviceResponseMsg.newBuilder().setStatus(status).build()).build(); + } TransportProtos.ProvisionDeviceResponseMsg.Builder provisionResponse = TransportProtos.ProvisionDeviceResponseMsg.newBuilder() .setCredentialsType(TransportProtos.CredentialsType.valueOf(deviceCredentials.getCredentialsType().name())) .setStatus(status); From 6a6bd6117b572083b5f9950c413d570c8e1c32e4 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Fri, 13 Nov 2020 16:49:17 +0200 Subject: [PATCH 375/463] Fix for test --- .../mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java index a964ca29e3..6f96ae1034 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java @@ -132,7 +132,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "ACCESS_TOKEN"); - Assert.assertEquals(deviceCredentials.getCredentialsValue(), "test_token"); + Assert.assertEquals(deviceCredentials.getCredentialsId(), "test_token"); Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); } From a2afde6261083bbecd6901b87b7ee01d3a5e848b Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 17 Nov 2020 10:57:02 +0200 Subject: [PATCH 376/463] Changed credentials value for device mqtt basic auth --- .../server/common/transport/adaptor/JsonConverter.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 2fa3fd6a88..912514d0fc 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -418,9 +418,14 @@ public class JsonConverter { if (toGateway) { result.addProperty("id", requestId); } + switch (payload.getCredentialsType()) { + case ACCESS_TOKEN: + case X509_CERTIFICATE: + result.addProperty("credentialsValue", payload.getCredentialsValue()); + case MQTT_BASIC: + result.add("credentialsValue", JSON_PARSER.parse(payload.getCredentialsValue()).getAsJsonObject()); + } result.addProperty("credentialsType", payload.getCredentialsType().name()); - result.addProperty("credentialsValue", - StringUtils.isEmpty(payload.getCredentialsValue()) ? null : payload.getCredentialsValue()); result.addProperty("status", ProvisionResponseStatus.SUCCESS.name()); } return result; From 6758a29c127005de70a62352374a8540946da9eb Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 17 Nov 2020 11:03:42 +0200 Subject: [PATCH 377/463] Refactoring --- .../server/common/transport/adaptor/JsonConverter.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 912514d0fc..a20f697bba 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -422,8 +422,10 @@ public class JsonConverter { case ACCESS_TOKEN: case X509_CERTIFICATE: result.addProperty("credentialsValue", payload.getCredentialsValue()); + break; case MQTT_BASIC: result.add("credentialsValue", JSON_PARSER.parse(payload.getCredentialsValue()).getAsJsonObject()); + break; } result.addProperty("credentialsType", payload.getCredentialsType().name()); result.addProperty("status", ProvisionResponseStatus.SUCCESS.name()); From 71324536a30b11a1cdf46af48300686345107ca1 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 17 Nov 2020 15:01:40 +0200 Subject: [PATCH 378/463] Additional Kafka settings and logging of time consuming messages --- .../queue/DefaultTbCoreConsumerService.java | 74 ++++++++++++------- .../src/main/resources/thingsboard.yml | 1 + .../queue/kafka/TbKafkaConsumerTemplate.java | 7 +- .../server/queue/kafka/TbKafkaSettings.java | 5 ++ 4 files changed, 55 insertions(+), 32 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index f56e08b10f..4406ff9ade 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.service.queue; +import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; @@ -76,6 +78,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Collectors; @@ -175,39 +178,48 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> ctx = new TbPackProcessingContext<>( processingTimeoutLatch, pendingMap, new ConcurrentHashMap<>()); - pendingMap.forEach((id, msg) -> { - log.trace("[{}] Creating main callback for message: {}", id, msg.getValue()); - TbCallback callback = new TbPackCallback<>(id, ctx); - try { - ToCoreMsg toCoreMsg = msg.getValue(); - if (toCoreMsg.hasToSubscriptionMgrMsg()) { - log.trace("[{}] Forwarding message to subscription manager service {}", id, toCoreMsg.getToSubscriptionMgrMsg()); - forwardToSubMgrService(toCoreMsg.getToSubscriptionMgrMsg(), callback); - } else if (toCoreMsg.hasToDeviceActorMsg()) { - log.trace("[{}] Forwarding message to device actor {}", id, toCoreMsg.getToDeviceActorMsg()); - forwardToDeviceActor(toCoreMsg.getToDeviceActorMsg(), callback); - } else if (toCoreMsg.hasDeviceStateServiceMsg()) { - log.trace("[{}] Forwarding message to state service {}", id, toCoreMsg.getDeviceStateServiceMsg()); - forwardToStateService(toCoreMsg.getDeviceStateServiceMsg(), callback); - } else if (toCoreMsg.getToDeviceActorNotificationMsg() != null && !toCoreMsg.getToDeviceActorNotificationMsg().isEmpty()) { - Optional actorMsg = encodingService.decode(toCoreMsg.getToDeviceActorNotificationMsg().toByteArray()); - if (actorMsg.isPresent()) { - TbActorMsg tbActorMsg = actorMsg.get(); - if (tbActorMsg.getMsgType().equals(MsgType.DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG)) { - tbCoreDeviceRpcService.forwardRpcRequestToDeviceActor((ToDeviceRpcRequestActorMsg) tbActorMsg); - } else { - log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get()); - actorContext.tell(actorMsg.get()); + PendingMsgHolder pendingMsgHolder = new PendingMsgHolder(); + Future packSubmitFuture = consumersExecutor.submit(() -> { + pendingMap.forEach((id, msg) -> { + log.trace("[{}] Creating main callback for message: {}", id, msg.getValue()); + TbCallback callback = new TbPackCallback<>(id, ctx); + try { + ToCoreMsg toCoreMsg = msg.getValue(); + pendingMsgHolder.setToCoreMsg(toCoreMsg); + if (toCoreMsg.hasToSubscriptionMgrMsg()) { + log.trace("[{}] Forwarding message to subscription manager service {}", id, toCoreMsg.getToSubscriptionMgrMsg()); + forwardToSubMgrService(toCoreMsg.getToSubscriptionMgrMsg(), callback); + } else if (toCoreMsg.hasToDeviceActorMsg()) { + log.trace("[{}] Forwarding message to device actor {}", id, toCoreMsg.getToDeviceActorMsg()); + forwardToDeviceActor(toCoreMsg.getToDeviceActorMsg(), callback); + } else if (toCoreMsg.hasDeviceStateServiceMsg()) { + log.trace("[{}] Forwarding message to state service {}", id, toCoreMsg.getDeviceStateServiceMsg()); + forwardToStateService(toCoreMsg.getDeviceStateServiceMsg(), callback); + } else if (toCoreMsg.getToDeviceActorNotificationMsg() != null && !toCoreMsg.getToDeviceActorNotificationMsg().isEmpty()) { + Optional actorMsg = encodingService.decode(toCoreMsg.getToDeviceActorNotificationMsg().toByteArray()); + if (actorMsg.isPresent()) { + TbActorMsg tbActorMsg = actorMsg.get(); + if (tbActorMsg.getMsgType().equals(MsgType.DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG)) { + tbCoreDeviceRpcService.forwardRpcRequestToDeviceActor((ToDeviceRpcRequestActorMsg) tbActorMsg); + } else { + log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get()); + actorContext.tell(actorMsg.get()); + } } + callback.onSuccess(); } - callback.onSuccess(); + } catch (Throwable e) { + log.warn("[{}] Failed to process message: {}", id, msg, e); + callback.onFailure(e); } - } catch (Throwable e) { - log.warn("[{}] Failed to process message: {}", id, msg, e); - callback.onFailure(e); - } + }); }); if (!processingTimeoutLatch.await(packProcessingTimeout, TimeUnit.MILLISECONDS)) { + if (!packSubmitFuture.isDone()) { + packSubmitFuture.cancel(true); + ToCoreMsg lastSubmitMsg = pendingMsgHolder.getToCoreMsg(); + log.info("Timeout to process message: {}", lastSubmitMsg); + } ctx.getAckMap().forEach((id, msg) -> log.debug("[{}] Timeout to process message: {}", id, msg.getValue())); ctx.getFailedMap().forEach((id, msg) -> log.warn("[{}] Failed to process message: {}", id, msg.getValue())); } @@ -227,6 +239,12 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService extends AbstractTbQue private TbKafkaConsumerTemplate(TbKafkaSettings settings, TbKafkaDecoder decoder, String clientId, String groupId, String topic, boolean autoCommit, int autoCommitIntervalMs, - int maxPollRecords, TbQueueAdmin admin) { super(topic); Properties props = settings.toProps(); @@ -54,6 +53,9 @@ public class TbKafkaConsumerTemplate extends AbstractTbQue if (groupId != null) { props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId); } + if (settings.getMaxPollIntervalMs() > 0) { + props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, settings.getMaxPollIntervalMs()); + } props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, settings.getMaxPollRecords()); props.put(ConsumerConfig.MAX_PARTITION_FETCH_BYTES_CONFIG, settings.getMaxPartitionFetchBytes()); props.put(ConsumerConfig.FETCH_MAX_BYTES_CONFIG, settings.getFetchMaxBytes()); @@ -61,9 +63,6 @@ public class TbKafkaConsumerTemplate extends AbstractTbQue props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, autoCommitIntervalMs); props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer"); props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.ByteArrayDeserializer"); - if (maxPollRecords > 0) { - props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, maxPollRecords); - } this.admin = admin; this.consumer = new KafkaConsumer<>(props); this.decoder = decoder; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java index 37e978c07d..15057d64d3 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java @@ -63,6 +63,10 @@ public class TbKafkaSettings { @Getter private int maxPollRecords; + @Value("${queue.kafka.max_poll_interval_ms:0}") + @Getter + private int maxPollIntervalMs; + @Value("${queue.kafka.max_partition_fetch_bytes:16777216}") @Getter private int maxPartitionFetchBytes; @@ -111,4 +115,5 @@ public class TbKafkaSettings { } return props; } + } From d0c39416b66b0f4ee68313abd6bad44a13f30232 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 17 Nov 2020 17:49:32 +0200 Subject: [PATCH 379/463] UI: Added copied button device provision parameters in device profile --- ...ile-provision-configuration.component.html | 16 ++++++++++++++++ ...ofile-provision-configuration.component.ts | 19 ++++++++++++++++++- .../assets/locale/locale.constant-en_US.json | 4 ++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html index 2e9299f0cf..9af62c421d 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html @@ -31,6 +31,14 @@ device-profile.provision-device-key + {{ 'device-profile.provision-device-key-required' | translate }} @@ -38,6 +46,14 @@ device-profile.provision-device-secret + {{ 'device-profile.provision-device-secret-required' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index e25c07e005..393aadb80c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -33,6 +33,10 @@ import { deviceProvisionTypeTranslationMap } from '@shared/models/device.models'; import { generateSecret, isDefinedAndNotNull } from '@core/utils'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'tb-device-profile-provision-configuration', @@ -73,7 +77,9 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu private propagateChange = (v: any) => { }; - constructor(private fb: FormBuilder) { + constructor(protected store: Store, + private fb: FormBuilder, + private translate: TranslateService) { } ngOnInit(): void { @@ -149,4 +155,15 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu } this.propagateChange(deviceProvisionConfiguration); } + + onProvisionCopied(isKey: boolean) { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant(isKey ? 'device-profile.provision-key-copied-message' : 'device-profile.provision-secret-copied-message'), + type: 'success', + duration: 1200, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); + } } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index ad79c56968..cddc73415e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -950,8 +950,12 @@ "provision-strategy-check-pre-provisioned": "Check for pre-provisioned devices", "provision-device-key": "Provision device key", "provision-device-key-required": "Provision device key is required.", + "copy-provision-key": "Copy provision key", + "provision-key-copied-message": "Provision key has been copied to clipboard", "provision-device-secret": "Provision device secret", "provision-device-secret-required": "Provision device secret is required.", + "copy-provision-secret": "Copy provision secret", + "provision-secret-copied-message": "Provision secret has been copied to clipboard", "condition": "Condition", "condition-type": "Condition type", "condition-type-simple": "Simple", From abb3c472396b5e1d4387384033ea4b9c76361ba0 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 17 Nov 2020 19:06:38 +0200 Subject: [PATCH 380/463] UI: Fixed didn't show alarm detail key --- .../home/components/widget/lib/table-widget.models.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts index 1ab9ea6fc4..53a9b17e4e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts @@ -184,10 +184,13 @@ export function getEntityValue(entity: any, key: DataKey): any { export function getAlarmValue(alarm: AlarmDataInfo, key: EntityColumn) { let alarmField = null; if (key.type === DataKeyType.alarm) { - alarmField = alarmFields[key.name]; + alarmField = alarmFields[key.name]?.value; + if (!alarmField && key.name.startsWith('details.')) { + alarmField = key.name; + } } if (alarmField) { - return getDescendantProp(alarm, alarmField.value); + return getDescendantProp(alarm, alarmField); } else { return getDescendantProp(alarm, key.label); } From 08693a407de00e3583a96edbad1754b942df92be Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 18 Nov 2020 13:36:34 +0200 Subject: [PATCH 381/463] UI: Refactoring device-profile payload type protobuf; Refactoring view --- .../profile/device-profile.component.ts | 4 +- ...ile-transport-configuration.component.html | 134 +++++++++--------- ...ofile-transport-configuration.component.ts | 96 +++++++------ .../device-profile-tabs.component.html | 6 +- 4 files changed, 123 insertions(+), 117 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts index 8c521dcd77..862b0cff2f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts @@ -88,7 +88,7 @@ export class DeviceProfileComponent extends EntityComponent { this.displayTransportConfiguration = entity && entity.transportType && deviceTransportTypeConfigurationInfoMap.get(entity.transportType).hasProfileConfiguration; const deviceProvisionConfiguration: DeviceProvisionConfiguration = { - type: entity?.provisionType ? entity?.provisionType : DeviceProvisionType.DISABLED, + type: entity?.provisionType ? entity.provisionType : DeviceProvisionType.DISABLED, provisionDeviceKey: entity?.provisionDeviceKey, provisionDeviceSecret: entity?.profileData?.provisionConfiguration?.provisionDeviceSecret }; @@ -162,7 +162,7 @@ export class DeviceProfileComponent extends EntityComponent { this.displayTransportConfiguration = entity.transportType && deviceTransportTypeConfigurationInfoMap.get(entity.transportType).hasProfileConfiguration; const deviceProvisionConfiguration: DeviceProvisionConfiguration = { - type: entity?.provisionType ? entity?.provisionType : DeviceProvisionType.DISABLED, + type: entity?.provisionType ? entity.provisionType : DeviceProvisionType.DISABLED, provisionDeviceKey: entity?.provisionDeviceKey, provisionDeviceSecret: entity?.profileData?.provisionConfiguration?.provisionDeviceSecret }; diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html index c855cfda97..a52826bc4f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html @@ -16,86 +16,80 @@ -->

-
+
+ device-profile.mqtt-device-topic-filters +
+
+ + device-profile.telemetry-topic-filter + + + {{ 'device-profile.telemetry-topic-filter-required' | translate}} + + + {{ 'device-profile.not-valid-single-character' | translate}} + + + {{ 'device-profile.not-valid-multi-character' | translate}} + + + + device-profile.attributes-topic-filter + + + {{ 'device-profile.attributes-topic-filter-required' | translate}} + + + {{ 'device-profile.not-valid-single-character' | translate}} + + + {{ 'device-profile.not-valid-multi-character' | translate}} + + +
+ + {{ 'device-profile.mqtt-device-topic-filters-unique' | translate }} + +
+
+
+
+
+
- device-profile.mqtt-device-topic-filters + device-profile.mqtt-device-payload-type
-
+ + + + {{mqttTransportPayloadTypeTranslations.get(type) | translate}} + + + + {{ 'device-profile.mqtt-payload-type-required' | translate }} + + +
- device-profile.telemetry-topic-filter - - - {{ 'device-profile.telemetry-topic-filter-required' | translate}} - - - {{ 'device-profile.not-valid-single-character' | translate}} - - - {{ 'device-profile.not-valid-multi-character' | translate}} + device-profile.telemetry-proto-schema + + + {{ 'device-profile.telemetry-proto-schema-required' | translate}} - device-profile.attributes-topic-filter - - - {{ 'device-profile.attributes-topic-filter-required' | translate}} - - - {{ 'device-profile.not-valid-single-character' | translate}} - - - {{ 'device-profile.not-valid-multi-character' | translate}} + device-profile.attributes-proto-schema + + + {{ 'device-profile.attributes-proto-schema-required' | translate}}
- - {{ 'device-profile.mqtt-device-topic-filters-unique' | translate }} - -
-
-
-
-
- device-profile.mqtt-device-payload-type -
- - - - {{mqttTransportPayloadTypeTranslations.get(type) | translate}} - - - - {{ 'device-profile.mqtt-payload-type-required' | translate }} - - -
- - device-profile.telemetry-proto-schema - - - {{ 'device-profile.telemetry-proto-schema-required' | translate}} - - - - device-profile.attributes-proto-schema - - - {{ 'device-profile.attributes-proto-schema-required' | translate}} - - -
-
-
-
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index a5d4ced861..5d26b9f8c8 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -54,6 +54,34 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control mqttDeviceProfileTransportConfigurationFormGroup: FormGroup; + private defaultTelemetrySchema = + 'syntax ="proto3";\n' + + 'package telemetry;\n' + + '\n' + + 'message SensorDataReading {\n' + + '\n' + + ' double temperature = 1;\n' + + ' double humidity = 2;\n' + + ' InnerObject innerObject = 3;\n' + + '\n' + + ' message InnerObject {\n' + + ' string key1 = 1;\n' + + ' bool key2 = 2;\n' + + ' double key3 = 3;\n' + + ' int32 key4 = 4;\n' + + ' string key5 = 5;\n' + + ' }\n' + + '}\n'; + + private defaultAttributesSchema = + 'syntax ="proto3";\n' + + 'package attributes;\n' + + '\n' + + 'message SensorConfiguration {\n' + + ' string firmwareVersion = 1;\n' + + ' string serialNumber = 2;\n' + + '}'; + private requiredValue: boolean; get required(): boolean { @@ -83,17 +111,18 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control ngOnInit() { this.mqttDeviceProfileTransportConfigurationFormGroup = this.fb.group({ - configuration: this.fb.group({ deviceAttributesTopic: [null, [Validators.required, this.validationMQTTTopic()]], deviceTelemetryTopic: [null, [Validators.required, this.validationMQTTTopic()]], transportPayloadTypeConfiguration: this.fb.group({ - transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required] + transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required], + deviceTelemetryProtoSchema: [this.defaultTelemetrySchema, Validators.required], + deviceAttributesProtoSchema: [this.defaultAttributesSchema, Validators.required] }) - }, {validator: this.uniqueDeviceTopicValidator}) - }); - this.mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration.transportPayloadType').valueChanges.subscribe(payloadType => { - this.updateTransportPayloadBasedControls(payloadType); - this.mqttDeviceProfileTransportConfigurationFormGroup.updateValueAndValidity(); + }, {validator: this.uniqueDeviceTopicValidator} + ); + this.mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.transportPayloadType') + .valueChanges.subscribe(payloadType => { + this.updateTransportPayloadBasedControls(payloadType, true); }); this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { this.updateModel(); @@ -110,58 +139,41 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control } get protoPayloadType(): boolean { - let transportPayloadType = this.mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration.transportPayloadType').value; + const transportPayloadType = this.mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.transportPayloadType').value; return transportPayloadType === MqttTransportPayloadType.PROTOBUF; } writeValue(value: MqttDeviceProfileTransportConfiguration | null): void { if (isDefinedAndNotNull(value)) { - this.updateTransportPayloadBasedControls(value.transportPayloadTypeConfiguration.transportPayloadType); - this.mqttDeviceProfileTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); + this.mqttDeviceProfileTransportConfigurationFormGroup.patchValue(value, {emitEvent: false}); + this.updateTransportPayloadBasedControls(value.transportPayloadTypeConfiguration?.transportPayloadType); } } private updateModel() { let configuration: DeviceProfileTransportConfiguration = null; if (this.mqttDeviceProfileTransportConfigurationFormGroup.valid) { - configuration = this.mqttDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration; + configuration = this.mqttDeviceProfileTransportConfigurationFormGroup.value; configuration.type = DeviceTransportType.MQTT; } this.propagateChange(configuration); } - private updateTransportPayloadBasedControls(type: MqttTransportPayloadType) { - const transportPayloadTypeConfigurationFormGroup = this.mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration') as FormGroup; - if (type === MqttTransportPayloadType.PROTOBUF) { - const defaultTelemetrySchema = "syntax =\"proto3\";\n" + - "package telemetry;\n" + - "\n" + - "message SensorDataReading {\n" + - "\n" + - " double temperature = 1;\n" + - " double humidity = 2;\n" + - " InnerObject innerObject = 3;\n" + - "\n" + - " message InnerObject {\n" + - " string key1 = 1;\n" + - " bool key2 = 2;\n" + - " double key3 = 3;\n" + - " int32 key4 = 4;\n" + - " string key5 = 5;\n" + - " }\n" + - "}\n"; - const defaultAttributesSchema = "syntax =\"proto3\";\n" + - "package attributes;\n" + - "\n" + - "message SensorConfiguration {\n" + - " string firmwareVersion = 1;\n" + - " string serialNumber = 2;\n" + - "}"; - transportPayloadTypeConfigurationFormGroup.registerControl('deviceTelemetryProtoSchema', this.fb.control(defaultTelemetrySchema, Validators.required)); - transportPayloadTypeConfigurationFormGroup.registerControl('deviceAttributesProtoSchema', this.fb.control(defaultAttributesSchema, Validators.required)); + private updateTransportPayloadBasedControls(type: MqttTransportPayloadType, forceUpdated = false) { + const transportPayloadTypeForm = this.mqttDeviceProfileTransportConfigurationFormGroup + .get('transportPayloadTypeConfiguration') as FormGroup; + if (forceUpdated) { + transportPayloadTypeForm.patchValue({ + deviceTelemetryProtoSchema: this.defaultTelemetrySchema, + deviceAttributesProtoSchema: this.defaultAttributesSchema + }, {emitEvent: false}); + } + if (type === MqttTransportPayloadType.PROTOBUF && !this.disabled) { + transportPayloadTypeForm.get('deviceTelemetryProtoSchema').enable({emitEvent: false}); + transportPayloadTypeForm.get('deviceAttributesProtoSchema').enable({emitEvent: false}); } else { - transportPayloadTypeConfigurationFormGroup.removeControl('deviceTelemetryProtoSchema'); - transportPayloadTypeConfigurationFormGroup.removeControl('deviceAttributesProtoSchema'); + transportPayloadTypeForm.get('deviceTelemetryProtoSchema').disable({emitEvent: false}); + transportPayloadTypeForm.get('deviceAttributesProtoSchema').disable({emitEvent: false}); } } diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html index a803d35b1d..a16cee1d3a 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html @@ -41,9 +41,9 @@ + label="{{'device-profile.alarm-rules-with-count' | translate: + {count: entity.profileData?.alarms?.length ? entity.profileData.alarms.length : 0} + }}" #alarmRules="matTab">
From 986cfe5cc16aff47ec8a5fe1d3a51a2b34a41e17 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 18 Nov 2020 15:59:11 +0200 Subject: [PATCH 382/463] Improve mqtt timeseries integration tests --- ...AbstractMqttTimeseriesIntegrationTest.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java index 9076edc546..64ba73c374 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java @@ -127,7 +127,34 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt } else { getTelemetryValuesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?keys=" + String.join(",", actualKeySet); } - Map>> values = doGetAsync(getTelemetryValuesUrl, Map.class); + start = System.currentTimeMillis(); + end = System.currentTimeMillis() + 5000; + Map>> values = null; + while (start <= end) { + values = doGetAsync(getTelemetryValuesUrl, Map.class); + boolean valid = values.size() == expectedKeys.size(); + if (valid) { + for (String key : expectedKeys) { + List> tsValues = values.get(key); + if (tsValues != null && tsValues.size() > 0) { + Object ts = tsValues.get(0).get("ts"); + if (ts == null) { + valid = false; + break; + } + } else { + valid = false; + break; + } + } + } + if (valid) { + break; + } + Thread.sleep(100); + start += 100; + } + assertNotNull(values); if (withTs) { assertTs(values, expectedKeys, 10000, 0); From 0fff85121052c4b38fe1d01cc3b0d850062b87b9 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 19 Nov 2020 11:26:51 +0200 Subject: [PATCH 383/463] Alarm Query fix for propagated alarms with entities assigned to customer --- .../query/DefaultAlarmQueryRepository.java | 40 ++++++++++--------- .../sql/query/DefaultQueryLogComponent.java | 4 +- .../rule/engine/profile/AlarmState.java | 6 ++- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java index e3aa614bfe..2c0d67513f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java @@ -130,7 +130,6 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { AlarmDataPageLink pageLink = query.getPageLink(); QueryContext ctx = new QueryContext(new QuerySecurityContext(tenantId, customerId, EntityType.ALARM)); ctx.addUuidListParameter("entity_ids", orderedEntityIds.stream().map(EntityId::getId).collect(Collectors.toList())); - StringBuilder selectPart = new StringBuilder(FIELDS_SELECTION); StringBuilder fromPart = new StringBuilder(" from alarm a "); StringBuilder wherePart = new StringBuilder(" where "); @@ -282,24 +281,27 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { StringBuilder permissionsQuery = new StringBuilder(); ctx.addUuidParameter("permissions_tenant_id", tenantId.getId()); permissionsQuery.append(" a.tenant_id = :permissions_tenant_id "); - if (customerId != null && !customerId.isNullUid()) { - ctx.addUuidParameter("permissions_customer_id", customerId.getId()); - ctx.addUuidParameter("permissions_device_customer_id", customerId.getId()); - ctx.addUuidParameter("permissions_asset_customer_id", customerId.getId()); - ctx.addUuidParameter("permissions_user_customer_id", customerId.getId()); - ctx.addUuidParameter("permissions_entity_view_customer_id", customerId.getId()); - permissionsQuery.append(" and ("); - permissionsQuery.append("(a.originator_type = '").append(EntityType.DEVICE.ordinal()).append("' and exists (select 1 from device cd where cd.id = a.originator_id and cd.customer_id = :permissions_device_customer_id))"); - permissionsQuery.append(" or "); - permissionsQuery.append("(a.originator_type = '").append(EntityType.ASSET.ordinal()).append("' and exists (select 1 from asset ca where ca.id = a.originator_id and ca.customer_id = :permissions_device_customer_id))"); - permissionsQuery.append(" or "); - permissionsQuery.append("(a.originator_type = '").append(EntityType.CUSTOMER.ordinal()).append("' and exists (select 1 from customer cc where cc.id = a.originator_id and cc.id = :permissions_customer_id))"); - permissionsQuery.append(" or "); - permissionsQuery.append("(a.originator_type = '").append(EntityType.USER.ordinal()).append("' and exists (select 1 from tb_user cu where cu.id = a.originator_id and cu.customer_id = :permissions_user_customer_id))"); - permissionsQuery.append(" or "); - permissionsQuery.append("(a.originator_type = '").append(EntityType.ENTITY_VIEW.ordinal()).append("' and exists (select 1 from entity_view cv where cv.id = a.originator_id and cv.customer_id = :permissions_entity_view_customer_id))"); - permissionsQuery.append(")"); - } +/* + No need to check the customer id, because we already use entity id list that passed security check when we were evaluating the data query. + */ +// if (customerId != null && !customerId.isNullUid()) { +// ctx.addUuidParameter("permissions_customer_id", customerId.getId()); +// ctx.addUuidParameter("permissions_device_customer_id", customerId.getId()); +// ctx.addUuidParameter("permissions_asset_customer_id", customerId.getId()); +// ctx.addUuidParameter("permissions_user_customer_id", customerId.getId()); +// ctx.addUuidParameter("permissions_entity_view_customer_id", customerId.getId()); +// permissionsQuery.append(" and ("); +// permissionsQuery.append("(a.originator_type = '").append(EntityType.DEVICE.ordinal()).append("' and exists (select 1 from device cd where cd.id = a.originator_id and cd.customer_id = :permissions_device_customer_id))"); +// permissionsQuery.append(" or "); +// permissionsQuery.append("(a.originator_type = '").append(EntityType.ASSET.ordinal()).append("' and exists (select 1 from asset ca where ca.id = a.originator_id and ca.customer_id = :permissions_device_customer_id))"); +// permissionsQuery.append(" or "); +// permissionsQuery.append("(a.originator_type = '").append(EntityType.CUSTOMER.ordinal()).append("' and exists (select 1 from customer cc where cc.id = a.originator_id and cc.id = :permissions_customer_id))"); +// permissionsQuery.append(" or "); +// permissionsQuery.append("(a.originator_type = '").append(EntityType.USER.ordinal()).append("' and exists (select 1 from tb_user cu where cu.id = a.originator_id and cu.customer_id = :permissions_user_customer_id))"); +// permissionsQuery.append(" or "); +// permissionsQuery.append("(a.originator_type = '").append(EntityType.ENTITY_VIEW.ordinal()).append("' and exists (select 1 from entity_view cv where cv.id = a.originator_id and cv.customer_id = :permissions_entity_view_customer_id))"); +// permissionsQuery.append(")"); +// } return permissionsQuery.toString(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java index 7cece0cf3a..62c8aed686 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java @@ -32,9 +32,9 @@ public class DefaultQueryLogComponent implements QueryLogComponent { @Override public void logQuery(QueryContext ctx, String query, long duration) { - if (logSqlQueries && duration > logQueriesThreshold) { +// if (logSqlQueries && duration > logQueriesThreshold) { log.info("QUERY: {} took {}ms", query, duration); Arrays.asList(ctx.getParameterNames()).forEach(param -> log.info("QUERY PARAM: {} -> {}", param, ctx.getValue(param))); - } +// } } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 81622b6b93..0a501a0890 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -123,8 +123,10 @@ class AlarmState { } public boolean clearAlarmState(boolean stateUpdate, AlarmRuleState state) { - state.clear(); - stateUpdate |= state.checkUpdate(); + if (state != null) { + state.clear(); + stateUpdate |= state.checkUpdate(); + } return stateUpdate; } From 5f8e5925956f145025a824f07edd2dd084987cda Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 19 Nov 2020 11:33:51 +0200 Subject: [PATCH 384/463] Reverted changes to the logging from previous commit --- .../server/dao/sql/query/DefaultQueryLogComponent.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java index 62c8aed686..7cece0cf3a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java @@ -32,9 +32,9 @@ public class DefaultQueryLogComponent implements QueryLogComponent { @Override public void logQuery(QueryContext ctx, String query, long duration) { -// if (logSqlQueries && duration > logQueriesThreshold) { + if (logSqlQueries && duration > logQueriesThreshold) { log.info("QUERY: {} took {}ms", query, duration); Arrays.asList(ctx.getParameterNames()).forEach(param -> log.info("QUERY PARAM: {} -> {}", param, ctx.getValue(param))); -// } + } } } From 0fb1147ab2efbfe6ca2d6382b81a964a70b870bf Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 19 Nov 2020 12:04:32 +0200 Subject: [PATCH 385/463] Alarm Query Tests improved --- .../dao/service/BaseAlarmServiceTest.java | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java index 15bd4eb905..16e895a869 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; @@ -45,6 +46,7 @@ import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.dao.alarm.AlarmOperationResult; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; import java.util.Arrays; import java.util.Collections; @@ -265,7 +267,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { PageData tenantAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, new CustomerId(CustomerId.NULL_UUID), toQuery(pageLink), Arrays.asList(tenantDevice.getId(), customerDevice.getId())); Assert.assertEquals(2, tenantAlarms.getData().size()); - PageData customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, customer.getId(), toQuery(pageLink), Arrays.asList(tenantDevice.getId(), customerDevice.getId())); + PageData customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, customer.getId(), toQuery(pageLink), Collections.singletonList(customerDevice.getId())); Assert.assertEquals(1, customerAlarms.getData().size()); Assert.assertEquals(deviceAlarm, customerAlarms.getData().get(0)); @@ -280,6 +282,70 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { Assert.assertEquals(tenantAlarm, alarms.getData().get(0)); } + @Test + public void testFindPopagatedCustomerAlarm() throws ExecutionException, InterruptedException { + Customer customer = new Customer(); + customer.setTitle("TestCustomer"); + customer.setTenantId(tenantId); + customer = customerService.saveCustomer(customer); + + Device tenantDevice = new Device(); + tenantDevice.setName("TestTenantDevice"); + tenantDevice.setType("default"); + tenantDevice.setTenantId(tenantId); + tenantDevice = deviceService.saveDevice(tenantDevice); + + Asset customerAsset = new Asset(); + customerAsset.setName("TestCustomerDevice"); + customerAsset.setType("default"); + customerAsset.setTenantId(tenantId); + customerAsset.setCustomerId(customer.getId()); + customerAsset = assetService.saveAsset(customerAsset); + + EntityRelation relation = new EntityRelation(); + relation.setFrom(customerAsset.getId()); + relation.setTo(tenantDevice.getId()); + relation.setAdditionalInfo(JacksonUtil.newObjectNode()); + relation.setType("Contains"); + relation.setTypeGroup(RelationTypeGroup.COMMON); + relationService.saveRelation(tenantId, relation); + + long ts = System.currentTimeMillis(); + Alarm tenantAlarm = Alarm.builder().tenantId(tenantId) + .originator(tenantDevice.getId()) + .type("Not Propagated") + .propagate(false) + .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK) + .startTs(ts).build(); + AlarmOperationResult result = alarmService.createOrUpdateAlarm(tenantAlarm); + tenantAlarm = result.getAlarm(); + + Alarm customerAlarm = Alarm.builder().tenantId(tenantId) + .originator(tenantDevice.getId()) + .type("Propagated") + .propagate(true) + .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK) + .startTs(ts).build(); + result = alarmService.createOrUpdateAlarm(customerAlarm); + customerAlarm = result.getAlarm(); + + AlarmDataPageLink pageLink = new AlarmDataPageLink(); + pageLink.setPage(0); + pageLink.setPageSize(10); + pageLink.setSortOrder(new EntityDataSortOrder(new EntityKey(EntityKeyType.ALARM_FIELD, "createdTime"))); + + pageLink.setStartTs(0L); + pageLink.setEndTs(System.currentTimeMillis()); + pageLink.setSearchPropagatedAlarms(true); + pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); + pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE)); + + //TEST that propagated alarms are visible on the asset level. + PageData customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, customer.getId(), toQuery(pageLink), Collections.singletonList(customerAsset.getId())); + Assert.assertEquals(1, customerAlarms.getData().size()); + Assert.assertEquals(customerAlarm, customerAlarms.getData().get(0)); + } + private AlarmDataQuery toQuery(AlarmDataPageLink pageLink){ return toQuery(pageLink, Collections.EMPTY_LIST); } From 39eafde85b0199acbf840be63b756920e7017d40 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 19 Nov 2020 11:40:44 +0200 Subject: [PATCH 386/463] fix MQTT x509 auth --- .../server/service/transport/DefaultTransportApiService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index d446b32e41..98f5708bad 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -178,6 +178,8 @@ public class DefaultTransportApiService implements TransportApiService { if (!checkMqttCredentials(mqtt, credentials)) { credentials = null; } + } else { + return getEmptyTransportApiResponseFuture(); } } if (credentials == null) { From 3cabec954e9d3f5305f95b4e66867334cee3449e Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 19 Nov 2020 17:06:54 +0200 Subject: [PATCH 387/463] Critical bug fix --- .../server/dao/sql/query/DefaultAlarmQueryRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java index 2c0d67513f..e9a5372894 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java @@ -156,7 +156,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { wherePart.append(" a.originator_id in (:entity_ids)"); } } else { - fromPart.append(" left join (select * from (VALUES"); + fromPart.append(" inner join (select * from (VALUES"); int entityIdIdx = 0; int lastEntityIdIdx = orderedEntityIds.size() - 1; for (EntityId entityId : orderedEntityIds) { From 1e1e3ec6a30f784da596768d9cb44836e39155f1 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 20 Nov 2020 13:08:20 +0200 Subject: [PATCH 388/463] Introduce SMS Service. Add Send SMS Rule Node --- application/pom.xml | 8 ++ .../server/actors/ActorSystemContext.java | 19 +++ .../actors/ruleChain/DefaultTbContext.java | 21 +++ .../server/controller/AdminController.java | 18 +++ .../server/service/sms/AbstractSmsSender.java | 54 ++++++++ .../service/sms/DefaultSmsSenderFactory.java | 42 ++++++ .../server/service/sms/DefaultSmsService.java | 127 ++++++++++++++++++ .../service/sms/SmsExecutorService.java | 33 +++++ .../server/service/sms/aws/AwsSmsSender.java | 73 ++++++++++ .../service/sms/twilio/TwilioSmsSender.java | 56 ++++++++ .../src/main/resources/thingsboard.yml | 4 + pom.xml | 29 +++- .../rule/engine/api/SmsService.java | 31 +++++ .../rule/engine/api/TbContext.java | 7 + .../rule/engine/api/sms/SmsSender.java | 26 ++++ .../rule/engine/api/sms/SmsSenderFactory.java | 24 ++++ .../AwsSnsSmsProviderConfiguration.java | 32 +++++ .../sms/config/SmsProviderConfiguration.java | 36 +++++ .../api/sms/config/SmsProviderType.java | 21 +++ .../engine/api/sms/config/TestSmsRequest.java | 27 ++++ .../TwilioSmsProviderConfiguration.java | 32 +++++ .../api/sms/exception/SmsException.java | 28 ++++ .../api/sms/exception/SmsParseException.java | 28 ++++ .../api/sms/exception/SmsSendException.java | 27 ++++ .../rule/engine/sms/TbSendSmsNode.java | 97 +++++++++++++ .../sms/TbSendSmsNodeConfiguration.java | 38 ++++++ ui-ngx/src/app/core/http/admin.service.ts | 13 +- ui-ngx/src/app/core/services/menu.service.ts | 14 +- .../home/components/home-components.module.ts | 78 ++++++----- ...-sns-provider-configuration.component.html | 40 ++++++ ...ws-sns-provider-configuration.component.ts | 99 ++++++++++++++ .../sms-provider-configuration.component.html | 44 ++++++ .../sms-provider-configuration.component.ts | 123 +++++++++++++++++ ...-sms-provider-configuration.component.html | 44 ++++++ ...io-sms-provider-configuration.component.ts | 101 ++++++++++++++ .../home/pages/admin/admin-routing.module.ts | 14 ++ .../modules/home/pages/admin/admin.module.ts | 4 + .../admin/send-test-sms-dialog.component.html | 70 ++++++++++ .../admin/send-test-sms-dialog.component.ts | 87 ++++++++++++ .../pages/admin/sms-provider.component.html | 50 +++++++ .../pages/admin/sms-provider.component.scss | 18 +++ .../pages/admin/sms-provider.component.ts | 95 +++++++++++++ ...ake-dashboard-public-dialog.component.html | 2 +- .../activation-link-dialog.component.html | 2 +- ui-ngx/src/app/shared/models/constants.ts | 1 + .../src/app/shared/models/settings.models.ts | 65 +++++++++ .../assets/locale/locale.constant-en_US.json | 27 ++++ 47 files changed, 1888 insertions(+), 41 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java create mode 100644 application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java create mode 100644 application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/sms/SmsExecutorService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java create mode 100644 application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSender.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSenderFactory.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/AwsSnsSmsProviderConfiguration.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderConfiguration.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderType.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TestSmsRequest.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TwilioSmsProviderConfiguration.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsException.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsParseException.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsSendException.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNodeConfiguration.java create mode 100644 ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.html create mode 100644 ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.html create mode 100644 ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.html create mode 100644 ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/send-test-sms-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/admin/send-test-sms-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.ts diff --git a/application/pom.xml b/application/pom.xml index 1b29ef3243..f62b1c9f5e 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -193,6 +193,14 @@ com.sun.mail javax.mail + + com.twilio.sdk + twilio + + + com.amazonaws + aws-java-sdk-sns + org.apache.curator curator-recipes diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 52dbb38c9c..49119ed667 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -32,6 +32,8 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.MailService; +import org.thingsboard.rule.engine.api.SmsService; +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.actors.tenant.DebugTbRateLimits; import org.thingsboard.server.common.data.DataConstants; @@ -80,6 +82,7 @@ import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; import org.thingsboard.server.service.script.JsExecutorService; import org.thingsboard.server.service.script.JsInvokeService; import org.thingsboard.server.service.session.DeviceSessionCacheService; +import org.thingsboard.server.service.sms.SmsExecutorService; import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; @@ -228,6 +231,10 @@ public class ActorSystemContext { @Getter private MailExecutorService mailExecutor; + @Autowired + @Getter + private SmsExecutorService smsExecutor; + @Autowired @Getter private DbCallbackExecutorService dbCallbackExecutor; @@ -244,6 +251,14 @@ public class ActorSystemContext { @Getter private MailService mailService; + @Autowired + @Getter + private SmsService smsService; + + @Autowired + @Getter + private SmsSenderFactory smsSenderFactory; + @Autowired @Getter private ClaimDevicesService claimDevicesService; @@ -325,6 +340,10 @@ public class ActorSystemContext { @Getter private boolean allowSystemMailService; + @Value("${actors.rule.allow_system_sms_service}") + @Getter + private boolean allowSystemSmsService; + @Value("${transport.sessions.inactivity_timeout}") @Getter private long sessionInactivityTimeout; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 6749ed69ed..b1d214069f 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -28,8 +28,10 @@ import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; import org.thingsboard.rule.engine.api.RuleEngineRpcService; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.ScriptEngine; +import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.common.data.Customer; @@ -302,6 +304,11 @@ class DefaultTbContext implements TbContext { return mainCtx.getMailExecutor(); } + @Override + public ListeningExecutor getSmsExecutor() { + return mainCtx.getSmsExecutor(); + } + @Override public ListeningExecutor getDbCallbackExecutor() { return mainCtx.getDbCallbackExecutor(); @@ -427,6 +434,20 @@ class DefaultTbContext implements TbContext { } } + @Override + public SmsService getSmsService() { + if (mainCtx.isAllowSystemSmsService()) { + return mainCtx.getSmsService(); + } else { + throw new RuntimeException("Access to System SMS Service is forbidden!"); + } + } + + @Override + public SmsSenderFactory getSmsSenderFactory() { + return mainCtx.getSmsSenderFactory(); + } + @Override public RuleEngineRpcService getRpcService() { return mainCtx.getTbRuleEngineDeviceRpcService(); diff --git a/application/src/main/java/org/thingsboard/server/controller/AdminController.java b/application/src/main/java/org/thingsboard/server/controller/AdminController.java index 134171c8b1..c980bff796 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AdminController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AdminController.java @@ -25,6 +25,8 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.rule.engine.api.MailService; +import org.thingsboard.rule.engine.api.SmsService; +import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.UpdateMessage; import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -45,6 +47,9 @@ public class AdminController extends BaseController { @Autowired private MailService mailService; + @Autowired + private SmsService smsService; + @Autowired private AdminSettingsService adminSettingsService; @@ -80,6 +85,8 @@ public class AdminController extends BaseController { if (adminSettings.getKey().equals("mail")) { mailService.updateMailConfiguration(); ((ObjectNode) adminSettings.getJsonValue()).put("password", ""); + } else if (adminSettings.getKey().equals("sms")) { + smsService.updateSmsConfiguration(); } return adminSettings; } catch (Exception e) { @@ -127,6 +134,17 @@ public class AdminController extends BaseController { } } + @PreAuthorize("hasAuthority('SYS_ADMIN')") + @RequestMapping(value = "/settings/testSms", method = RequestMethod.POST) + public void sendTestSms(@RequestBody TestSmsRequest testSmsRequest) throws ThingsboardException { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); + smsService.sendTestSms(testSmsRequest); + } catch (Exception e) { + throw handleException(e); + } + } + @PreAuthorize("hasAuthority('SYS_ADMIN')") @RequestMapping(value = "/updates", method = RequestMethod.GET) @ResponseBody diff --git a/application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java b/application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java new file mode 100644 index 0000000000..1add9cd79b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java @@ -0,0 +1,54 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sms; + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.rule.engine.api.sms.SmsSender; +import org.thingsboard.rule.engine.api.sms.exception.SmsParseException; +import sun.misc.Regexp; + +import java.util.regex.Pattern; + +@Slf4j +public abstract class AbstractSmsSender implements SmsSender { + + private static final Pattern E_164_PHONE_NUMBER_PATTERN = Pattern.compile("^\\+[1-9]\\d{1,14}$"); + + private static final int MAX_SMS_MESSAGE_LENGTH = 1600; + private static final int MAX_SMS_SEGMENT_LENGTH = 70; + + protected String validatePhoneNumber(String phoneNumber) throws SmsParseException { + phoneNumber = phoneNumber.trim(); + if (!E_164_PHONE_NUMBER_PATTERN.matcher(phoneNumber).matches()) { + throw new SmsParseException("Invalid phone number format. Phone number must be in E.164 format."); + } + return phoneNumber; + } + + protected String prepareMessage(String message) { + message = message.replaceAll("^\"|\"$", "").replaceAll("\\\\n", "\n"); + if (message.length() > MAX_SMS_MESSAGE_LENGTH) { + log.warn("SMS message exceeds maximum symbols length and will be truncated"); + message = message.substring(0, MAX_SMS_MESSAGE_LENGTH); + } + return message; + } + + protected int countMessageSegments(String message) { + return (int)Math.ceil((double) message.length() / (double) MAX_SMS_SEGMENT_LENGTH); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java new file mode 100644 index 0000000000..3452bcf1ac --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sms; + +import org.springframework.stereotype.Component; +import org.thingsboard.rule.engine.api.sms.SmsSender; +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; +import org.thingsboard.rule.engine.api.sms.config.AwsSnsSmsProviderConfiguration; +import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; +import org.thingsboard.rule.engine.api.sms.config.TwilioSmsProviderConfiguration; +import org.thingsboard.server.service.sms.aws.AwsSmsSender; +import org.thingsboard.server.service.sms.twilio.TwilioSmsSender; + +@Component +public class DefaultSmsSenderFactory implements SmsSenderFactory { + + @Override + public SmsSender createSmsSender(SmsProviderConfiguration config) { + switch (config.getType()) { + case AWS_SNS: + return new AwsSmsSender((AwsSnsSmsProviderConfiguration)config); + case TWILIO: + return new TwilioSmsSender((TwilioSmsProviderConfiguration)config); + default: + throw new RuntimeException("Unknown SMS provider type " + config.getType()); + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java new file mode 100644 index 0000000000..05df30deae --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java @@ -0,0 +1,127 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sms; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.NestedRuntimeException; +import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.SmsService; +import org.thingsboard.rule.engine.api.sms.SmsSender; +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; +import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; +import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; +import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.settings.AdminSettingsService; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +@Service +@Slf4j +public class DefaultSmsService implements SmsService { + + @Autowired + private SmsSenderFactory smsSenderFactory; + + @Autowired + private AdminSettingsService adminSettingsService; + + private SmsSender smsSender; + + @PostConstruct + private void init() { + updateSmsConfiguration(); + } + + @PreDestroy + private void destroy() { + if (this.smsSender != null) { + this.smsSender.destroy(); + } + } + + @Override + public void updateSmsConfiguration() { + AdminSettings settings = adminSettingsService.findAdminSettingsByKey(new TenantId(EntityId.NULL_UUID), "sms"); + if (settings != null) { + try { + JsonNode jsonConfig = settings.getJsonValue(); + SmsProviderConfiguration configuration = JacksonUtil.convertValue(jsonConfig, SmsProviderConfiguration.class); + SmsSender newSmsSender = this.smsSenderFactory.createSmsSender(configuration); + if (this.smsSender != null) { + this.smsSender.destroy(); + } + this.smsSender = newSmsSender; + } catch (Exception e) { + log.error("Failed to create SMS sender", e); + } + } + } + + @Override + public void sendSms(String numberTo, String message) throws ThingsboardException { + if (this.smsSender == null) { + throw new ThingsboardException("Unable to send SMS: no SMS provider configured!", ThingsboardErrorCode.GENERAL); + } + this.sendSms(this.smsSender, numberTo, message); + } + + @Override + public void sendSms(String[] numbersTo, String message) throws ThingsboardException { + for (String numberTo : numbersTo) { + this.sendSms(numberTo, message); + } + } + + @Override + public void sendTestSms(TestSmsRequest testSmsRequest) throws ThingsboardException { + SmsSender testSmsSender; + try { + testSmsSender = this.smsSenderFactory.createSmsSender(testSmsRequest.getProviderConfiguration()); + } catch (Exception e) { + throw handleException(e); + } + this.sendSms(testSmsSender, testSmsRequest.getNumberTo(), testSmsRequest.getMessage()); + testSmsSender.destroy(); + } + + private int sendSms(SmsSender smsSender, String numberTo, String message) throws ThingsboardException { + try { + return smsSender.sendSms(numberTo, message); + } catch (Exception e) { + throw handleException(e); + } + } + + private ThingsboardException handleException(Exception exception) { + String message; + if (exception instanceof NestedRuntimeException) { + message = ((NestedRuntimeException) exception).getMostSpecificCause().getMessage(); + } else { + message = exception.getMessage(); + } + log.warn("Unable to send SMS: {}", message); + return new ThingsboardException(String.format("Unable to send SMS: %s", message), + ThingsboardErrorCode.GENERAL); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/sms/SmsExecutorService.java b/application/src/main/java/org/thingsboard/server/service/sms/SmsExecutorService.java new file mode 100644 index 0000000000..48aa2d5dc1 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sms/SmsExecutorService.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sms; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.thingsboard.common.util.AbstractListeningExecutor; + +@Component +public class SmsExecutorService extends AbstractListeningExecutor { + + @Value("${actors.rule.sms_thread_pool_size}") + private int smsExecutorThreadPoolSize; + + @Override + protected int getThreadPollSize() { + return smsExecutorThreadPoolSize; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java b/application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java new file mode 100644 index 0000000000..98a8556a5d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java @@ -0,0 +1,73 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sms.aws; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.sns.AmazonSNS; +import com.amazonaws.services.sns.AmazonSNSClient; +import com.amazonaws.services.sns.model.PublishRequest; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.thingsboard.rule.engine.api.sms.config.AwsSnsSmsProviderConfiguration; +import org.thingsboard.rule.engine.api.sms.exception.SmsException; +import org.thingsboard.rule.engine.api.sms.exception.SmsSendException; +import org.thingsboard.server.service.sms.AbstractSmsSender; + +@Slf4j +public class AwsSmsSender extends AbstractSmsSender { + + private AmazonSNS snsClient; + + public AwsSmsSender(AwsSnsSmsProviderConfiguration config) { + if (StringUtils.isEmpty(config.getAccessKeyId()) || StringUtils.isEmpty(config.getSecretAccessKey()) || StringUtils.isEmpty(config.getRegion())) { + throw new IllegalArgumentException("Invalid AWS sms provider configuration: aws accessKeyId, aws secretAccessKey and aws region should be specified!"); + } + AWSCredentials awsCredentials = new BasicAWSCredentials(config.getAccessKeyId(), config.getSecretAccessKey()); + AWSStaticCredentialsProvider credProvider = new AWSStaticCredentialsProvider(awsCredentials); + this.snsClient = AmazonSNSClient.builder() + .withCredentials(credProvider) + .withRegion(config.getRegion()) + .build(); + } + + @Override + public int sendSms(String numberTo, String message) throws SmsException { + numberTo = this.validatePhoneNumber(numberTo); + message = this.prepareMessage(message); + try { + PublishRequest publishRequest = new PublishRequest() + .withPhoneNumber(numberTo) + .withMessage(message); + this.snsClient.publish(publishRequest); + return this.countMessageSegments(message); + } catch (Exception e) { + throw new SmsSendException("Failed to send SMS message - " + e.getMessage(), e); + } + } + + @Override + public void destroy() { + if (this.snsClient != null) { + try { + this.snsClient.shutdown(); + } catch (Exception e) { + log.error("Failed to shutdown SNS client during destroy()", e); + } + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java b/application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java new file mode 100644 index 0000000000..072b2b2ff7 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java @@ -0,0 +1,56 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sms.twilio; + +import com.twilio.http.TwilioRestClient; +import com.twilio.rest.api.v2010.account.Message; +import com.twilio.type.PhoneNumber; +import org.apache.commons.lang3.StringUtils; +import org.thingsboard.rule.engine.api.sms.config.TwilioSmsProviderConfiguration; +import org.thingsboard.rule.engine.api.sms.exception.SmsException; +import org.thingsboard.rule.engine.api.sms.exception.SmsSendException; +import org.thingsboard.server.service.sms.AbstractSmsSender; + +public class TwilioSmsSender extends AbstractSmsSender { + + private TwilioRestClient twilioRestClient; + private String numberFrom; + + public TwilioSmsSender(TwilioSmsProviderConfiguration config) { + if (StringUtils.isEmpty(config.getAccountSid()) || StringUtils.isEmpty(config.getAccountToken()) || StringUtils.isEmpty(config.getNumberFrom())) { + throw new IllegalArgumentException("Invalid twilio sms provider configuration: accountSid, accountToken and numberFrom should be specified!"); + } + this.numberFrom = this.validatePhoneNumber(config.getNumberFrom()); + this.twilioRestClient = new TwilioRestClient.Builder(config.getAccountSid(), config.getAccountToken()).build(); + } + + @Override + public int sendSms(String numberTo, String message) throws SmsException { + numberTo = this.validatePhoneNumber(numberTo); + message = this.prepareMessage(message); + try { + String numSegments = Message.creator(new PhoneNumber(numberTo), new PhoneNumber(this.numberFrom), message).create(this.twilioRestClient).getNumSegments(); + return Integer.valueOf(numSegments); + } catch (Exception e) { + throw new SmsSendException("Failed to send SMS message - " + e.getMessage(), e); + } + } + + @Override + public void destroy() { + + } +} diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 73b36b2104..06661d9b58 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -281,8 +281,12 @@ actors: js_thread_pool_size: "${ACTORS_RULE_JS_THREAD_POOL_SIZE:50}" # Specify thread pool size for mail sender executor service mail_thread_pool_size: "${ACTORS_RULE_MAIL_THREAD_POOL_SIZE:50}" + # Specify thread pool size for sms sender executor service + sms_thread_pool_size: "${ACTORS_RULE_SMS_THREAD_POOL_SIZE:50}" # Whether to allow usage of system mail service for rules allow_system_mail_service: "${ACTORS_RULE_ALLOW_SYSTEM_MAIL_SERVICE:true}" + # Whether to allow usage of system sms service for rules + allow_system_sms_service: "${ACTORS_RULE_ALLOW_SYSTEM_SMS_SERVICE:true}" # Specify thread pool size for external call service external_call_thread_pool_size: "${ACTORS_RULE_EXTERNAL_CALL_THREAD_POOL_SIZE:50}" chain: diff --git a/pom.xml b/pom.xml index 45dbb3be87..660b36f621 100755 --- a/pom.xml +++ b/pom.xml @@ -97,7 +97,7 @@ 2.57 2.7.7 1.27 - 1.11.747 + 1.11.747 1.105.0 3.2.0 1.5.0 @@ -108,6 +108,7 @@ 1.5.2 1.0.2TB 3.4.0 + 7.54.2 @@ -1318,7 +1319,12 @@ com.amazonaws aws-java-sdk-sqs - ${amazonaws.sqs.version} + ${aws.sdk.version} + + + com.amazonaws + aws-java-sdk-sns + ${aws.sdk.version} com.google.cloud @@ -1381,6 +1387,25 @@ wire-schema ${wire-schema.version} + + com.twilio.sdk + twilio + ${twilio.version} + + + io.jsonwebtoken + jjwt-api + + + io.jsonwebtoken + jjwt-jackson + + + io.jsonwebtoken + jjwt-impl + + + diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java new file mode 100644 index 0000000000..a041ad9390 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.api; + +import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; +import org.thingsboard.server.common.data.exception.ThingsboardException; + +public interface SmsService { + + void updateSmsConfiguration(); + + void sendSms(String numberTo, String message) throws ThingsboardException; + + void sendSms(String[] numbersTo, String message) throws ThingsboardException;; + + void sendTestSms(TestSmsRequest testSmsRequest) throws ThingsboardException; + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 081a84eef0..b104c6f07b 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -18,6 +18,7 @@ package org.thingsboard.rule.engine.api; import io.netty.channel.EventLoopGroup; import org.springframework.data.redis.core.RedisTemplate; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; @@ -194,12 +195,18 @@ public interface TbContext { ListeningExecutor getMailExecutor(); + ListeningExecutor getSmsExecutor(); + ListeningExecutor getDbCallbackExecutor(); ListeningExecutor getExternalCallExecutor(); MailService getMailService(); + SmsService getSmsService(); + + SmsSenderFactory getSmsSenderFactory(); + ScriptEngine createJsScriptEngine(String script, String... argNames); void logJsEvalRequest(); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSender.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSender.java new file mode 100644 index 0000000000..672992df64 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSender.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.api.sms; + +import org.thingsboard.rule.engine.api.sms.exception.SmsException; + +public interface SmsSender { + + int sendSms(String numberTo, String message) throws SmsException; + + void destroy(); + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSenderFactory.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSenderFactory.java new file mode 100644 index 0000000000..39daa48983 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSenderFactory.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.api.sms; + +import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; + +public interface SmsSenderFactory { + + SmsSender createSmsSender(SmsProviderConfiguration config); + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/AwsSnsSmsProviderConfiguration.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/AwsSnsSmsProviderConfiguration.java new file mode 100644 index 0000000000..74cd5486a1 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/AwsSnsSmsProviderConfiguration.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.api.sms.config; + +import lombok.Data; + +@Data +public class AwsSnsSmsProviderConfiguration implements SmsProviderConfiguration { + + private String accessKeyId; + private String secretAccessKey; + private String region; + + @Override + public SmsProviderType getType() { + return SmsProviderType.AWS_SNS; + } + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderConfiguration.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderConfiguration.java new file mode 100644 index 0000000000..8feec6ff40 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderConfiguration.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.api.sms.config; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = AwsSnsSmsProviderConfiguration.class, name = "AWS_SNS"), + @JsonSubTypes.Type(value = TwilioSmsProviderConfiguration.class, name = "TWILIO")}) +public interface SmsProviderConfiguration { + + @JsonIgnore + SmsProviderType getType(); + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderType.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderType.java new file mode 100644 index 0000000000..e698dd4783 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderType.java @@ -0,0 +1,21 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.api.sms.config; + +public enum SmsProviderType { + AWS_SNS, + TWILIO +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TestSmsRequest.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TestSmsRequest.java new file mode 100644 index 0000000000..3d00f9e580 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TestSmsRequest.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.api.sms.config; + +import lombok.Data; + +@Data +public class TestSmsRequest { + + private SmsProviderConfiguration providerConfiguration; + private String numberTo; + private String message; + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TwilioSmsProviderConfiguration.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TwilioSmsProviderConfiguration.java new file mode 100644 index 0000000000..2e0585e466 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TwilioSmsProviderConfiguration.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.api.sms.config; + +import lombok.Data; + +@Data +public class TwilioSmsProviderConfiguration implements SmsProviderConfiguration { + + private String accountSid; + private String accountToken; + private String numberFrom; + + @Override + public SmsProviderType getType() { + return SmsProviderType.TWILIO; + } + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsException.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsException.java new file mode 100644 index 0000000000..dce599404c --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsException.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.api.sms.exception; + +public abstract class SmsException extends RuntimeException { + + public SmsException(String msg) { + super(msg); + } + + public SmsException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsParseException.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsParseException.java new file mode 100644 index 0000000000..de3c41029e --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsParseException.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.api.sms.exception; + +public class SmsParseException extends SmsException { + + public SmsParseException(String msg) { + super(msg); + } + + public SmsParseException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsSendException.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsSendException.java new file mode 100644 index 0000000000..f5254bf6d6 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsSendException.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.api.sms.exception; + +public class SmsSendException extends SmsException { + + public SmsSendException(String msg) { + super(msg); + } + + public SmsSendException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java new file mode 100644 index 0000000000..f2c6ad3725 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java @@ -0,0 +1,97 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.sms; + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.rule.engine.api.RuleNode; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNode; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.sms.SmsSender; +import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.msg.TbMsg; + +import static org.thingsboard.common.util.DonAsynchron.withCallback; + +@Slf4j +@RuleNode( + type = ComponentType.EXTERNAL, + name = "send sms", + configClazz = TbSendSmsNodeConfiguration.class, + nodeDescription = "Sends SMS message via SMS provider.", + nodeDetails = "Will send SMS message by populating target phone numbers and sms message fields using values derived from message metadata.", + uiResources = {"static/rulenode/rulenode-core-config.js"}, + configDirective = "tbActionNodeSendSmsConfig", + icon = "sms" +) +public class TbSendSmsNode implements TbNode { + + private TbSendSmsNodeConfiguration config; + private SmsSender smsSender; + + @Override + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + try { + this.config = TbNodeUtils.convert(configuration, TbSendSmsNodeConfiguration.class); + if (!this.config.isUseSystemSmsSettings()) { + smsSender = createSmsSender(ctx); + } + } catch (Exception e) { + throw new TbNodeException(e); + } + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) { + try { + withCallback(ctx.getSmsExecutor().executeAsync(() -> { + sendSms(ctx, msg); + return null; + }), + ok -> ctx.tellSuccess(msg), + fail -> ctx.tellFailure(msg, fail)); + } catch (Exception ex) { + ctx.tellFailure(msg, ex); + } + } + + private void sendSms(TbContext ctx, TbMsg msg) throws Exception { + String numbersTo = TbNodeUtils.processPattern(this.config.getNumbersToTemplate(), msg.getMetaData()); + String message = TbNodeUtils.processPattern(this.config.getSmsMessageTemplate(), msg.getMetaData()); + String[] numbersToList = numbersTo.split(","); + if (this.config.isUseSystemSmsSettings()) { + ctx.getSmsService().sendSms(numbersToList, message); + } else { + for (String numberTo : numbersToList) { + this.smsSender.sendSms(numberTo, message); + } + } + } + + @Override + public void destroy() { + if (this.smsSender != null) { + this.smsSender.destroy(); + } + } + + private SmsSender createSmsSender(TbContext ctx) { + return ctx.getSmsSenderFactory().createSmsSender(this.config.getSmsProviderConfiguration()); + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNodeConfiguration.java new file mode 100644 index 0000000000..14720aa342 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNodeConfiguration.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.sms; + +import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; + +@Data +public class TbSendSmsNodeConfiguration implements NodeConfiguration { + + private String numbersToTemplate; + private String smsMessageTemplate; + private boolean useSystemSmsSettings; + private SmsProviderConfiguration smsProviderConfiguration; + + @Override + public NodeConfiguration defaultConfiguration() { + TbSendSmsNodeConfiguration configuration = new TbSendSmsNodeConfiguration(); + configuration.numbersToTemplate = "${userPhone}"; + configuration.smsMessageTemplate = "Device ${deviceName} has high temperature ${temp}"; + configuration.setUseSystemSmsSettings(true); + return configuration; + } +} diff --git a/ui-ngx/src/app/core/http/admin.service.ts b/ui-ngx/src/app/core/http/admin.service.ts index f575f56906..a228234c3d 100644 --- a/ui-ngx/src/app/core/http/admin.service.ts +++ b/ui-ngx/src/app/core/http/admin.service.ts @@ -18,7 +18,13 @@ import { Injectable } from '@angular/core'; import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; -import { AdminSettings, MailServerSettings, SecuritySettings, UpdateMessage } from '@shared/models/settings.models'; +import { + AdminSettings, + MailServerSettings, + SecuritySettings, + TestSmsRequest, + UpdateMessage +} from '@shared/models/settings.models'; @Injectable({ providedIn: 'root' @@ -43,6 +49,11 @@ export class AdminService { return this.http.post('/api/admin/settings/testMail', adminSettings, defaultHttpOptionsFromConfig(config)); } + public sendTestSms(testSmsRequest: TestSmsRequest, + config?: RequestConfig): Observable { + return this.http.post('/api/admin/settings/testSms', testSmsRequest, defaultHttpOptionsFromConfig(config)); + } + public getSecuritySettings(config?: RequestConfig): Observable { return this.http.get(`/api/admin/securitySettings`, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index 4e98f375c2..e30d993d92 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -108,7 +108,7 @@ export class MenuService { name: 'admin.system-settings', type: 'toggle', path: '/settings', - height: '160px', + height: '200px', icon: 'settings', pages: [ { @@ -125,6 +125,13 @@ export class MenuService { path: '/settings/outgoing-mail', icon: 'mail' }, + { + id: guid(), + name: 'admin.sms-provider', + type: 'link', + path: '/settings/sms-provider', + icon: 'sms' + }, { id: guid(), name: 'admin.security-settings', @@ -187,6 +194,11 @@ export class MenuService { icon: 'mail', path: '/settings/outgoing-mail' }, + { + name: 'admin.sms-provider', + icon: 'sms', + path: '/settings/sms-provider' + }, { name: 'admin.security-settings', icon: 'security', diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index ec7ccea660..a50cef697d 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -77,44 +77,47 @@ import { ComplexFilterPredicateDialogComponent } from '@home/components/filter/c import { KeyFilterDialogComponent } from '@home/components/filter/key-filter-dialog.component'; import { FiltersDialogComponent } from '@home/components/filter/filters-dialog.component'; import { FilterDialogComponent } from '@home/components/filter/filter-dialog.component'; -import { FilterSelectComponent } from './filter/filter-select.component'; +import { FilterSelectComponent } from '@home/components/filter/filter-select.component'; import { FiltersEditComponent } from '@home/components/filter/filters-edit.component'; import { FiltersEditPanelComponent } from '@home/components/filter/filters-edit-panel.component'; import { UserFilterDialogComponent } from '@home/components/filter/user-filter-dialog.component'; -import { FilterUserInfoComponent } from './filter/filter-user-info.component'; -import { FilterUserInfoDialogComponent } from './filter/filter-user-info-dialog.component'; -import { FilterPredicateValueComponent } from './filter/filter-predicate-value.component'; -import { TenantProfileAutocompleteComponent } from './profile/tenant-profile-autocomplete.component'; -import { TenantProfileComponent } from './profile/tenant-profile.component'; -import { TenantProfileDialogComponent } from './profile/tenant-profile-dialog.component'; -import { TenantProfileDataComponent } from './profile/tenant-profile-data.component'; -import { DefaultDeviceProfileConfigurationComponent } from './profile/device/default-device-profile-configuration.component'; -import { DeviceProfileConfigurationComponent } from './profile/device/device-profile-configuration.component'; -import { DeviceProfileComponent } from './profile/device-profile.component'; -import { DefaultDeviceProfileTransportConfigurationComponent } from './profile/device/default-device-profile-transport-configuration.component'; -import { DeviceProfileTransportConfigurationComponent } from './profile/device/device-profile-transport-configuration.component'; -import { DeviceProfileDialogComponent } from './profile/device-profile-dialog.component'; -import { DeviceProfileAutocompleteComponent } from './profile/device-profile-autocomplete.component'; -import { MqttDeviceProfileTransportConfigurationComponent } from './profile/device/mqtt-device-profile-transport-configuration.component'; -import { Lwm2mDeviceProfileTransportConfigurationComponent } from './profile/device/lwm2m-device-profile-transport-configuration.component'; -import { DeviceProfileAlarmsComponent } from './profile/alarm/device-profile-alarms.component'; -import { DeviceProfileAlarmComponent } from './profile/alarm/device-profile-alarm.component'; -import { CreateAlarmRulesComponent } from './profile/alarm/create-alarm-rules.component'; -import { AlarmRuleComponent } from './profile/alarm/alarm-rule.component'; -import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-condition.component'; -import { FilterTextComponent } from './filter/filter-text.component'; -import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component'; -import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component'; -import { DeviceProfileProvisionConfigurationComponent } from "./profile/device-profile-provision-configuration.component"; -import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component'; -import { DeviceWizardDialogComponent } from './wizard/device-wizard-dialog.component'; -import { DeviceCredentialsComponent } from './device/device-credentials.component'; -import { AlarmScheduleInfoComponent } from './profile/alarm/alarm-schedule-info.component'; +import { FilterUserInfoComponent } from '@home/components/filter/filter-user-info.component'; +import { FilterUserInfoDialogComponent } from '@home/components/filter/filter-user-info-dialog.component'; +import { FilterPredicateValueComponent } from '@home/components/filter/filter-predicate-value.component'; +import { TenantProfileAutocompleteComponent } from '@home/components/profile/tenant-profile-autocomplete.component'; +import { TenantProfileComponent } from '@home/components/profile/tenant-profile.component'; +import { TenantProfileDialogComponent } from '@home/components/profile/tenant-profile-dialog.component'; +import { TenantProfileDataComponent } from '@home/components/profile/tenant-profile-data.component'; +import { DefaultDeviceProfileConfigurationComponent } from '@home/components/profile/device/default-device-profile-configuration.component'; +import { DeviceProfileConfigurationComponent } from '@home/components/profile/device/device-profile-configuration.component'; +import { DeviceProfileComponent } from '@home/components/profile/device-profile.component'; +import { DefaultDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/default-device-profile-transport-configuration.component'; +import { DeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/device-profile-transport-configuration.component'; +import { DeviceProfileDialogComponent } from '@home/components/profile/device-profile-dialog.component'; +import { DeviceProfileAutocompleteComponent } from '@home/components/profile/device-profile-autocomplete.component'; +import { MqttDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/mqtt-device-profile-transport-configuration.component'; +import { Lwm2mDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/lwm2m-device-profile-transport-configuration.component'; +import { DeviceProfileAlarmsComponent } from '@home/components/profile/alarm/device-profile-alarms.component'; +import { DeviceProfileAlarmComponent } from '@home/components/profile/alarm/device-profile-alarm.component'; +import { CreateAlarmRulesComponent } from '@home/components/profile/alarm/create-alarm-rules.component'; +import { AlarmRuleComponent } from '@home/components/profile/alarm/alarm-rule.component'; +import { AlarmRuleConditionComponent } from '@home/components/profile/alarm/alarm-rule-condition.component'; +import { FilterTextComponent } from '@home/components/filter/filter-text.component'; +import { AddDeviceProfileDialogComponent } from '@home/components/profile/add-device-profile-dialog.component'; +import { RuleChainAutocompleteComponent } from '@home/components/rule-chain/rule-chain-autocomplete.component'; +import { DeviceProfileProvisionConfigurationComponent } from '@home/components/profile/device-profile-provision-configuration.component'; +import { AlarmScheduleComponent } from '@home/components/profile/alarm/alarm-schedule.component'; +import { DeviceWizardDialogComponent } from '@home/components/wizard/device-wizard-dialog.component'; +import { DeviceCredentialsComponent } from '@home/components/device/device-credentials.component'; +import { AlarmScheduleInfoComponent } from '@home/components/profile/alarm/alarm-schedule-info.component'; import { AlarmScheduleDialogComponent } from '@home/components/profile/alarm/alarm-schedule-dialog.component'; -import { EditAlarmDetailsDialogComponent } from './profile/alarm/edit-alarm-details-dialog.component'; +import { EditAlarmDetailsDialogComponent } from '@home/components/profile/alarm/edit-alarm-details-dialog.component'; import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alarm/alarm-rule-condition-dialog.component'; -import { DefaultTenantProfileConfigurationComponent } from './profile/tenant/default-tenant-profile-configuration.component'; -import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-profile-configuration.component'; +import { DefaultTenantProfileConfigurationComponent } from '@home/components/profile/tenant/default-tenant-profile-configuration.component'; +import { TenantProfileConfigurationComponent } from '@home/components/profile/tenant/tenant-profile-configuration.component'; +import { SmsProviderConfigurationComponent } from '@home/components/sms/sms-provider-configuration.component'; +import { AwsSnsProviderConfigurationComponent } from '@home/components/sms/aws-sns-provider-configuration.component'; +import { TwilioSmsProviderConfigurationComponent } from '@home/components/sms/twilio-sms-provider-configuration.component'; @NgModule({ declarations: @@ -212,7 +215,10 @@ import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-pro DeviceWizardDialogComponent, DeviceCredentialsComponent, AlarmScheduleDialogComponent, - EditAlarmDetailsDialogComponent + EditAlarmDetailsDialogComponent, + SmsProviderConfigurationComponent, + AwsSnsProviderConfigurationComponent, + TwilioSmsProviderConfigurationComponent ], imports: [ CommonModule, @@ -298,7 +304,9 @@ import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-pro AlarmScheduleDialogComponent, EditAlarmDetailsDialogComponent, DeviceProfileProvisionConfigurationComponent, - AlarmScheduleComponent + SmsProviderConfigurationComponent, + AwsSnsProviderConfigurationComponent, + TwilioSmsProviderConfigurationComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.html b/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.html new file mode 100644 index 0000000000..60d1ff9a1e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.html @@ -0,0 +1,40 @@ + +
+ + admin.aws-access-key-id + + + {{ 'admin.aws-access-key-id-required' | translate }} + + + + admin.aws-secret-access-key + + + {{ 'admin.aws-secret-access-key-required' | translate }} + + + + admin.aws-region + + + {{ 'admin.aws-region-required' | translate }} + + +
diff --git a/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.ts b/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.ts new file mode 100644 index 0000000000..6b4a2e23ad --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.ts @@ -0,0 +1,99 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@app/core/core.state'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { isDefinedAndNotNull } from '@core/utils'; +import { AwsSnsSmsProviderConfiguration } from '@shared/models/settings.models'; + +@Component({ + selector: 'tb-aws-sns-provider-configuration', + templateUrl: './aws-sns-provider-configuration.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => AwsSnsProviderConfigurationComponent), + multi: true + }] +}) +export class AwsSnsProviderConfigurationComponent implements ControlValueAccessor, OnInit { + + awsSnsProviderConfigurationFormGroup: FormGroup; + + private requiredValue: boolean; + + get required(): boolean { + return this.requiredValue; + } + + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private fb: FormBuilder) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.awsSnsProviderConfigurationFormGroup = this.fb.group({ + accessKeyId: [null, [Validators.required]], + secretAccessKey: [null, [Validators.required]], + region: [null, [Validators.required]] + }); + this.awsSnsProviderConfigurationFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.awsSnsProviderConfigurationFormGroup.disable({emitEvent: false}); + } else { + this.awsSnsProviderConfigurationFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: AwsSnsSmsProviderConfiguration | null): void { + if (isDefinedAndNotNull(value)) { + this.awsSnsProviderConfigurationFormGroup.patchValue(value, {emitEvent: false}); + } + } + + private updateModel() { + let configuration: AwsSnsSmsProviderConfiguration = null; + if (this.awsSnsProviderConfigurationFormGroup.valid) { + configuration = this.awsSnsProviderConfigurationFormGroup.value; + } + this.propagateChange(configuration); + } +} diff --git a/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.html b/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.html new file mode 100644 index 0000000000..e852b82314 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.html @@ -0,0 +1,44 @@ + +
+ + admin.sms-provider-type + + + {{smsProviderTypeTranslations.get(type) | translate}} + + + + {{ 'admin.sms-provider-type-required' | translate }} + + +
+ + + + + + + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.ts b/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.ts new file mode 100644 index 0000000000..4aafff9369 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.ts @@ -0,0 +1,123 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@app/core/core.state'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { + DeviceProfileTransportConfiguration, + DeviceTransportType, + deviceTransportTypeTranslationMap +} from '@shared/models/device.models'; +import { deepClone } from '@core/utils'; +import { + createSmsProviderConfiguration, + SmsProviderConfiguration, + SmsProviderType, + smsProviderTypeTranslationMap +} from '@shared/models/settings.models'; + +@Component({ + selector: 'tb-sms-provider-configuration', + templateUrl: './sms-provider-configuration.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SmsProviderConfigurationComponent), + multi: true + }] +}) +export class SmsProviderConfigurationComponent implements ControlValueAccessor, OnInit { + + smsProviderType = SmsProviderType; + smsProviderTypes = Object.keys(SmsProviderType); + smsProviderTypeTranslations = smsProviderTypeTranslationMap; + + smsProviderConfigurationFormGroup: FormGroup; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private fb: FormBuilder) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.smsProviderConfigurationFormGroup = this.fb.group({ + type: [null, Validators.required], + configuration: [null, Validators.required] + }); + this.smsProviderConfigurationFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + this.smsProviderConfigurationFormGroup.get('type').valueChanges.subscribe(() => { + this.smsProviderTypeChanged(); + }); + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.smsProviderConfigurationFormGroup.disable({emitEvent: false}); + } else { + this.smsProviderConfigurationFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: SmsProviderConfiguration | null): void { + const configuration = deepClone(value); + const type = configuration?.type; + if (configuration) { + delete configuration.type; + } + this.smsProviderConfigurationFormGroup.patchValue({type}, {emitEvent: false}); + this.smsProviderConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); + } + + private smsProviderTypeChanged() { + const type: SmsProviderType = this.smsProviderConfigurationFormGroup.get('type').value; + this.smsProviderConfigurationFormGroup.patchValue({configuration: createSmsProviderConfiguration(type)}, {emitEvent: false}); + } + + private updateModel() { + let configuration: SmsProviderConfiguration = null; + if (this.smsProviderConfigurationFormGroup.valid) { + configuration = this.smsProviderConfigurationFormGroup.getRawValue().configuration; + configuration.type = this.smsProviderConfigurationFormGroup.getRawValue().type; + } + this.propagateChange(configuration); + } +} diff --git a/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.html b/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.html new file mode 100644 index 0000000000..449140a18e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.html @@ -0,0 +1,44 @@ + +
+ + admin.number-from + + + {{ 'admin.number-from-required' | translate }} + + + {{ 'admin.phone-number-pattern' | translate }} + + + + + admin.twilio-account-sid + + + {{ 'admin.twilio-account-sid-required' | translate }} + + + + admin.twilio-account-token + + + {{ 'admin.twilio-account-token-required' | translate }} + + +
diff --git a/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts b/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts new file mode 100644 index 0000000000..9d2c1a154b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts @@ -0,0 +1,101 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@app/core/core.state'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { isDefinedAndNotNull } from '@core/utils'; +import { phoneNumberPattern, TwilioSmsProviderConfiguration } from '@shared/models/settings.models'; + +@Component({ + selector: 'tb-twilio-sms-provider-configuration', + templateUrl: './twilio-sms-provider-configuration.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TwilioSmsProviderConfigurationComponent), + multi: true + }] +}) +export class TwilioSmsProviderConfigurationComponent implements ControlValueAccessor, OnInit { + + twilioSmsProviderConfigurationFormGroup: FormGroup; + + phoneNumberPattern = phoneNumberPattern; + + private requiredValue: boolean; + + get required(): boolean { + return this.requiredValue; + } + + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private fb: FormBuilder) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.twilioSmsProviderConfigurationFormGroup = this.fb.group({ + numberFrom: [null, [Validators.required, Validators.pattern(phoneNumberPattern)]], + accountSid: [null, [Validators.required]], + accountToken: [null, [Validators.required]] + }); + this.twilioSmsProviderConfigurationFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.twilioSmsProviderConfigurationFormGroup.disable({emitEvent: false}); + } else { + this.twilioSmsProviderConfigurationFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: TwilioSmsProviderConfiguration | null): void { + if (isDefinedAndNotNull(value)) { + this.twilioSmsProviderConfigurationFormGroup.patchValue(value, {emitEvent: false}); + } + } + + private updateModel() { + let configuration: TwilioSmsProviderConfiguration = null; + if (this.twilioSmsProviderConfigurationFormGroup.valid) { + configuration = this.twilioSmsProviderConfigurationFormGroup.value; + } + this.propagateChange(configuration); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts index 7fefbfb521..36413da3cd 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts @@ -31,6 +31,7 @@ import { Observable } from 'rxjs'; import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { OAuth2Service } from '@core/http/oauth2.service'; import { UserProfileResolver } from '@home/pages/profile/profile-routing.module'; +import { SmsProviderComponent } from '@home/pages/admin/sms-provider.component'; @Injectable() export class OAuth2LoginProcessingUrlResolver implements Resolve { @@ -85,6 +86,19 @@ const routes: Routes = [ } } }, + { + path: 'sms-provider', + component: SmsProviderComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + auth: [Authority.SYS_ADMIN], + title: 'admin.sms-provider-settings', + breadcrumb: { + label: 'admin.sms-provider', + icon: 'sms' + } + } + }, { path: 'security-settings', component: SecuritySettingsComponent, diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts index 73fef6a32f..b3dd8d4531 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts @@ -24,12 +24,16 @@ import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-sett import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component'; import { HomeComponentsModule } from '@modules/home/components/home-components.module'; import { OAuth2SettingsComponent } from '@modules/home/pages/admin/oauth2-settings.component'; +import { SmsProviderComponent } from '@home/pages/admin/sms-provider.component'; +import { SendTestSmsDialogComponent } from '@home/pages/admin/send-test-sms-dialog.component'; @NgModule({ declarations: [ GeneralSettingsComponent, MailServerComponent, + SmsProviderComponent, + SendTestSmsDialogComponent, SecuritySettingsComponent, OAuth2SettingsComponent ], diff --git a/ui-ngx/src/app/modules/home/pages/admin/send-test-sms-dialog.component.html b/ui-ngx/src/app/modules/home/pages/admin/send-test-sms-dialog.component.html new file mode 100644 index 0000000000..7b2b460294 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/send-test-sms-dialog.component.html @@ -0,0 +1,70 @@ + +
+ +

admin.send-test-sms

+ + +
+ + +
+
+
+ + admin.number-to + + + {{ 'admin.number-to-required' | translate }} + + + {{ 'admin.phone-number-pattern' | translate }} + + + + + admin.sms-message + + + {{ 'admin.sms-message-required' | translate }} + + + {{ 'admin.sms-message-max-length' | translate }} + + +
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/send-test-sms-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/admin/send-test-sms-dialog.component.ts new file mode 100644 index 0000000000..b29f48e15b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/send-test-sms-dialog.component.ts @@ -0,0 +1,87 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, OnInit } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Router } from '@angular/router'; +import { phoneNumberPattern, SmsProviderConfiguration, TestSmsRequest } from '@shared/models/settings.models'; +import { AdminService } from '@core/http/admin.service'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { TranslateService } from '@ngx-translate/core'; + +export interface SendTestSmsDialogData { + smsProviderConfiguration: SmsProviderConfiguration; +} + +@Component({ + selector: 'tb-send-test-sms-dialog', + templateUrl: './send-test-sms-dialog.component.html', + styleUrls: [] +}) +export class SendTestSmsDialogComponent extends + DialogComponent implements OnInit { + + phoneNumberPattern = phoneNumberPattern; + + sendTestSmsFormGroup: FormGroup; + + smsProviderConfiguration = this.data.smsProviderConfiguration; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: SendTestSmsDialogData, + private adminService: AdminService, + private translate: TranslateService, + public dialogRef: MatDialogRef, + public fb: FormBuilder) { + super(store, router, dialogRef); + } + + ngOnInit(): void { + this.sendTestSmsFormGroup = this.fb.group({ + numberTo: [null, [Validators.required, Validators.pattern(phoneNumberPattern)]], + message: [null, [Validators.required, Validators.maxLength(1600)]] + }); + } + + close(): void { + this.dialogRef.close(); + } + + sendTestSms(): void { + const request: TestSmsRequest = { + providerConfiguration: this.smsProviderConfiguration, + numberTo: this.sendTestSmsFormGroup.value.numberTo, + message: this.sendTestSmsFormGroup.value.message + }; + this.adminService.sendTestSms(request).subscribe( + () => { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('admin.test-sms-sent'), + target: 'sendTestSmsDialogContent', + verticalPosition: 'bottom', + horizontalPosition: 'left', + type: 'success' + })); + } + ); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.html b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.html new file mode 100644 index 0000000000..426c3eda25 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.html @@ -0,0 +1,50 @@ + +
+ + +
+ admin.sms-provider-settings + +
+
+
+ + +
+ +
+
+ + +
+ + +
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.scss b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.scss new file mode 100644 index 0000000000..bb3718c2da --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.scss @@ -0,0 +1,18 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.ts b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.ts new file mode 100644 index 0000000000..69510bd683 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.ts @@ -0,0 +1,95 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { PageComponent } from '@shared/components/page.component'; +import { Router } from '@angular/router'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { AdminSettings, SmsProviderConfiguration } from '@shared/models/settings.models'; +import { AdminService } from '@core/http/admin.service'; +import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; +import { MatDialog } from '@angular/material/dialog'; +import { SendTestSmsDialogComponent, SendTestSmsDialogData } from '@home/pages/admin/send-test-sms-dialog.component'; + +@Component({ + selector: 'tb-sms-provider', + templateUrl: './sms-provider.component.html', + styleUrls: ['./sms-provider.component.scss', './settings-card.scss'] +}) +export class SmsProviderComponent extends PageComponent implements OnInit, HasConfirmForm { + + smsProvider: FormGroup; + adminSettings: AdminSettings; + + constructor(protected store: Store, + private router: Router, + private adminService: AdminService, + private dialog: MatDialog, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.buildSmsProviderForm(); + this.adminService.getAdminSettings('sms', {ignoreErrors: true}).subscribe( + (adminSettings) => { + this.adminSettings = adminSettings; + this.smsProvider.reset({configuration: this.adminSettings.jsonValue}); + }, + () => { + this.adminSettings = { + key: 'sms', + jsonValue: null + }; + this.smsProvider.reset({configuration: this.adminSettings.jsonValue}); + } + ); + } + + buildSmsProviderForm() { + this.smsProvider = this.fb.group({ + configuration: [null, [Validators.required]] + }); + this.registerDisableOnLoadFormControl(this.smsProvider.get('configuration')); + } + + sendTestSms(): void { + this.dialog.open(SendTestSmsDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + smsProviderConfiguration: this.smsProvider.value.configuration + } + }); + } + + save(): void { + this.adminSettings.jsonValue = this.smsProvider.value.configuration; + this.adminService.saveAdminSettings(this.adminSettings).subscribe( + (adminSettings) => { + this.adminSettings = adminSettings; + this.smsProvider.reset({configuration: this.adminSettings.jsonValue}); + } + ); + } + + confirmForm(): FormGroup { + return this.smsProvider; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.html index 3097e0e303..2c1f2c603a 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+

{{ 'dashboard.public-dashboard-title' | translate }}

diff --git a/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.html b/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.html index 52d221c55f..0221d412c8 100644 --- a/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+

user.activation-link

diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index 71dcb16a37..049ba03270 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -59,6 +59,7 @@ const helpBaseUrl = 'https://thingsboard.io'; export const HelpLinks = { linksMap: { outgoingMailSettings: helpBaseUrl + '/docs/user-guide/ui/mail-settings', + smsProviderSettings: helpBaseUrl + '/docs/user-guide/ui/sms-provider-settings', securitySettings: helpBaseUrl + '/docs/user-guide/ui/security-settings', oauth2Settings: helpBaseUrl + '/docs/user-guide/oauth-2-support/', ruleEngine: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/overview/', diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index f71ac1bb2e..313b92d603 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -14,6 +14,8 @@ /// limitations under the License. /// +import { DeviceTransportType } from '@shared/models/device.models'; + export const smtpPortPattern: RegExp = /^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/; export interface AdminSettings { @@ -60,3 +62,66 @@ export interface UpdateMessage { message: string; updateAvailable: boolean; } + +export const phoneNumberPattern = /^\+[1-9]\d{1,14}$/; + +export enum SmsProviderType { + AWS_SNS = 'AWS_SNS', + TWILIO = 'TWILIO' +} + +export const smsProviderTypeTranslationMap = new Map( + [ + [SmsProviderType.AWS_SNS, 'admin.sms-provider-type-aws-sns'], + [SmsProviderType.TWILIO, 'admin.sms-provider-type-twilio'] + ] +); + +export interface AwsSnsSmsProviderConfiguration { + accessKeyId?: string; + secretAccessKey?: string; + region?: string; +} + +export interface TwilioSmsProviderConfiguration { + accountSid?: string; + accountToken?: string; + numberFrom?: string; +} + +export type SmsProviderConfigurations = AwsSnsSmsProviderConfiguration & TwilioSmsProviderConfiguration; + +export interface SmsProviderConfiguration extends SmsProviderConfigurations { + type: SmsProviderType; +} + +export interface TestSmsRequest { + providerConfiguration: SmsProviderConfiguration; + numberTo: string; + message: string; +} + +export function createSmsProviderConfiguration(type: SmsProviderType): SmsProviderConfiguration { + let smsProviderConfiguration: SmsProviderConfiguration; + if (type) { + switch (type) { + case SmsProviderType.AWS_SNS: + const awsSnsSmsProviderConfiguration: AwsSnsSmsProviderConfiguration = { + accessKeyId: '', + secretAccessKey: '', + region: 'us-east-1' + }; + smsProviderConfiguration = {...awsSnsSmsProviderConfiguration, type: SmsProviderType.AWS_SNS}; + break; + case SmsProviderType.TWILIO: + const twilioSmsProviderConfiguration: TwilioSmsProviderConfiguration = { + numberFrom: '', + accountSid: '', + accountToken: '' + }; + smsProviderConfiguration = {...twilioSmsProviderConfiguration, type: SmsProviderType.TWILIO}; + break; + } + } + return smsProviderConfiguration; +} diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index cddc73415e..ca3717eeff 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -104,6 +104,33 @@ "proxy-user": "Proxy user", "proxy-password": "Proxy password", "send-test-mail": "Send test mail", + "sms-provider": "SMS provider", + "sms-provider-settings": "SMS provider settings", + "sms-provider-type": "SMS provider type", + "sms-provider-type-required": "SMS provider type is required.", + "sms-provider-type-aws-sns": "Amazon SNS", + "sms-provider-type-twilio": "Twilio", + "aws-access-key-id": "AWS Access Key ID", + "aws-access-key-id-required": "AWS Access Key ID is required", + "aws-secret-access-key": "AWS Secret Access Key", + "aws-secret-access-key-required": "AWS Secret Access Key is required", + "aws-region": "AWS Region", + "aws-region-required": "AWS Region is required", + "number-from": "Phone Number From", + "number-from-required": "Phone Number From is required.", + "number-to": "Phone Number To", + "number-to-required": "Phone Number To is required.", + "phone-number-hint": "Phone Number in E.164 format, ex. +19995550123", + "phone-number-pattern": "Invalid phone number. Should be in E.164 format, ex. +19995550123.", + "sms-message": "SMS message", + "sms-message-required": "SMS message is required.", + "sms-message-max-length": "SMS message can't be longer 1600 characters", + "twilio-account-sid": "Twilio Account SID", + "twilio-account-sid-required": "Twilio Account SID is required", + "twilio-account-token": "Twilio Account Token", + "twilio-account-token-required": "Twilio Account Token is required", + "send-test-sms": "Send test SMS", + "test-sms-sent": "Test SMS was successfully sent!", "security-settings": "Security settings", "password-policy": "Password policy", "minimum-password-length": "Minimum password length", From ffdf56222e843d80c2f27dbcd5ecc54c1ca2583b Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 20 Nov 2020 14:58:24 +0200 Subject: [PATCH 389/463] Add send sms rule node config ui --- .../org/thingsboard/server/service/sms/AbstractSmsSender.java | 1 - .../resources/public/static/rulenode/rulenode-core-config.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java b/application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java index 1add9cd79b..98c96c3564 100644 --- a/application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java +++ b/application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java @@ -18,7 +18,6 @@ package org.thingsboard.server.service.sms; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.sms.SmsSender; import org.thingsboard.rule.engine.api.sms.exception.SmsParseException; -import sun.misc.Regexp; import java.util.regex.Pattern; diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js index a8bd79d68b..0926cb41cb 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js @@ -1,4 +1,4 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core"),require("@angular/common"),require("@ngx-translate/core"),require("@shared/public-api"),require("@ngrx/store"),require("@angular/forms"),require("@core/public-api"),require("@angular/cdk/keycodes"),require("@home/components/public-api"),require("@angular/cdk/coercion"),require("@angular/material/chips"),require("@angular/material/autocomplete"),require("rxjs"),require("rxjs/operators")):"function"==typeof define&&define.amd?define("rulenode-core-config",["exports","@angular/core","@angular/common","@ngx-translate/core","@shared/public-api","@ngrx/store","@angular/forms","@core/public-api","@angular/cdk/keycodes","@home/components/public-api","@angular/cdk/coercion","@angular/material/chips","@angular/material/autocomplete","rxjs","rxjs/operators"],t):t((e=e||self)["rulenode-core-config"]={},e.ng.core,e.ng.common,e["ngx-translate"],e.shared,e["ngrx-store"],e.ng.forms,e.core,e.ng.cdk.keycodes,e.publicApi$2,e.ng.cdk.coercion,e.ng.material.chips,e.ng.material.autocomplete,e.rxjs,e.rxjs.operators)}(this,(function(e,t,r,n,a,o,i,l,s,m,u,d,p,c,f){"use strict"; +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core"),require("@angular/common"),require("@ngx-translate/core"),require("@shared/public-api"),require("@ngrx/store"),require("@angular/forms"),require("@home/components/public-api"),require("@core/public-api"),require("@angular/cdk/keycodes"),require("@angular/cdk/coercion"),require("@angular/material/chips"),require("@angular/material/autocomplete"),require("rxjs"),require("rxjs/operators")):"function"==typeof define&&define.amd?define("rulenode-core-config",["exports","@angular/core","@angular/common","@ngx-translate/core","@shared/public-api","@ngrx/store","@angular/forms","@home/components/public-api","@core/public-api","@angular/cdk/keycodes","@angular/cdk/coercion","@angular/material/chips","@angular/material/autocomplete","rxjs","rxjs/operators"],t):t((e=e||self)["rulenode-core-config"]={},e.ng.core,e.ng.common,e["ngx-translate"],e.shared,e["ngrx-store"],e.ng.forms,e.publicApi$1,e.core,e.ng.cdk.keycodes,e.ng.cdk.coercion,e.ng.material.chips,e.ng.material.autocomplete,e.rxjs,e.rxjs.operators)}(this,(function(e,t,r,n,a,o,i,l,s,m,u,d,p,c,f){"use strict"; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. @@ -12,5 +12,5 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - ***************************************************************************** */var g=function(e,t){return(g=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])})(e,t)};function y(e,t){function r(){this.constructor=e}g(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function b(e,t,r,n){var a,o=arguments.length,i=o<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,r):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,r,n);else for(var l=e.length-1;l>=0;l--)(a=e[l])&&(i=(o<3?a(i):o>3?a(t,r,i):a(t,r))||i);return o>3&&i&&Object.defineProperty(t,r,i),i}function h(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}Object.create;function C(e){var t="function"==typeof Symbol&&Symbol.iterator,r=t&&e[t],n=0;if(r)return r.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}Object.create;var v,F=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.emptyConfigForm},r.prototype.onConfigurationSet=function(e){this.emptyConfigForm=this.fb.group({})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-node-empty-config",template:"
"}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),x=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.attributesConfigForm},r.prototype.onConfigurationSet=function(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[i.Validators.required]],notifyDevice:[!e||e.scope,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-attributes-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-hint
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),T=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.timeseriesConfigForm},r.prototype.onConfigurationSet=function(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-timeseries-config",template:'
\n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),q=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcRequestConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-request-config",template:'
\n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),S=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.logConfigForm},r.prototype.onConfigurationSet=function(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),I=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.assignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),k=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.clearAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],alarmType:[e?e.alarmType:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-clear-alarm-config",template:'
\n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),N=function(e){function r(t,r,n,o){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.nodeScriptTestService=n,i.translate=o,i.alarmSeverities=Object.keys(a.AlarmSeverity),i.alarmSeverityTranslationMap=a.alarmSeverityTranslations,i.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],i}return y(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]})},r.prototype.validatorTriggers=function(){return["useMessageAlarmData"]},r.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([i.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([i.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},r.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.removeKey=function(e,t){var r=this.createAlarmConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.createAlarmConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),V=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.createRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[i.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["entityType"]},r.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([i.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-create-relation-config",template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),E=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgDelayConfigForm},r.prototype.onConfigurationSet=function(e){this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(1e5)]]})},r.prototype.validatorTriggers=function(){return["useMetadataPeriodInSecondsPatterns"]},r.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([i.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([i.Validators.required,i.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-delay-config",template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n \n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),A=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.deleteRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["deleteForSingleEntity","entityType"]},r.prototype.updateValidators=function(e){var t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-delete-relation-config",template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),L=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.generatorConfigForm},r.prototype.onConfigurationSet=function(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[i.Validators.required,i.Validators.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[i.Validators.required,i.Validators.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.prepareInputConfig=function(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e},r.prototype.prepareOutputConfig=function(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e},r.prototype.testScript=function(){var e=this,t=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId).subscribe((function(t){t&&e.generatorConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-generator-config",template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent);!function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR"}(v||(v={}));var M,P=new Map([[v.CUSTOMER,"tb.rulenode.originator-customer"],[v.TENANT,"tb.rulenode.originator-tenant"],[v.RELATED,"tb.rulenode.originator-related"],[v.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(M||(M={}));var R,w=new Map([[M.CIRCLE,"tb.rulenode.perimeter-circle"],[M.POLYGON,"tb.rulenode.perimeter-polygon"]]);!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(R||(R={}));var O,D=new Map([[R.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[R.SECONDS,"tb.rulenode.time-unit-seconds"],[R.MINUTES,"tb.rulenode.time-unit-minutes"],[R.HOURS,"tb.rulenode.time-unit-hours"],[R.DAYS,"tb.rulenode.time-unit-days"]]);!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(O||(O={}));var K,B=new Map([[O.METER,"tb.rulenode.range-unit-meter"],[O.KILOMETER,"tb.rulenode.range-unit-kilometer"],[O.FOOT,"tb.rulenode.range-unit-foot"],[O.MILE,"tb.rulenode.range-unit-mile"],[O.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);!function(e){e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(K||(K={}));var U,j,H,G=new Map([[K.TITLE,"tb.rulenode.entity-details-title"],[K.COUNTRY,"tb.rulenode.entity-details-country"],[K.STATE,"tb.rulenode.entity-details-state"],[K.ZIP,"tb.rulenode.entity-details-zip"],[K.ADDRESS,"tb.rulenode.entity-details-address"],[K.ADDRESS2,"tb.rulenode.entity-details-address2"],[K.PHONE,"tb.rulenode.entity-details-phone"],[K.EMAIL,"tb.rulenode.entity-details-email"],[K.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(U||(U={})),function(e){e.ASC="ASC",e.DESC="DESC"}(j||(j={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(H||(H={}));var z,$=new Map([[H.STANDARD,"tb.rulenode.sqs-queue-standard"],[H.FIFO,"tb.rulenode.sqs-queue-fifo"]]),Q=["anonymous","basic","cert.PEM"],_=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),W=["sas","cert.PEM"],J=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(z||(z={}));var Y=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],Z=new Map([["US-ASCII","tb.rulenode.charset-us-ascii"],["ISO-8859-1","tb.rulenode.charset-iso-8859-1"],["UTF-8","tb.rulenode.charset-utf-8"],["UTF-16BE","tb.rulenode.charset-utf-16be"],["UTF-16LE","tb.rulenode.charset-utf-16le"],["UTF-16","tb.rulenode.charset-utf-16"]]),X=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.geoActionConfigForm},r.prototype.onConfigurationSet=function(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]],minInsideDuration:[e?e.minInsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[i.Validators.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterType").setValidators([]):this.geoActionConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([])):(this.geoActionConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoActionConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoActionConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoActionConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgCountConfigForm},r.prototype.onConfigurationSet=function(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[i.Validators.required,i.Validators.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-count-config",template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcReplyConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.saveToCustomTableConfigForm},r.prototype.onConfigurationSet=function(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[i.Validators.required]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-custom-table-config",template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ne=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.translate=r,o.injector=n,o.fb=a,o.propagateChange=null,o.valueChangeSubscription=null,o}var a;return y(r,e),a=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){this.ngControl=this.injector.get(i.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},r.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t,r,n=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var o=C(Object.keys(e)),l=o.next();!l.done;l=o.next()){var s=l.value;Object.prototype.hasOwnProperty.call(e,s)&&a.push(this.fb.group({key:[s,[i.Validators.required]],value:[e[s],[i.Validators.required]]}))}}catch(e){t={error:e}}finally{try{l&&!l.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){n.updateModel()}))},r.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},r.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[i.Validators.required]],value:["",[i.Validators.required]]}))},r.prototype.validate=function(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}},r.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:t.Injector},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",String)],r.prototype,"requiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyRequiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valRequiredText",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=a=b([t.Component({selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return a})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return a})),multi:!0}],styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}),h("design:paramtypes",[o.Store,n.TranslateService,t.Injector,i.FormBuilder])],r)}(a.PageComponent),ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[i.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-device-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),ie=function(e){function r(t,r,n,o){var i,l,m=e.call(this,t)||this;m.store=t,m.translate=r,m.truncate=n,m.fb=o,m.placeholder="tb.rulenode.message-type",m.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],m.messageTypes=[],m.messageTypesList=[],m.searchText="",m.propagateChange=function(e){},m.messageTypeConfigForm=m.fb.group({messageType:[null]});try{for(var u=C(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;m.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){i={error:e}}finally{try{d&&!d.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}return m}var l;return y(r,e),l=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.ngOnInit=function(){var e=this;this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(t){return e.fetchMessageTypes(t)})),f.share())},r.prototype.ngAfterViewInit=function(){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.forEach((function(e){var r=t.messageTypesList.find((function(t){return t.value===e}));r?t.messageTypes.push({name:r.name,value:r.value}):t.messageTypes.push({name:e,value:e})}))},r.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},r.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},r.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},r.prototype.add=function(e){this.transformMessageType(e.value)},r.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return c.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return c.of(this.messageTypesList)},r.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,r=e.trim(),n=this.messageTypesList.find((function(e){return e.name===r}));(t=n?{name:n.name,value:n.value}:{name:r,value:r})&&this.addMessageType(t)}this.clear("")},r.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},r.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},r.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},r.prototype.onFocus=function(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},r.prototype.updateModel=function(){var e=this.messageTypes.map((function(e){return e.value}));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:a.TruncatePipe},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),b([t.Input(),h("design:type",String)],r.prototype,"label",void 0),b([t.Input(),h("design:type",Object)],r.prototype,"placeholder",void 0),b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.ViewChild("chipList",{static:!1}),h("design:type",d.MatChipList)],r.prototype,"chipList",void 0),b([t.ViewChild("messageTypeAutocomplete",{static:!1}),h("design:type",p.MatAutocomplete)],r.prototype,"matAutocomplete",void 0),b([t.ViewChild("messageTypeInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"messageTypeInput",void 0),r=l=b([t.Component({selector:"tb-message-types-config",template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n \n {{ \'tb.rulenode.message-types-required\' | translate }}\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return l})),multi:!0}]}),h("design:paramtypes",[o.Store,n.TranslateService,a.TruncatePipe,i.FormBuilder])],r)}(a.PageComponent),le=function(){function e(){}return e=b([t.NgModule({declarations:[ne,ae,oe,ie],imports:[r.CommonModule,a.SharedModule,m.HomeComponentsModule],exports:[ne,ae,oe,ie]})],e)}(),se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.unassignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-un-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.snsConfigForm},r.prototype.onConfigurationSet=function(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[i.Validators.required]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sns-config",template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.sqsQueueType=H,n.sqsQueueTypes=Object.keys(H),n.sqsQueueTypeTranslationsMap=$,n}return y(r,e),r.prototype.configForm=function(){return this.sqsConfigForm},r.prototype.onConfigurationSet=function(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[i.Validators.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[i.Validators.required]],delaySeconds:[e?e.delaySeconds:null,[i.Validators.min(0),i.Validators.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sqs-config",template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.pubSubConfigForm},r.prototype.onConfigurationSet=function(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[i.Validators.required]],topicName:[e?e.topicName:null,[i.Validators.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[i.Validators.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[i.Validators.required]],messageAttributes:[e?e.messageAttributes:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-pub-sub-config",template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.ackValues=["all","-1","0","1"],n.ToByteStandartCharsetTypesValues=Y,n.ToByteStandartCharsetTypeTranslationMap=Z,n}return y(r,e),r.prototype.configForm=function(){return this.kafkaConfigForm},r.prototype.onConfigurationSet=function(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],bootstrapServers:[e?e.bootstrapServers:null,[i.Validators.required]],retries:[e?e.retries:null,[i.Validators.min(0)]],batchSize:[e?e.batchSize:null,[i.Validators.min(0)]],linger:[e?e.linger:null,[i.Validators.min(0)]],bufferMemory:[e?e.bufferMemory:null,[i.Validators.min(0)]],acks:[e?e.acks:null,[i.Validators.required]],keySerializer:[e?e.keySerializer:null,[i.Validators.required]],valueSerializer:[e?e.valueSerializer:null,[i.Validators.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})},r.prototype.validatorTriggers=function(){return["addMetadataKeyValuesAsKafkaHeaders"]},r.prototype.updateValidators=function(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([i.Validators.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allMqttCredentialsTypes=Q,n.mqttCredentialsTypeTranslationsMap=_,n}return y(r,e),r.prototype.configForm=function(){return this.mqttConfigForm},r.prototype.onConfigurationSet=function(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],username:[e&&e.credentials?e.credentials.username:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;switch(t){case"anonymous":e.credentials={type:t};break;case"basic":e.credentials={type:t,username:e.credentials.username,password:e.credentials.password};break;case"cert.PEM":delete e.credentials.username}return e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.mqttConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("username").setValidators([]),t.get("password").setValidators([]),t.get("caCert").setValidators([]),t.get("caCertFileName").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"anonymous":break;case"basic":t.get("username").setValidators([i.Validators.required]),t.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("caCert").setValidators([i.Validators.required]),t.get("caCertFileName").setValidators([i.Validators.required]),t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("username").updateValueAndValidity({emitEvent:e}),t.get("password").updateValueAndValidity({emitEvent:e}),t.get("caCert").updateValueAndValidity({emitEvent:e}),t.get("caCertFileName").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-mqtt-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n \n tb.rulenode.credentials\n \n {{ mqttCredentialsTypeTranslationsMap.get(mqttConfigForm.get(\'credentials\').get(\'type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ mqttCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return y(r,e),r.prototype.configForm=function(){return this.rabbitMqConfigForm},r.prototype.onConfigurationSet=function(e){this.rabbitMqConfigForm=this.fb.group({exchangeNamePattern:[e?e.exchangeNamePattern:null,[]],routingKeyPattern:[e?e.routingKeyPattern:null,[]],messageProperties:[e?e.messageProperties:null,[]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],virtualHost:[e?e.virtualHost:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]],automaticRecoveryEnabled:[!!e&&e.automaticRecoveryEnabled,[]],connectionTimeout:[e?e.connectionTimeout:null,[i.Validators.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[i.Validators.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rabbit-mq-config",template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.proxySchemes=["http","https"],n.httpRequestTypes=Object.keys(z),n}return y(r,e),r.prototype.configForm=function(){return this.restApiCallConfigForm},r.prototype.onConfigurationSet=function(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[i.Validators.required]],requestMethod:[e?e.requestMethod:null,[i.Validators.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],enableProxy:[!!e&&e.enableProxy,[]],useSystemProxyProperties:[!!e&&e.enableProxy,[]],proxyScheme:[e?e.proxyHost:null,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[i.Validators.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]]})},r.prototype.validatorTriggers=function(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]},r.prototype.updateValidators=function(e){var t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,r=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,n=this.restApiCallConfigForm.get("enableProxy").value,a=this.restApiCallConfigForm.get("useSystemProxyProperties").value;n&&!a?(this.restApiCallConfigForm.get("proxyHost").setValidators(n?[i.Validators.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(n?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([i.Validators.min(0)])),r?this.restApiCallConfigForm.get("maxQueueSize").setValidators([i.Validators.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rest-api-call-config",template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n \n \n \n tb.rulenode.max-parallel-requests-count\n \n \n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ye=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.smtpProtocols=["smtp","smtps"],n.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"],n}return y(r,e),r.prototype.configForm=function(){return this.sendEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.sendEmailConfigForm=this.fb.group({useSystemSmtpSettings:[!!e&&e.useSystemSmtpSettings,[]],smtpProtocol:[e?e.smtpProtocol:null,[]],smtpHost:[e?e.smtpHost:null,[]],smtpPort:[e?e.smtpPort:null,[]],timeout:[e?e.timeout:null,[]],enableTls:[!!e&&e.enableTls,[]],tlsVersion:[e?e.tlsVersion:null,[]],enableProxy:[!!e&&e.enableProxy,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmtpSettings","enableProxy"]},r.prototype.updateValidators=function(e){var t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,r=this.sendEmailConfigForm.get("enableProxy").value;t?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([]),this.sendEmailConfigForm.get("proxyHost").setValidators([]),this.sendEmailConfigForm.get("proxyPort").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([i.Validators.required,i.Validators.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(r?[i.Validators.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(r?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])),this.sendEmailConfigForm.get("smtpProtocol").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpPort").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("timeout").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-send-email-config",template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.serviceType=a.ServiceType.TB_RULE_ENGINE,n}return y(r,e),r.prototype.configForm=function(){return this.checkPointConfigForm},r.prototype.onConfigurationSet=function(e){this.checkPointConfigForm=this.fb.group({queueName:[e?e.queueName:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-check-point-config",template:'
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),he=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allAzureIotHubCredentialsTypes=W,n.azureIotHubCredentialsTypeTranslationsMap=J,n}return y(r,e),r.prototype.configForm=function(){return this.azureIotHubConfigForm},r.prototype.onConfigurationSet=function(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[i.Validators.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],sasKey:[e&&e.credentials?e.credentials.sasKey:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.azureIotHubConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"sas":t.get("sasKey").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("sasKey").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-azure-iot-hub-config",template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.deviceProfile},r.prototype.onConfigurationSet=function(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState,i.Validators.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,i.Validators.required]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-device-profile-config",template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ve=function(){function e(){}return e=b([t.NgModule({declarations:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce],imports:[r.CommonModule,a.SharedModule,le],exports:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce]})],e)}(),Fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.checkMessageConfigForm},r.prototype.onConfigurationSet=function(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})},r.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},r.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},r.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},r.prototype.addMessageName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("messageNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("messageNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.prototype.addMetadataName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("metadataNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("metadataNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),xe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.checkRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],relationType:[e?e.relationType:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["checkForSingleEntity"]},r.prototype.updateValidators=function(e){var t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-relation-config",template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n}return y(r,e),r.prototype.configForm=function(){return this.geoFilterConfigForm},r.prototype.onConfigurationSet=function(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([])):(this.geoFilterConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoFilterConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoFilterConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),qe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.messageTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],n}return y(r,e),r.prototype.configForm=function(){return this.originatorTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ie=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),ke=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.switchConfigForm},r.prototype.onConfigurationSet=function(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ne=function(e){function r(t,r,n){var o,l,s=e.call(this,t)||this;s.store=t,s.translate=r,s.fb=n,s.alarmStatusTranslationsMap=a.alarmStatusTranslations,s.alarmStatusList=[],s.searchText="",s.displayStatusFn=s.displayStatus.bind(s);try{for(var m=C(Object.keys(a.AlarmStatus)),u=m.next();!u.done;u=m.next()){var d=u.value;s.alarmStatusList.push(a.AlarmStatus[d])}}catch(e){o={error:e}}finally{try{u&&!u.done&&(l=m.return)&&l.call(m)}finally{if(o)throw o.error}}return s.statusFormControl=new i.FormControl(""),s.filteredAlarmStatus=s.statusFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return s.fetchAlarmStatus(e)})),f.share()),s}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.alarmStatusConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.statusFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e?e.alarmStatusList:null,[i.Validators.required]]})},r.prototype.displayStatus=function(e){return e?this.translate.instant(a.alarmStatusTranslations.get(e)):void 0},r.prototype.fetchAlarmStatus=function(e){var t=this,r=this.getAlarmStatusList();if(this.searchText=e,this.searchText&&this.searchText.length){var n=this.searchText.toUpperCase();return c.of(r.filter((function(e){return t.translate.instant(a.alarmStatusTranslations.get(a.AlarmStatus[e])).toUpperCase().includes(n)})))}return c.of(r)},r.prototype.alarmStatusSelected=function(e){this.addAlarmStatus(e.option.value),this.clear("")},r.prototype.removeAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}},r.prototype.addAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))},r.prototype.getAlarmStatusList=function(){var e=this;return this.alarmStatusList.filter((function(t){return-1===e.alarmStatusConfigForm.get("alarmStatusList").value.indexOf(t)}))},r.prototype.onAlarmStatusInputFocus=function(){this.statusFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.alarmStatusInput.nativeElement.value=e,this.statusFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.alarmStatusInput.nativeElement.blur(),t.alarmStatusInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("alarmStatusInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"alarmStatusInput",void 0),r=b([t.Component({selector:"tb-filter-node-check-alarm-status-config",template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\n
\n
\n
\n \n
\n\n\n\n'}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ve=function(){function e(){}return e=b([t.NgModule({declarations:[Fe,xe,Te,qe,Se,Ie,ke,Ne],imports:[r.CommonModule,a.SharedModule,le],exports:[Fe,xe,Te,qe,Se,Ie,ke,Ne]})],e)}(),Ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.customerAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ae=function(e){function r(t,r,n){var a,o,l=e.call(this,t)||this;l.store=t,l.translate=r,l.fb=n,l.entityDetailsTranslationsMap=G,l.entityDetailsList=[],l.searchText="",l.displayDetailsFn=l.displayDetails.bind(l);try{for(var s=C(Object.keys(K)),m=s.next();!m.done;m=s.next()){var u=m.value;l.entityDetailsList.push(K[u])}}catch(e){a={error:e}}finally{try{m&&!m.done&&(o=s.return)&&o.call(s)}finally{if(a)throw a.error}}return l.detailsFormControl=new i.FormControl(""),l.filteredEntityDetails=l.detailsFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return l.fetchEntityDetails(e)})),f.share()),l}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.entityDetailsConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[i.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})},r.prototype.displayDetails=function(e){return e?this.translate.instant(G.get(e)):void 0},r.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var r=this.searchText.toUpperCase();return c.of(this.entityDetailsList.filter((function(e){return t.translate.instant(G.get(K[e])).toUpperCase().includes(r)})))}return c.of(this.entityDetailsList)},r.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},r.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},r.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},r.prototype.onEntityDetailsInputFocus=function(){this.detailsFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("detailsInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"detailsInput",void 0),r=b([t.Component({selector:"tb-enrichment-node-entity-details-config",template:'
\n \n tb.rulenode.entity-details\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Le=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.deviceAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[i.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.deviceAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.deviceAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.originatorAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.originatorAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.originatorAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.originatorFieldsConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n.fetchMode=U,n.fetchModes=Object.keys(U),n.samplingOrders=Object.keys(j),n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.getTelemetryFromDatabaseConfigForm},r.prototype.onConfigurationSet=function(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[i.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchMode","useMetadataIntervalPatterns"]},r.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,r=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===U.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([i.Validators.required,i.Validators.min(2),i.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),r?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([i.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},r.prototype.removeKey=function(e,t){var r=this.getTelemetryFromDatabaseConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),we=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.relatedAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[i.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.tenantAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),De=function(){function e(){}return e=b([t.NgModule({declarations:[Ee,Ae,Le,Me,Pe,Re,we,Oe],imports:[r.CommonModule,a.SharedModule,le],exports:[Ee,Ae,Le,Me,Pe,Re,we,Oe]})],e)}(),Ke=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=v,n.originatorSources=Object.keys(v),n.originatorSourceTranslationMap=P,n}return y(r,e),r.prototype.configForm=function(){return this.changeOriginatorConfigForm},r.prototype.onConfigurationSet=function(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[i.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]})},r.prototype.validatorTriggers=function(){return["originatorSource"]},r.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===v.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([i.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-change-originator-config",template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n \n \n
\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Be=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.toEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[i.Validators.required]],toTemplate:[e?e.toTemplate:null,[i.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[i.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-to-email-config",template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),je=function(){function e(){}return e=b([t.NgModule({declarations:[Ke,Be,Ue],imports:[r.CommonModule,a.SharedModule,le],exports:[Ke,Be,Ue]})],e)}(),He=function(){function e(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","client-attributes-hint":"Client attributes, use ${metaKeyName} to substitute variables from metadata","shared-attributes":"Shared attributes","shared-attributes-hint":"Shared attributes, use ${metaKeyName} to substitute variables from metadata","server-attributes":"Server attributes","server-attributes-hint":"Server attributes, use ${metaKeyName} to substitute variables from metadata","notify-device":"Notify Device","notify-device-hint":"If the message arrives from the device, we will push it back to the device by default.","latest-timeseries":"Latest timeseries","latest-timeseries-hint":"Latest timeseries, use ${metaKeyName} to substitute variables from metadata","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","alarm-status-filter":"Alarm status filter","alarm-status-list-empty":"Alarm status list is empty","no-alarm-status-matching":"No alarm status matching were found.",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata",topic:"Topic","topic-required":"Topic is required","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","device-id":"Device ID","device-id-required":"Device ID is required.","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required.","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","tls-version":"TLS version","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"Proxy port is required.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.ctorParameters=function(){return[{type:n.TranslateService}]},e=b([t.NgModule({declarations:[F],imports:[r.CommonModule,a.SharedModule],exports:[ve,Ve,De,je,F]}),h("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=He,e.ɵa=F,e.ɵb=ve,e.ɵba=be,e.ɵbb=he,e.ɵbc=Ce,e.ɵbd=le,e.ɵbe=ne,e.ɵbf=ae,e.ɵbg=oe,e.ɵbh=ie,e.ɵbi=Ve,e.ɵbj=Fe,e.ɵbk=xe,e.ɵbl=Te,e.ɵbm=qe,e.ɵbn=Se,e.ɵbo=Ie,e.ɵbp=ke,e.ɵbq=Ne,e.ɵbr=De,e.ɵbs=Ee,e.ɵbt=Ae,e.ɵbu=Le,e.ɵbv=Me,e.ɵbw=Pe,e.ɵbx=Re,e.ɵby=we,e.ɵbz=Oe,e.ɵc=x,e.ɵca=je,e.ɵcb=Ke,e.ɵcc=Be,e.ɵcd=Ue,e.ɵd=T,e.ɵe=q,e.ɵf=S,e.ɵg=I,e.ɵh=k,e.ɵi=N,e.ɵj=V,e.ɵk=E,e.ɵl=A,e.ɵm=L,e.ɵn=X,e.ɵo=ee,e.ɵp=te,e.ɵq=re,e.ɵr=se,e.ɵs=me,e.ɵt=ue,e.ɵu=de,e.ɵv=pe,e.ɵw=ce,e.ɵx=fe,e.ɵy=ge,e.ɵz=ye,Object.defineProperty(e,"__esModule",{value:!0})})); + ***************************************************************************** */var g=function(e,t){return(g=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])})(e,t)};function y(e,t){function r(){this.constructor=e}g(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function b(e,t,r,n){var a,o=arguments.length,i=o<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,r):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,r,n);else for(var l=e.length-1;l>=0;l--)(a=e[l])&&(i=(o<3?a(i):o>3?a(t,r,i):a(t,r))||i);return o>3&&i&&Object.defineProperty(t,r,i),i}function h(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}Object.create;function C(e){var t="function"==typeof Symbol&&Symbol.iterator,r=t&&e[t],n=0;if(r)return r.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}Object.create;var v,F=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.emptyConfigForm},r.prototype.onConfigurationSet=function(e){this.emptyConfigForm=this.fb.group({})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-node-empty-config",template:"
"}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),x=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.attributesConfigForm},r.prototype.onConfigurationSet=function(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[i.Validators.required]],notifyDevice:[!e||e.scope,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-attributes-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-hint
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),T=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.timeseriesConfigForm},r.prototype.onConfigurationSet=function(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-timeseries-config",template:'
\n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),q=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcRequestConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-request-config",template:'
\n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),S=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.logConfigForm},r.prototype.onConfigurationSet=function(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),I=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.assignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),k=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.clearAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],alarmType:[e?e.alarmType:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-clear-alarm-config",template:'
\n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),N=function(e){function r(t,r,n,o){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.nodeScriptTestService=n,i.translate=o,i.alarmSeverities=Object.keys(a.AlarmSeverity),i.alarmSeverityTranslationMap=a.alarmSeverityTranslations,i.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],i}return y(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]})},r.prototype.validatorTriggers=function(){return["useMessageAlarmData"]},r.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([i.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([i.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},r.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.removeKey=function(e,t){var r=this.createAlarmConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.createAlarmConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),V=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.createRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[i.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["entityType"]},r.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([i.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-create-relation-config",template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),E=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgDelayConfigForm},r.prototype.onConfigurationSet=function(e){this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(1e5)]]})},r.prototype.validatorTriggers=function(){return["useMetadataPeriodInSecondsPatterns"]},r.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([i.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([i.Validators.required,i.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-delay-config",template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n \n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),A=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.deleteRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["deleteForSingleEntity","entityType"]},r.prototype.updateValidators=function(e){var t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-delete-relation-config",template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),L=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.generatorConfigForm},r.prototype.onConfigurationSet=function(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[i.Validators.required,i.Validators.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[i.Validators.required,i.Validators.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.prepareInputConfig=function(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e},r.prototype.prepareOutputConfig=function(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e},r.prototype.testScript=function(){var e=this,t=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId).subscribe((function(t){t&&e.generatorConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-generator-config",template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent);!function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR"}(v||(v={}));var M,P=new Map([[v.CUSTOMER,"tb.rulenode.originator-customer"],[v.TENANT,"tb.rulenode.originator-tenant"],[v.RELATED,"tb.rulenode.originator-related"],[v.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(M||(M={}));var R,w=new Map([[M.CIRCLE,"tb.rulenode.perimeter-circle"],[M.POLYGON,"tb.rulenode.perimeter-polygon"]]);!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(R||(R={}));var O,D=new Map([[R.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[R.SECONDS,"tb.rulenode.time-unit-seconds"],[R.MINUTES,"tb.rulenode.time-unit-minutes"],[R.HOURS,"tb.rulenode.time-unit-hours"],[R.DAYS,"tb.rulenode.time-unit-days"]]);!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(O||(O={}));var K,B=new Map([[O.METER,"tb.rulenode.range-unit-meter"],[O.KILOMETER,"tb.rulenode.range-unit-kilometer"],[O.FOOT,"tb.rulenode.range-unit-foot"],[O.MILE,"tb.rulenode.range-unit-mile"],[O.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);!function(e){e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(K||(K={}));var U,j,H,G=new Map([[K.TITLE,"tb.rulenode.entity-details-title"],[K.COUNTRY,"tb.rulenode.entity-details-country"],[K.STATE,"tb.rulenode.entity-details-state"],[K.ZIP,"tb.rulenode.entity-details-zip"],[K.ADDRESS,"tb.rulenode.entity-details-address"],[K.ADDRESS2,"tb.rulenode.entity-details-address2"],[K.PHONE,"tb.rulenode.entity-details-phone"],[K.EMAIL,"tb.rulenode.entity-details-email"],[K.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(U||(U={})),function(e){e.ASC="ASC",e.DESC="DESC"}(j||(j={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(H||(H={}));var z,$=new Map([[H.STANDARD,"tb.rulenode.sqs-queue-standard"],[H.FIFO,"tb.rulenode.sqs-queue-fifo"]]),Q=["anonymous","basic","cert.PEM"],_=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),W=["sas","cert.PEM"],J=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(z||(z={}));var Y=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],Z=new Map([["US-ASCII","tb.rulenode.charset-us-ascii"],["ISO-8859-1","tb.rulenode.charset-iso-8859-1"],["UTF-8","tb.rulenode.charset-utf-8"],["UTF-16BE","tb.rulenode.charset-utf-16be"],["UTF-16LE","tb.rulenode.charset-utf-16le"],["UTF-16","tb.rulenode.charset-utf-16"]]),X=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.geoActionConfigForm},r.prototype.onConfigurationSet=function(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]],minInsideDuration:[e?e.minInsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[i.Validators.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterType").setValidators([]):this.geoActionConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([])):(this.geoActionConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoActionConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoActionConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoActionConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgCountConfigForm},r.prototype.onConfigurationSet=function(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[i.Validators.required,i.Validators.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-count-config",template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcReplyConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.saveToCustomTableConfigForm},r.prototype.onConfigurationSet=function(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[i.Validators.required]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-custom-table-config",template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ne=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.translate=r,o.injector=n,o.fb=a,o.propagateChange=null,o.valueChangeSubscription=null,o}var a;return y(r,e),a=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){this.ngControl=this.injector.get(i.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},r.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t,r,n=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var o=C(Object.keys(e)),l=o.next();!l.done;l=o.next()){var s=l.value;Object.prototype.hasOwnProperty.call(e,s)&&a.push(this.fb.group({key:[s,[i.Validators.required]],value:[e[s],[i.Validators.required]]}))}}catch(e){t={error:e}}finally{try{l&&!l.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){n.updateModel()}))},r.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},r.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[i.Validators.required]],value:["",[i.Validators.required]]}))},r.prototype.validate=function(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}},r.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:t.Injector},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",String)],r.prototype,"requiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyRequiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valRequiredText",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=a=b([t.Component({selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return a})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return a})),multi:!0}],styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}),h("design:paramtypes",[o.Store,n.TranslateService,t.Injector,i.FormBuilder])],r)}(a.PageComponent),ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[i.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-device-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),ie=function(e){function r(t,r,n,o){var i,l,s=e.call(this,t)||this;s.store=t,s.translate=r,s.truncate=n,s.fb=o,s.placeholder="tb.rulenode.message-type",s.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],s.messageTypes=[],s.messageTypesList=[],s.searchText="",s.propagateChange=function(e){},s.messageTypeConfigForm=s.fb.group({messageType:[null]});try{for(var u=C(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;s.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){i={error:e}}finally{try{d&&!d.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}return s}var l;return y(r,e),l=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.ngOnInit=function(){var e=this;this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(t){return e.fetchMessageTypes(t)})),f.share())},r.prototype.ngAfterViewInit=function(){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.forEach((function(e){var r=t.messageTypesList.find((function(t){return t.value===e}));r?t.messageTypes.push({name:r.name,value:r.value}):t.messageTypes.push({name:e,value:e})}))},r.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},r.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},r.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},r.prototype.add=function(e){this.transformMessageType(e.value)},r.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return c.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return c.of(this.messageTypesList)},r.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,r=e.trim(),n=this.messageTypesList.find((function(e){return e.name===r}));(t=n?{name:n.name,value:n.value}:{name:r,value:r})&&this.addMessageType(t)}this.clear("")},r.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},r.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},r.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},r.prototype.onFocus=function(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},r.prototype.updateModel=function(){var e=this.messageTypes.map((function(e){return e.value}));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:a.TruncatePipe},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),b([t.Input(),h("design:type",String)],r.prototype,"label",void 0),b([t.Input(),h("design:type",Object)],r.prototype,"placeholder",void 0),b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.ViewChild("chipList",{static:!1}),h("design:type",d.MatChipList)],r.prototype,"chipList",void 0),b([t.ViewChild("messageTypeAutocomplete",{static:!1}),h("design:type",p.MatAutocomplete)],r.prototype,"matAutocomplete",void 0),b([t.ViewChild("messageTypeInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"messageTypeInput",void 0),r=l=b([t.Component({selector:"tb-message-types-config",template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n \n {{ \'tb.rulenode.message-types-required\' | translate }}\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return l})),multi:!0}]}),h("design:paramtypes",[o.Store,n.TranslateService,a.TruncatePipe,i.FormBuilder])],r)}(a.PageComponent),le=function(){function e(){}return e=b([t.NgModule({declarations:[ne,ae,oe,ie],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule],exports:[ne,ae,oe,ie]})],e)}(),se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.unassignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-un-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.snsConfigForm},r.prototype.onConfigurationSet=function(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[i.Validators.required]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sns-config",template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.sqsQueueType=H,n.sqsQueueTypes=Object.keys(H),n.sqsQueueTypeTranslationsMap=$,n}return y(r,e),r.prototype.configForm=function(){return this.sqsConfigForm},r.prototype.onConfigurationSet=function(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[i.Validators.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[i.Validators.required]],delaySeconds:[e?e.delaySeconds:null,[i.Validators.min(0),i.Validators.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sqs-config",template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.pubSubConfigForm},r.prototype.onConfigurationSet=function(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[i.Validators.required]],topicName:[e?e.topicName:null,[i.Validators.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[i.Validators.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[i.Validators.required]],messageAttributes:[e?e.messageAttributes:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-pub-sub-config",template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.ackValues=["all","-1","0","1"],n.ToByteStandartCharsetTypesValues=Y,n.ToByteStandartCharsetTypeTranslationMap=Z,n}return y(r,e),r.prototype.configForm=function(){return this.kafkaConfigForm},r.prototype.onConfigurationSet=function(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],bootstrapServers:[e?e.bootstrapServers:null,[i.Validators.required]],retries:[e?e.retries:null,[i.Validators.min(0)]],batchSize:[e?e.batchSize:null,[i.Validators.min(0)]],linger:[e?e.linger:null,[i.Validators.min(0)]],bufferMemory:[e?e.bufferMemory:null,[i.Validators.min(0)]],acks:[e?e.acks:null,[i.Validators.required]],keySerializer:[e?e.keySerializer:null,[i.Validators.required]],valueSerializer:[e?e.valueSerializer:null,[i.Validators.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})},r.prototype.validatorTriggers=function(){return["addMetadataKeyValuesAsKafkaHeaders"]},r.prototype.updateValidators=function(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([i.Validators.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allMqttCredentialsTypes=Q,n.mqttCredentialsTypeTranslationsMap=_,n}return y(r,e),r.prototype.configForm=function(){return this.mqttConfigForm},r.prototype.onConfigurationSet=function(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],username:[e&&e.credentials?e.credentials.username:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;switch(t){case"anonymous":e.credentials={type:t};break;case"basic":e.credentials={type:t,username:e.credentials.username,password:e.credentials.password};break;case"cert.PEM":delete e.credentials.username}return e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.mqttConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("username").setValidators([]),t.get("password").setValidators([]),t.get("caCert").setValidators([]),t.get("caCertFileName").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"anonymous":break;case"basic":t.get("username").setValidators([i.Validators.required]),t.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("caCert").setValidators([i.Validators.required]),t.get("caCertFileName").setValidators([i.Validators.required]),t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("username").updateValueAndValidity({emitEvent:e}),t.get("password").updateValueAndValidity({emitEvent:e}),t.get("caCert").updateValueAndValidity({emitEvent:e}),t.get("caCertFileName").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-mqtt-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n \n tb.rulenode.credentials\n \n {{ mqttCredentialsTypeTranslationsMap.get(mqttConfigForm.get(\'credentials\').get(\'type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ mqttCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return y(r,e),r.prototype.configForm=function(){return this.rabbitMqConfigForm},r.prototype.onConfigurationSet=function(e){this.rabbitMqConfigForm=this.fb.group({exchangeNamePattern:[e?e.exchangeNamePattern:null,[]],routingKeyPattern:[e?e.routingKeyPattern:null,[]],messageProperties:[e?e.messageProperties:null,[]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],virtualHost:[e?e.virtualHost:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]],automaticRecoveryEnabled:[!!e&&e.automaticRecoveryEnabled,[]],connectionTimeout:[e?e.connectionTimeout:null,[i.Validators.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[i.Validators.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rabbit-mq-config",template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.proxySchemes=["http","https"],n.httpRequestTypes=Object.keys(z),n}return y(r,e),r.prototype.configForm=function(){return this.restApiCallConfigForm},r.prototype.onConfigurationSet=function(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[i.Validators.required]],requestMethod:[e?e.requestMethod:null,[i.Validators.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],enableProxy:[!!e&&e.enableProxy,[]],useSystemProxyProperties:[!!e&&e.enableProxy,[]],proxyScheme:[e?e.proxyHost:null,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[i.Validators.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]]})},r.prototype.validatorTriggers=function(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]},r.prototype.updateValidators=function(e){var t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,r=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,n=this.restApiCallConfigForm.get("enableProxy").value,a=this.restApiCallConfigForm.get("useSystemProxyProperties").value;n&&!a?(this.restApiCallConfigForm.get("proxyHost").setValidators(n?[i.Validators.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(n?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([i.Validators.min(0)])),r?this.restApiCallConfigForm.get("maxQueueSize").setValidators([i.Validators.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rest-api-call-config",template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n \n \n \n tb.rulenode.max-parallel-requests-count\n \n \n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ye=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.smtpProtocols=["smtp","smtps"],n.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"],n}return y(r,e),r.prototype.configForm=function(){return this.sendEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.sendEmailConfigForm=this.fb.group({useSystemSmtpSettings:[!!e&&e.useSystemSmtpSettings,[]],smtpProtocol:[e?e.smtpProtocol:null,[]],smtpHost:[e?e.smtpHost:null,[]],smtpPort:[e?e.smtpPort:null,[]],timeout:[e?e.timeout:null,[]],enableTls:[!!e&&e.enableTls,[]],tlsVersion:[e?e.tlsVersion:null,[]],enableProxy:[!!e&&e.enableProxy,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmtpSettings","enableProxy"]},r.prototype.updateValidators=function(e){var t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,r=this.sendEmailConfigForm.get("enableProxy").value;t?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([]),this.sendEmailConfigForm.get("proxyHost").setValidators([]),this.sendEmailConfigForm.get("proxyPort").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([i.Validators.required,i.Validators.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(r?[i.Validators.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(r?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])),this.sendEmailConfigForm.get("smtpProtocol").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpPort").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("timeout").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-send-email-config",template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.serviceType=a.ServiceType.TB_RULE_ENGINE,n}return y(r,e),r.prototype.configForm=function(){return this.checkPointConfigForm},r.prototype.onConfigurationSet=function(e){this.checkPointConfigForm=this.fb.group({queueName:[e?e.queueName:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-check-point-config",template:'
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),he=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allAzureIotHubCredentialsTypes=W,n.azureIotHubCredentialsTypeTranslationsMap=J,n}return y(r,e),r.prototype.configForm=function(){return this.azureIotHubConfigForm},r.prototype.onConfigurationSet=function(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[i.Validators.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],sasKey:[e&&e.credentials?e.credentials.sasKey:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.azureIotHubConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"sas":t.get("sasKey").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("sasKey").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-azure-iot-hub-config",template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.deviceProfile},r.prototype.onConfigurationSet=function(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState,i.Validators.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,i.Validators.required]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-device-profile-config",template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ve=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.sendSmsConfigForm},r.prototype.onConfigurationSet=function(e){this.sendSmsConfigForm=this.fb.group({numbersToTemplate:[e?e.numbersToTemplate:null,[i.Validators.required]],smsMessageTemplate:[e?e.smsMessageTemplate:null,[i.Validators.required]],useSystemSmsSettings:[!!e&&e.useSystemSmsSettings,[]],smsProviderConfiguration:[e?e.smsProviderConfiguration:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmsSettings"]},r.prototype.updateValidators=function(e){this.sendSmsConfigForm.get("useSystemSmsSettings").value?this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([]):this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([i.Validators.required]),this.sendSmsConfigForm.get("smsProviderConfiguration").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-send-sms-config",template:'
\n \n tb.rulenode.numbers-to-template\n \n \n {{ \'tb.rulenode.numbers-to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.sms-message-template\n \n \n {{ \'tb.rulenode.sms-message-template-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Fe=function(){function e(){}return e=b([t.NgModule({declarations:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce,ve],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule,le],exports:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce,ve]})],e)}(),xe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.checkMessageConfigForm},r.prototype.onConfigurationSet=function(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})},r.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},r.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},r.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},r.prototype.addMessageName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("messageNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("messageNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.prototype.addMetadataName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("metadataNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("metadataNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.checkRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],relationType:[e?e.relationType:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["checkForSingleEntity"]},r.prototype.updateValidators=function(e){var t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-relation-config",template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),qe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n}return y(r,e),r.prototype.configForm=function(){return this.geoFilterConfigForm},r.prototype.onConfigurationSet=function(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([])):(this.geoFilterConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoFilterConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoFilterConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.messageTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ie=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],n}return y(r,e),r.prototype.configForm=function(){return this.originatorTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ke=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ne=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.switchConfigForm},r.prototype.onConfigurationSet=function(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ve=function(e){function r(t,r,n){var o,l,s=e.call(this,t)||this;s.store=t,s.translate=r,s.fb=n,s.alarmStatusTranslationsMap=a.alarmStatusTranslations,s.alarmStatusList=[],s.searchText="",s.displayStatusFn=s.displayStatus.bind(s);try{for(var m=C(Object.keys(a.AlarmStatus)),u=m.next();!u.done;u=m.next()){var d=u.value;s.alarmStatusList.push(a.AlarmStatus[d])}}catch(e){o={error:e}}finally{try{u&&!u.done&&(l=m.return)&&l.call(m)}finally{if(o)throw o.error}}return s.statusFormControl=new i.FormControl(""),s.filteredAlarmStatus=s.statusFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return s.fetchAlarmStatus(e)})),f.share()),s}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.alarmStatusConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.statusFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e?e.alarmStatusList:null,[i.Validators.required]]})},r.prototype.displayStatus=function(e){return e?this.translate.instant(a.alarmStatusTranslations.get(e)):void 0},r.prototype.fetchAlarmStatus=function(e){var t=this,r=this.getAlarmStatusList();if(this.searchText=e,this.searchText&&this.searchText.length){var n=this.searchText.toUpperCase();return c.of(r.filter((function(e){return t.translate.instant(a.alarmStatusTranslations.get(a.AlarmStatus[e])).toUpperCase().includes(n)})))}return c.of(r)},r.prototype.alarmStatusSelected=function(e){this.addAlarmStatus(e.option.value),this.clear("")},r.prototype.removeAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}},r.prototype.addAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))},r.prototype.getAlarmStatusList=function(){var e=this;return this.alarmStatusList.filter((function(t){return-1===e.alarmStatusConfigForm.get("alarmStatusList").value.indexOf(t)}))},r.prototype.onAlarmStatusInputFocus=function(){this.statusFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.alarmStatusInput.nativeElement.value=e,this.statusFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.alarmStatusInput.nativeElement.blur(),t.alarmStatusInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("alarmStatusInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"alarmStatusInput",void 0),r=b([t.Component({selector:"tb-filter-node-check-alarm-status-config",template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\n
\n
\n
\n \n
\n\n\n\n'}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ee=function(){function e(){}return e=b([t.NgModule({declarations:[xe,Te,qe,Se,Ie,ke,Ne,Ve],imports:[r.CommonModule,a.SharedModule,le],exports:[xe,Te,qe,Se,Ie,ke,Ne,Ve]})],e)}(),Ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.customerAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Le=function(e){function r(t,r,n){var a,o,l=e.call(this,t)||this;l.store=t,l.translate=r,l.fb=n,l.entityDetailsTranslationsMap=G,l.entityDetailsList=[],l.searchText="",l.displayDetailsFn=l.displayDetails.bind(l);try{for(var s=C(Object.keys(K)),m=s.next();!m.done;m=s.next()){var u=m.value;l.entityDetailsList.push(K[u])}}catch(e){a={error:e}}finally{try{m&&!m.done&&(o=s.return)&&o.call(s)}finally{if(a)throw a.error}}return l.detailsFormControl=new i.FormControl(""),l.filteredEntityDetails=l.detailsFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return l.fetchEntityDetails(e)})),f.share()),l}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.entityDetailsConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[i.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})},r.prototype.displayDetails=function(e){return e?this.translate.instant(G.get(e)):void 0},r.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var r=this.searchText.toUpperCase();return c.of(this.entityDetailsList.filter((function(e){return t.translate.instant(G.get(K[e])).toUpperCase().includes(r)})))}return c.of(this.entityDetailsList)},r.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},r.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},r.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},r.prototype.onEntityDetailsInputFocus=function(){this.detailsFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("detailsInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"detailsInput",void 0),r=b([t.Component({selector:"tb-enrichment-node-entity-details-config",template:'
\n \n tb.rulenode.entity-details\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.deviceAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[i.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.deviceAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.deviceAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.originatorAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.originatorAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.originatorAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.originatorFieldsConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),we=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n.fetchMode=U,n.fetchModes=Object.keys(U),n.samplingOrders=Object.keys(j),n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.getTelemetryFromDatabaseConfigForm},r.prototype.onConfigurationSet=function(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[i.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchMode","useMetadataIntervalPatterns"]},r.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,r=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===U.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([i.Validators.required,i.Validators.min(2),i.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),r?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([i.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},r.prototype.removeKey=function(e,t){var r=this.getTelemetryFromDatabaseConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.relatedAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[i.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),De=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.tenantAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ke=function(){function e(){}return e=b([t.NgModule({declarations:[Ae,Le,Me,Pe,Re,we,Oe,De],imports:[r.CommonModule,a.SharedModule,le],exports:[Ae,Le,Me,Pe,Re,we,Oe,De]})],e)}(),Be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=v,n.originatorSources=Object.keys(v),n.originatorSourceTranslationMap=P,n}return y(r,e),r.prototype.configForm=function(){return this.changeOriginatorConfigForm},r.prototype.onConfigurationSet=function(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[i.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]})},r.prototype.validatorTriggers=function(){return["originatorSource"]},r.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===v.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([i.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-change-originator-config",template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n \n \n
\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ue=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),je=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.toEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[i.Validators.required]],toTemplate:[e?e.toTemplate:null,[i.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[i.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-to-email-config",template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),He=function(){function e(){}return e=b([t.NgModule({declarations:[Be,Ue,je],imports:[r.CommonModule,a.SharedModule,le],exports:[Be,Ue,je]})],e)}(),Ge=function(){function e(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","client-attributes-hint":"Client attributes, use ${metaKeyName} to substitute variables from metadata","shared-attributes":"Shared attributes","shared-attributes-hint":"Shared attributes, use ${metaKeyName} to substitute variables from metadata","server-attributes":"Server attributes","server-attributes-hint":"Server attributes, use ${metaKeyName} to substitute variables from metadata","notify-device":"Notify Device","notify-device-hint":"If the message arrives from the device, we will push it back to the device by default.","latest-timeseries":"Latest timeseries","latest-timeseries-hint":"Latest timeseries, use ${metaKeyName} to substitute variables from metadata","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","alarm-status-filter":"Alarm status filter","alarm-status-list-empty":"Alarm status list is empty","no-alarm-status-matching":"No alarm status matching were found.",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata",topic:"Topic","topic-required":"Topic is required","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","device-id":"Device ID","device-id-required":"Device ID is required.","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required.","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","tls-version":"TLS version","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"Proxy port is required.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","numbers-to-template":"Phone Numbers To Template","numbers-to-template-required":"Phone Numbers To Template is required","numbers-to-template-hint":"Comma separated Phone Numbers, use ${metaKeyName} to substitute variables from metadata","sms-message-template":"SMS message Template","sms-message-template-required":"SMS message Template is required","sms-message-template-hint":"SMS message template, use ${metaKeyName} to substitute variables from metadata","use-system-sms-settings":"Use system SMS provider settings","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.ctorParameters=function(){return[{type:n.TranslateService}]},e=b([t.NgModule({declarations:[F],imports:[r.CommonModule,a.SharedModule],exports:[Fe,Ee,Ke,He,F]}),h("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=Ge,e.ɵa=F,e.ɵb=Fe,e.ɵba=be,e.ɵbb=he,e.ɵbc=Ce,e.ɵbd=ve,e.ɵbe=le,e.ɵbf=ne,e.ɵbg=ae,e.ɵbh=oe,e.ɵbi=ie,e.ɵbj=Ee,e.ɵbk=xe,e.ɵbl=Te,e.ɵbm=qe,e.ɵbn=Se,e.ɵbo=Ie,e.ɵbp=ke,e.ɵbq=Ne,e.ɵbr=Ve,e.ɵbs=Ke,e.ɵbt=Ae,e.ɵbu=Le,e.ɵbv=Me,e.ɵbw=Pe,e.ɵbx=Re,e.ɵby=we,e.ɵbz=Oe,e.ɵc=x,e.ɵca=De,e.ɵcb=He,e.ɵcc=Be,e.ɵcd=Ue,e.ɵce=je,e.ɵd=T,e.ɵe=q,e.ɵf=S,e.ɵg=I,e.ɵh=k,e.ɵi=N,e.ɵj=V,e.ɵk=E,e.ɵl=A,e.ɵm=L,e.ɵn=X,e.ɵo=ee,e.ɵp=te,e.ɵq=re,e.ɵr=se,e.ɵs=me,e.ɵt=ue,e.ɵu=de,e.ɵv=pe,e.ɵw=ce,e.ɵx=fe,e.ɵy=ge,e.ɵz=ye,Object.defineProperty(e,"__esModule",{value:!0})})); //# sourceMappingURL=rulenode-core-config.umd.min.js.map \ No newline at end of file From 449cb5941d06ce496bc8104eeb03f18bea2be708 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 20 Nov 2020 15:17:31 +0200 Subject: [PATCH 390/463] Fix Api Usage State alias --- .../home/components/entity/entity-filter.component.ts | 5 ++++- ui-ngx/src/app/shared/models/alias.models.ts | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.ts index 64100eacfc..2555b3d336 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.ts @@ -147,6 +147,9 @@ export class EntityFilterComponent implements ControlValueAccessor, OnInit { entityViewNameFilter: [filter ? filter.entityViewNameFilter : '', []], }); break; + case AliasFilterType.apiUsageState: + this.filterFormGroup = this.fb.group({}); + break; case AliasFilterType.relationsQuery: case AliasFilterType.assetSearchQuery: case AliasFilterType.deviceSearchQuery: @@ -190,7 +193,7 @@ export class EntityFilterComponent implements ControlValueAccessor, OnInit { private filterTypeChanged(type: AliasFilterType) { let resolveMultiple = true; - if (type === AliasFilterType.singleEntity || type === AliasFilterType.stateEntity) { + if (type === AliasFilterType.singleEntity || type === AliasFilterType.stateEntity || type === AliasFilterType.apiUsageState) { resolveMultiple = false; } if (this.resolveMultiple !== resolveMultiple) { diff --git a/ui-ngx/src/app/shared/models/alias.models.ts b/ui-ngx/src/app/shared/models/alias.models.ts index f437931f27..74662cfca2 100644 --- a/ui-ngx/src/app/shared/models/alias.models.ts +++ b/ui-ngx/src/app/shared/models/alias.models.ts @@ -108,6 +108,7 @@ export interface EntitySearchQueryFilter { fetchLastLevelOnly?: boolean; } +// tslint:disable-next-line:no-empty-interface export interface ApiUsageStateFilter { } From 1c79480b332c20151c9d99acef82f3059522617a Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 17 Nov 2020 17:32:47 +0200 Subject: [PATCH 391/463] created sendApiFeatureStateEmail --- .../DefaultTbApiUsageStateService.java | 37 +++-- .../service/mail/DefaultMailService.java | 29 ++++ .../main/resources/i18n/messages.properties | 3 +- .../resources/templates/state.disabled.ftl | 126 ++++++++++++++++++ .../resources/templates/state.enabled.ftl | 126 ++++++++++++++++++ .../resources/templates/state.warning.ftl | 126 ++++++++++++++++++ .../common/data/ApiUsageStateMailMessage.java | 25 ++++ .../rule/engine/api/MailService.java | 5 + 8 files changed, 467 insertions(+), 10 deletions(-) create mode 100644 application/src/main/resources/templates/state.disabled.ftl create mode 100644 application/src/main/resources/templates/state.enabled.ftl create mode 100644 application/src/main/resources/templates/state.warning.ftl create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateMailMessage.java diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index c7206712c3..9b45ffdaed 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -17,21 +17,21 @@ package org.thingsboard.server.service.apiusage; import com.google.common.util.concurrent.FutureCallback; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.annotation.Lazy; -import org.springframework.context.event.EventListener; -import org.springframework.core.annotation.Order; -import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.ApiUsageStateMailMessage; import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; @@ -45,6 +45,7 @@ import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.tools.SchedulerUtils; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; @@ -54,14 +55,11 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.scheduler.SchedulerComponent; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.telemetry.InternalTelemetryService; import javax.annotation.PostConstruct; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -94,6 +92,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { private final ApiUsageStateService apiUsageStateService; private final SchedulerComponent scheduler; private final TbTenantProfileCache tenantProfileCache; + private final MailService mailService; @Lazy @Autowired @@ -118,7 +117,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { TimeseriesService tsService, ApiUsageStateService apiUsageStateService, SchedulerComponent scheduler, - TbTenantProfileCache tenantProfileCache) { + TbTenantProfileCache tenantProfileCache, MailService mailService) { this.clusterService = clusterService; this.partitionService = partitionService; this.tenantService = tenantService; @@ -126,6 +125,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { this.apiUsageStateService = apiUsageStateService; this.scheduler = scheduler; this.tenantProfileCache = tenantProfileCache; + this.mailService = mailService; } @PostConstruct @@ -286,7 +286,26 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { List stateTelemetry = new ArrayList<>(); result.forEach(((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(apiFeature.getApiStateKey(), aState.name()))))); tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK); - //TODO: notify tenant admin via email! + + String email = tenantService.findTenantById(state.getTenantId()).getEmail(); + + if (StringUtils.isNotEmpty(email)) { + result.forEach((apiFeature, stateValue) -> { + ApiUsageRecordKey[] keys = ApiUsageRecordKey.getKeys(apiFeature); + ApiUsageStateMailMessage[] msgs = new ApiUsageStateMailMessage[keys.length]; + for (int i = 0; i < keys.length; i++) { + ApiUsageRecordKey key = keys[i]; + msgs[i] = new ApiUsageStateMailMessage(key, state.getProfileThreshold(key), state.get(key)); + } + try { + mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, msgs); + } catch (ThingsboardException e) { + log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e); + } + }); + } else { + log.warn("[{}] Can't send update of the API state to tenant with empty email!", state.getTenantId()); + } } private void checkStartOfNextCycle() { diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index bcf541880c..1b85c73196 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -29,6 +29,9 @@ import org.springframework.stereotype.Service; import org.springframework.ui.freemarker.FreeMarkerTemplateUtils; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.ApiFeature; +import org.thingsboard.server.common.data.ApiUsageStateMailMessage; +import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.EntityId; @@ -246,6 +249,32 @@ public class DefaultMailService implements MailService { sendMail(mailSender, mailFrom, email, subject, message); } + @Override + public void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage[] stateMailMessages) throws ThingsboardException { + String subject = messages.getMessage("api.usage.state", null, Locale.US); + + Map model = new HashMap<>(); + model.put("apiFeature", apiFeature.getApiStateKey()); + model.put("apiUsageStateMailMessages", stateMailMessages); + + model.put(TARGET_EMAIL, email); + + String message = null; + + switch (stateValue) { + case ENABLED: + message = mergeTemplateIntoString("state.enabled.ftl", model); + break; + case WARNING: + message = mergeTemplateIntoString("state.warning.ftl", model); + break; + case DISABLED: + message = mergeTemplateIntoString("state.disabled.ftl", model); + break; + } + sendMail(mailSender, mailFrom, email, subject, message); + } + private void sendMail(JavaMailSenderImpl mailSender, String mailFrom, String email, String subject, String message) throws ThingsboardException { diff --git a/application/src/main/resources/i18n/messages.properties b/application/src/main/resources/i18n/messages.properties index a34582e132..9de9c4b789 100644 --- a/application/src/main/resources/i18n/messages.properties +++ b/application/src/main/resources/i18n/messages.properties @@ -3,4 +3,5 @@ activation.subject=Your account activation on Thingsboard account.activated.subject=Thingsboard - your account has been activated reset.password.subject=Thingsboard - Password reset has been requested password.was.reset.subject=Thingsboard - your account password has been reset -account.lockout.subject=Thingsboard - User account has been lockout \ No newline at end of file +account.lockout.subject=Thingsboard - User account has been lockout +api.usage.state=Thingsboard - Api Usage State for tenant has been updated \ No newline at end of file diff --git a/application/src/main/resources/templates/state.disabled.ftl b/application/src/main/resources/templates/state.disabled.ftl new file mode 100644 index 0000000000..c1f81da181 --- /dev/null +++ b/application/src/main/resources/templates/state.disabled.ftl @@ -0,0 +1,126 @@ +<#-- + + Copyright © 2016-2020 The Thingsboard Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> + + + + + +Thingsboard - Api Usage State + + + + + + + + + + + +
+
+ + +
+ + + + + + + + <#list apiUsageStateMailMessages as msg> + + + + + + + + + +
+

Thingsboard Api Usage State for tenant has been updated

+
+ Thingsboard Usage state ${apiFeature} was updated to status DISABLED. +
+ ${msg.key.apiLimitKey} = ${msg.threshold} +
+ ${msg.key.apiCountKey} = ${msg.value} +
+ — The Thingsboard +
+ +
+
+ + diff --git a/application/src/main/resources/templates/state.enabled.ftl b/application/src/main/resources/templates/state.enabled.ftl new file mode 100644 index 0000000000..497b3cd780 --- /dev/null +++ b/application/src/main/resources/templates/state.enabled.ftl @@ -0,0 +1,126 @@ +<#-- + + Copyright © 2016-2020 The Thingsboard Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> + + + + + +Thingsboard - Api Usage State + + + + + + + + + + + +
+
+ + +
+ + + + + + + + <#list apiUsageStateMailMessages as msg> + + + + + + + + + +
+

Thingsboard Api Usage State for tenant has been updated

+
+ Thingsboard Usage state ${apiFeature} was updated to status ENABLED. +
+ ${msg.key.apiLimitKey} = ${msg.threshold} +
+ ${msg.key.apiCountKey} = ${msg.value} +
+ — The Thingsboard +
+ +
+
+ + diff --git a/application/src/main/resources/templates/state.warning.ftl b/application/src/main/resources/templates/state.warning.ftl new file mode 100644 index 0000000000..bd4424a0b9 --- /dev/null +++ b/application/src/main/resources/templates/state.warning.ftl @@ -0,0 +1,126 @@ +<#-- + + Copyright © 2016-2020 The Thingsboard Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> + + + + + +Thingsboard - Api Usage State + + + + + + + + + + + +
+
+ + +
+ + + + + + + + <#list apiUsageStateMailMessages as msg> + + + + + + + + + +
+

Thingsboard Api Usage State for tenant has been updated

+
+ Thingsboard Usage state ${apiFeature} was updated to status WARNING. +
+ ${msg.key.apiLimitKey} = ${msg.threshold} +
+ ${msg.key.apiCountKey} = ${msg.value} +
+ — The Thingsboard +
+ +
+
+ + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateMailMessage.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateMailMessage.java new file mode 100644 index 0000000000..cab3789fdc --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateMailMessage.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +import lombok.Data; + +@Data +public class ApiUsageStateMailMessage { + private final ApiUsageRecordKey key; + private final long threshold; + private final long value; +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java index a36089107d..59b96a2e67 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java @@ -16,6 +16,9 @@ package org.thingsboard.rule.engine.api; import com.fasterxml.jackson.databind.JsonNode; +import org.thingsboard.server.common.data.ApiFeature; +import org.thingsboard.server.common.data.ApiUsageStateMailMessage; +import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.exception.ThingsboardException; import javax.mail.MessagingException; @@ -39,4 +42,6 @@ public interface MailService { void send(String from, String to, String cc, String bcc, String subject, String body) throws MessagingException; void sendAccountLockoutEmail( String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException; + + void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage[] stateMailMessages) throws ThingsboardException; } From ee3abe59eabc8463ae0be84cb65b7731ae9d1f35 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 17 Nov 2020 18:06:08 +0200 Subject: [PATCH 392/463] added mail executor to DefaultTbApiUsageStateService --- .../DefaultTbApiUsageStateService.java | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 9b45ffdaed..84dfd7310b 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -59,6 +59,7 @@ import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.telemetry.InternalTelemetryService; import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -67,6 +68,8 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -111,6 +114,8 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { private final Lock updateLock = new ReentrantLock(); + private final ExecutorService mailExecutor; + public DefaultTbApiUsageStateService(TbClusterService clusterService, PartitionService partitionService, TenantService tenantService, @@ -126,6 +131,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { this.scheduler = scheduler; this.tenantProfileCache = tenantProfileCache; this.mailService = mailService; + this.mailExecutor = Executors.newSingleThreadExecutor(); } @PostConstruct @@ -297,11 +303,13 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { ApiUsageRecordKey key = keys[i]; msgs[i] = new ApiUsageStateMailMessage(key, state.getProfileThreshold(key), state.get(key)); } - try { - mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, msgs); - } catch (ThingsboardException e) { - log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e); - } + mailExecutor.submit(() -> { + try { + mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, msgs); + } catch (ThingsboardException e) { + log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e); + } + }); }); } else { log.warn("[{}] Can't send update of the API state to tenant with empty email!", state.getTenantId()); @@ -386,4 +394,10 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } + @PreDestroy + private void destroy() { + if (mailExecutor != null) { + mailExecutor.shutdownNow(); + } + } } From 3feb32c0f6fae02c3a51ba99457a7da1fb8e2fb0 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 17 Nov 2020 19:00:38 +0200 Subject: [PATCH 393/463] Improvements to the templates --- .../DefaultTbApiUsageStateService.java | 2 +- .../service/mail/DefaultMailService.java | 37 ++- .../resources/templates/state.disabled.ftl | 242 ++++++++++-------- .../server/common/data/ApiFeature.java | 13 +- .../rule/engine/api/MailService.java | 2 +- 5 files changed, 182 insertions(+), 114 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 84dfd7310b..59b63d7200 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -305,7 +305,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } mailExecutor.submit(() -> { try { - mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, msgs); + mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, msgs[0]); } catch (ThingsboardException e) { log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e); } diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 1b85c73196..19509243ef 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -250,13 +250,11 @@ public class DefaultMailService implements MailService { } @Override - public void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage[] stateMailMessages) throws ThingsboardException { + public void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage msg) throws ThingsboardException { String subject = messages.getMessage("api.usage.state", null, Locale.US); Map model = new HashMap<>(); - model.put("apiFeature", apiFeature.getApiStateKey()); - model.put("apiUsageStateMailMessages", stateMailMessages); - + model.put("apiFeature", apiFeature.getLabel()); model.put(TARGET_EMAIL, email); String message = null; @@ -269,12 +267,43 @@ public class DefaultMailService implements MailService { message = mergeTemplateIntoString("state.warning.ftl", model); break; case DISABLED: + model.put("apiLimitValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(msg)); message = mergeTemplateIntoString("state.disabled.ftl", model); break; } sendMail(mailSender, mailFrom, email, subject, message); } + private String toDisabledValueLabel(ApiFeature apiFeature) { + switch (apiFeature) { + case DB: + return "saved"; + case TRANSPORT: + return "received"; + case JS: + case RE: + return "invoked"; + default: + throw new RuntimeException("Not implemented!"); + } + } + + private String toDisabledValueLabel(ApiUsageStateMailMessage msg) { + switch (msg.getKey()) { + case STORAGE_DP_COUNT: + case TRANSPORT_DP_COUNT: + return (msg.getThreshold() / 1000000) + "M data points"; + case TRANSPORT_MSG_COUNT: + return (msg.getThreshold() / 1000000) + "M messages"; + case JS_EXEC_COUNT: + return (msg.getThreshold() / 1000000) + "M JavaScript functions"; + case RE_EXEC_COUNT: + return (msg.getThreshold() / 1000000) + "M Rule Engine nodes"; + default: + throw new RuntimeException("Not implemented!"); + } + } + private void sendMail(JavaMailSenderImpl mailSender, String mailFrom, String email, String subject, String message) throws ThingsboardException { diff --git a/application/src/main/resources/templates/state.disabled.ftl b/application/src/main/resources/templates/state.disabled.ftl index c1f81da181..33a368cceb 100644 --- a/application/src/main/resources/templates/state.disabled.ftl +++ b/application/src/main/resources/templates/state.disabled.ftl @@ -15,112 +15,148 @@ limitations under the License. --> - - + + - - -Thingsboard - Api Usage State - - - + + + Thingsboard - Api Usage State + + + - - - - - - + + +
-
- - -
- - - - - - - - <#list apiUsageStateMailMessages as msg> - - - - - - - - - -
-

Thingsboard Api Usage State for tenant has been updated

-
- Thingsboard Usage state ${apiFeature} was updated to status DISABLED. -
- ${msg.key.apiLimitKey} = ${msg.threshold} -
- ${msg.key.apiCountKey} = ${msg.value} -
- — The Thingsboard -
- -
-
+ + + + + +
+ + + + + + + + + + + + + + + +
+

Your ThingsBoard account feature was disabled

+
We have disabled the ${apiFeature} for your account because ThingsBoard has already  ${apiLimitValueLabel} + . +
Please contact your system administrator to resolve the issue. +
— The ThingsBoard +
+
+ + + + + +
This email was sent to ${targetEmail} by ThingsBoard. +
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java index e12cb5fb14..fe923cefaa 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java @@ -18,16 +18,19 @@ package org.thingsboard.server.common.data; import lombok.Getter; public enum ApiFeature { - TRANSPORT("transportApiState"), - DB("dbApiState"), - RE("ruleEngineApiState"), - JS("jsExecutionApiState"); + TRANSPORT("transportApiState", "Device API"), + DB("dbApiState", "Telemetry persistence"), + RE("ruleEngineApiState", "Rule engine execution"), + JS("jsExecutionApiState", "JavaScript functions execution"); @Getter private final String apiStateKey; + @Getter + private final String label; - ApiFeature(String apiStateKey) { + ApiFeature(String apiStateKey, String label) { this.apiStateKey = apiStateKey; + this.label = label; } } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java index 59b96a2e67..4a4228731c 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java @@ -43,5 +43,5 @@ public interface MailService { void sendAccountLockoutEmail( String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException; - void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage[] stateMailMessages) throws ThingsboardException; + void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage msg) throws ThingsboardException; } From fc7b42c3d6738a63905b0c02b42ee2f7681a9d1e Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 18 Nov 2020 15:28:16 +0200 Subject: [PATCH 394/463] Improvements to the templates --- .../DefaultTbApiUsageStateService.java | 35 ++- .../service/mail/DefaultMailService.java | 32 ++- .../resources/templates/state.enabled.ftl | 231 +++++++++-------- .../resources/templates/state.warning.ftl | 238 ++++++++++-------- 4 files changed, 320 insertions(+), 216 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 59b63d7200..330ecc23f5 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -297,15 +297,9 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { if (StringUtils.isNotEmpty(email)) { result.forEach((apiFeature, stateValue) -> { - ApiUsageRecordKey[] keys = ApiUsageRecordKey.getKeys(apiFeature); - ApiUsageStateMailMessage[] msgs = new ApiUsageStateMailMessage[keys.length]; - for (int i = 0; i < keys.length; i++) { - ApiUsageRecordKey key = keys[i]; - msgs[i] = new ApiUsageStateMailMessage(key, state.getProfileThreshold(key), state.get(key)); - } mailExecutor.submit(() -> { try { - mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, msgs[0]); + mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, createStateMailMessage(state, apiFeature, stateValue)); } catch (ThingsboardException e) { log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e); } @@ -316,6 +310,33 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } + private ApiUsageStateMailMessage createStateMailMessage(TenantApiUsageState state, ApiFeature apiFeature, ApiUsageStateValue stateValue) { + StateChecker checker = getStateChecker(stateValue); + for (ApiUsageRecordKey apiUsageRecordKey : ApiUsageRecordKey.getKeys(apiFeature)) { + long threshold = state.getProfileThreshold(apiUsageRecordKey); + long warnThreshold = state.getProfileWarnThreshold(apiUsageRecordKey); + long value = state.get(apiUsageRecordKey); + if (checker.check(threshold, warnThreshold, value)) { + return new ApiUsageStateMailMessage(apiUsageRecordKey, threshold, value); + } + } + return null; + } + + private StateChecker getStateChecker(ApiUsageStateValue stateValue) { + if (ApiUsageStateValue.ENABLED.equals(stateValue)) { + return (t, wt, v) -> true; + } else if (ApiUsageStateValue.WARNING.equals(stateValue)) { + return (t, wt, v) -> v < t && v >= wt; + } else { + return (t, wt, v) -> v >= t; + } + } + + private interface StateChecker { + boolean check(long threshold, long warnThreshold, long value); + } + private void checkStartOfNextCycle() { updateLock.lock(); try { diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 19509243ef..3045fe0cce 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -30,6 +30,7 @@ import org.springframework.ui.freemarker.FreeMarkerTemplateUtils; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.ApiFeature; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageStateMailMessage; import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; @@ -261,19 +262,36 @@ public class DefaultMailService implements MailService { switch (stateValue) { case ENABLED: + model.put("apiLabel", toEnabledValueLabel(apiFeature)); message = mergeTemplateIntoString("state.enabled.ftl", model); break; case WARNING: + model.put("apiLimitValueLabel", toDisabledValueLabel(msg.getKey(), msg.getThreshold())); + model.put("apiValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(msg.getKey(), msg.getValue())); message = mergeTemplateIntoString("state.warning.ftl", model); break; case DISABLED: - model.put("apiLimitValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(msg)); + model.put("apiLimitValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(msg.getKey(), msg.getThreshold())); message = mergeTemplateIntoString("state.disabled.ftl", model); break; } sendMail(mailSender, mailFrom, email, subject, message); } + private String toEnabledValueLabel(ApiFeature apiFeature) { + switch (apiFeature) { + case DB: + return "save"; + case TRANSPORT: + return "receive"; + case JS: + case RE: + return "invoke"; + default: + throw new RuntimeException("Not implemented!"); + } + } + private String toDisabledValueLabel(ApiFeature apiFeature) { switch (apiFeature) { case DB: @@ -288,17 +306,17 @@ public class DefaultMailService implements MailService { } } - private String toDisabledValueLabel(ApiUsageStateMailMessage msg) { - switch (msg.getKey()) { + private String toDisabledValueLabel(ApiUsageRecordKey key, long value) { + switch (key) { case STORAGE_DP_COUNT: case TRANSPORT_DP_COUNT: - return (msg.getThreshold() / 1000000) + "M data points"; + return (value / 1000000) + "M data points"; case TRANSPORT_MSG_COUNT: - return (msg.getThreshold() / 1000000) + "M messages"; + return (value / 1000000) + "M messages"; case JS_EXEC_COUNT: - return (msg.getThreshold() / 1000000) + "M JavaScript functions"; + return (value / 1000000) + "M JavaScript functions"; case RE_EXEC_COUNT: - return (msg.getThreshold() / 1000000) + "M Rule Engine nodes"; + return (value / 1000000) + "M Rule Engine nodes"; default: throw new RuntimeException("Not implemented!"); } diff --git a/application/src/main/resources/templates/state.enabled.ftl b/application/src/main/resources/templates/state.enabled.ftl index 497b3cd780..52db9683b7 100644 --- a/application/src/main/resources/templates/state.enabled.ftl +++ b/application/src/main/resources/templates/state.enabled.ftl @@ -15,112 +15,141 @@ limitations under the License. --> - - + + - - -Thingsboard - Api Usage State - - - + + + Thingsboard - Api Usage State + + + - - - -
-
- - -
- - - - - - - - <#list apiUsageStateMailMessages as msg> - - - - - - - - - -
-

Thingsboard Api Usage State for tenant has been updated

-
- Thingsboard Usage state ${apiFeature} was updated to status ENABLED. -
- ${msg.key.apiLimitKey} = ${msg.threshold} -
- ${msg.key.apiCountKey} = ${msg.value} -
- — The Thingsboard -
- -
+ + + + + + + + +
+ + + + + + + + + + + + +
+

Your ThingsBoard account feature was enabled

+
We have enabled the ${apiFeature} for your account and ThingsBoard already able to ${apiLabel} messages. +
— The ThingsBoard +
+
+ + + + - +
This email was sent to ${targetEmail} by ThingsBoard.
diff --git a/application/src/main/resources/templates/state.warning.ftl b/application/src/main/resources/templates/state.warning.ftl index bd4424a0b9..1a2787a9f5 100644 --- a/application/src/main/resources/templates/state.warning.ftl +++ b/application/src/main/resources/templates/state.warning.ftl @@ -15,112 +15,148 @@ limitations under the License. --> - - + + - - -Thingsboard - Api Usage State - - - + + + Thingsboard - Api Usage State + + + - - - - diff --git a/application/src/main/resources/templates/state.enabled.ftl b/application/src/main/resources/templates/state.enabled.ftl index 52db9683b7..0a7841f871 100644 --- a/application/src/main/resources/templates/state.enabled.ftl +++ b/application/src/main/resources/templates/state.enabled.ftl @@ -123,7 +123,7 @@ diff --git a/application/src/main/resources/templates/state.warning.ftl b/application/src/main/resources/templates/state.warning.ftl index 1a2787a9f5..1ce29100a7 100644 --- a/application/src/main/resources/templates/state.warning.ftl +++ b/application/src/main/resources/templates/state.warning.ftl @@ -117,14 +117,14 @@ diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java index fe923cefaa..1f693a51b9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java @@ -20,7 +20,7 @@ import lombok.Getter; public enum ApiFeature { TRANSPORT("transportApiState", "Device API"), DB("dbApiState", "Telemetry persistence"), - RE("ruleEngineApiState", "Rule engine execution"), + RE("ruleEngineApiState", "Rule Engine execution"), JS("jsExecutionApiState", "JavaScript functions execution"); @Getter From eb8975d504be5d02d52d7693c6afaba01da7982d Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 20 Nov 2020 18:02:09 +0200 Subject: [PATCH 396/463] SMS Email Limits --- .../actors/ruleChain/DefaultTbContext.java | 1 + .../service/mail/DefaultMailService.java | 57 ++++++++++++------- .../server/service/sms/DefaultSmsService.java | 40 +++++++++---- .../server/common/data/ApiFeature.java | 4 +- .../server/common/data/ApiUsageRecordKey.java | 10 +++- .../server/common/data/ApiUsageState.java | 16 ++++++ .../DefaultTenantProfileConfiguration.java | 6 ++ .../server/dao/model/ModelConstants.java | 2 + .../dao/model/sql/ApiUsageStateEntity.java | 10 ++++ .../usagerecord/ApiUsageStateServiceImpl.java | 2 + .../resources/sql/schema-entities-hsql.sql | 2 + .../main/resources/sql/schema-entities.sql | 2 + .../rule/engine/api/MailService.java | 5 +- .../rule/engine/api/SmsService.java | 5 +- .../rule/engine/api/TbContext.java | 1 + .../rule/engine/mail/TbSendEmailNode.java | 3 +- .../rule/engine/sms/TbSendSmsNode.java | 3 +- 17 files changed, 126 insertions(+), 43 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index b1d214069f..e645339e0b 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -34,6 +34,7 @@ import org.thingsboard.rule.engine.api.TbRelationTypes; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.TbActorRef; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index a26363c5ff..5f4ea6ad9d 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -21,7 +21,6 @@ import freemarker.template.Template; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.core.NestedRuntimeException; import org.springframework.mail.javamail.JavaMailSenderImpl; @@ -40,6 +39,8 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.settings.AdminSettingsService; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import javax.annotation.PostConstruct; import javax.mail.MessagingException; @@ -58,18 +59,24 @@ public class DefaultMailService implements MailService { public static final String UTF_8 = "UTF-8"; public static final int _10K = 10000; public static final int _1M = 1000000; - @Autowired - private MessageSource messages; - @Autowired - private Configuration freemarkerConfig; + private final MessageSource messages; + private final Configuration freemarkerConfig; + private final AdminSettingsService adminSettingsService; + private final TbApiUsageStateService apiUsageStateService; + private final TbApiUsageClient apiUsageClient; private JavaMailSenderImpl mailSender; private String mailFrom; - @Autowired - private AdminSettingsService adminSettingsService; + public DefaultMailService(MessageSource messages, Configuration freemarkerConfig, AdminSettingsService adminSettingsService, TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient) { + this.messages = messages; + this.freemarkerConfig = freemarkerConfig; + this.adminSettingsService = adminSettingsService; + this.apiUsageStateService = apiUsageStateService; + this.apiUsageClient = apiUsageClient; + } @PostConstruct private void init() { @@ -148,8 +155,11 @@ public class DefaultMailService implements MailService { } @Override - public void sendEmail(String email, String subject, String message) throws ThingsboardException { - sendMail(mailSender, mailFrom, email, subject, message); + public void sendEmail(TenantId tenantId, String email, String subject, String message) throws ThingsboardException { + if (apiUsageStateService.getApiUsageState(tenantId).isEmailSendEnabled()) { + sendMail(mailSender, mailFrom, email, subject, message); + apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1); + } } @Override @@ -223,20 +233,23 @@ public class DefaultMailService implements MailService { } @Override - public void send(String from, String to, String cc, String bcc, String subject, String body) throws MessagingException { - MimeMessage mailMsg = mailSender.createMimeMessage(); - MimeMessageHelper helper = new MimeMessageHelper(mailMsg, "UTF-8"); - helper.setFrom(StringUtils.isBlank(from) ? mailFrom : from); - helper.setTo(to.split("\\s*,\\s*")); - if (!StringUtils.isBlank(cc)) { - helper.setCc(cc.split("\\s*,\\s*")); - } - if (!StringUtils.isBlank(bcc)) { - helper.setBcc(bcc.split("\\s*,\\s*")); + public void send(TenantId tenantId, String from, String to, String cc, String bcc, String subject, String body) throws MessagingException { + if (apiUsageStateService.getApiUsageState(tenantId).isEmailSendEnabled()) { + MimeMessage mailMsg = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(mailMsg, "UTF-8"); + helper.setFrom(StringUtils.isBlank(from) ? mailFrom : from); + helper.setTo(to.split("\\s*,\\s*")); + if (!StringUtils.isBlank(cc)) { + helper.setCc(cc.split("\\s*,\\s*")); + } + if (!StringUtils.isBlank(bcc)) { + helper.setBcc(bcc.split("\\s*,\\s*")); + } + helper.setSubject(subject); + helper.setText(body); + mailSender.send(helper.getMimeMessage()); + apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1); } - helper.setSubject(subject); - helper.setText(body); - mailSender.send(helper.getMimeMessage()); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java index 05df30deae..203fc1127d 100644 --- a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java +++ b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java @@ -17,7 +17,6 @@ package org.thingsboard.server.service.sms; import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.NestedRuntimeException; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.SmsService; @@ -26,12 +25,15 @@ import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.util.mapping.JacksonUtil; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -40,14 +42,20 @@ import javax.annotation.PreDestroy; @Slf4j public class DefaultSmsService implements SmsService { - @Autowired - private SmsSenderFactory smsSenderFactory; - - @Autowired - private AdminSettingsService adminSettingsService; + private final SmsSenderFactory smsSenderFactory; + private final AdminSettingsService adminSettingsService; + private final TbApiUsageStateService apiUsageStateService; + private final TbApiUsageClient apiUsageClient; private SmsSender smsSender; + public DefaultSmsService(SmsSenderFactory smsSenderFactory, AdminSettingsService adminSettingsService, TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient) { + this.smsSenderFactory = smsSenderFactory; + this.adminSettingsService = adminSettingsService; + this.apiUsageStateService = apiUsageStateService; + this.apiUsageClient = apiUsageClient; + } + @PostConstruct private void init() { updateSmsConfiguration(); @@ -78,18 +86,26 @@ public class DefaultSmsService implements SmsService { } } - @Override - public void sendSms(String numberTo, String message) throws ThingsboardException { + private int sendSms(String numberTo, String message) throws ThingsboardException { if (this.smsSender == null) { throw new ThingsboardException("Unable to send SMS: no SMS provider configured!", ThingsboardErrorCode.GENERAL); } - this.sendSms(this.smsSender, numberTo, message); + return this.sendSms(this.smsSender, numberTo, message); } @Override - public void sendSms(String[] numbersTo, String message) throws ThingsboardException { - for (String numberTo : numbersTo) { - this.sendSms(numberTo, message); + public void sendSms(TenantId tenantId, String[] numbersTo, String message) throws ThingsboardException { + if (apiUsageStateService.getApiUsageState(tenantId).isSmsSendEnabled()) { + int smsCount = 0; + try { + for (String numberTo : numbersTo) { + smsCount += this.sendSms(numberTo, message); + } + } finally { + if (smsCount > 0) { + apiUsageClient.report(tenantId, ApiUsageRecordKey.SMS_EXEC_COUNT, smsCount); + } + } } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java index 1f693a51b9..10ae01a699 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java @@ -21,7 +21,9 @@ public enum ApiFeature { TRANSPORT("transportApiState", "Device API"), DB("dbApiState", "Telemetry persistence"), RE("ruleEngineApiState", "Rule Engine execution"), - JS("jsExecutionApiState", "JavaScript functions execution"); + JS("jsExecutionApiState", "JavaScript functions execution"), + EMAIL("emailApiState", "Email messages"), + SMS("smsApiState", "SMS messages"); @Getter private final String apiStateKey; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java index dc8556a755..70b2902256 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java @@ -23,11 +23,15 @@ public enum ApiUsageRecordKey { TRANSPORT_DP_COUNT(ApiFeature.TRANSPORT, "transportDataPointsCount", "transportDataPointsLimit"), STORAGE_DP_COUNT(ApiFeature.DB, "storageDataPointsCount", "storageDataPointsLimit"), RE_EXEC_COUNT(ApiFeature.RE, "ruleEngineExecutionCount", "ruleEngineExecutionLimit"), - JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit"); + JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit"), + EMAIL_EXEC_COUNT(ApiFeature.EMAIL, "emailCount", "emailLimit"), + SMS_EXEC_COUNT(ApiFeature.SMS, "smsCount", "smsLimit"); private static final ApiUsageRecordKey[] JS_RECORD_KEYS = {JS_EXEC_COUNT}; private static final ApiUsageRecordKey[] RE_RECORD_KEYS = {RE_EXEC_COUNT}; private static final ApiUsageRecordKey[] DB_RECORD_KEYS = {STORAGE_DP_COUNT}; private static final ApiUsageRecordKey[] TRANSPORT_RECORD_KEYS = {TRANSPORT_MSG_COUNT, TRANSPORT_DP_COUNT}; + private static final ApiUsageRecordKey[] EMAIL_RECORD_KEYS = {EMAIL_EXEC_COUNT}; + private static final ApiUsageRecordKey[] SMS_RECORD_KEYS = {SMS_EXEC_COUNT}; @Getter private final ApiFeature apiFeature; @@ -52,6 +56,10 @@ public enum ApiUsageRecordKey { return RE_RECORD_KEYS; case JS: return JS_RECORD_KEYS; + case EMAIL: + return EMAIL_RECORD_KEYS; + case SMS: + return SMS_RECORD_KEYS; default: return new ApiUsageRecordKey[]{}; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java index 52797baa3b..8f13fa8164 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java @@ -47,6 +47,12 @@ public class ApiUsageState extends BaseData implements HasTenan @Getter @Setter private ApiUsageStateValue jsExecState; + @Getter + @Setter + private ApiUsageStateValue emailExecState; + @Getter + @Setter + private ApiUsageStateValue smsExecState; public ApiUsageState() { super(); @@ -64,6 +70,8 @@ public class ApiUsageState extends BaseData implements HasTenan this.dbStorageState = ur.getDbStorageState(); this.reExecState = ur.getReExecState(); this.jsExecState = ur.getJsExecState(); + this.emailExecState = ur.getEmailExecState(); + this.smsExecState = ur.getSmsExecState(); } public boolean isTransportEnabled() { @@ -81,4 +89,12 @@ public class ApiUsageState extends BaseData implements HasTenan public boolean isJsExecEnabled() { return !ApiUsageStateValue.DISABLED.equals(jsExecState); } + + public boolean isEmailSendEnabled(){ + return !ApiUsageStateValue.DISABLED.equals(emailExecState); + } + + public boolean isSmsSendEnabled(){ + return !ApiUsageStateValue.DISABLED.equals(smsExecState); + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java index 10df66ece5..28ef5cae13 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java @@ -42,6 +42,8 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura private long maxJSExecutions; private long maxDPStorageDays; private int maxRuleNodeExecutionsPerMessage; + private long maxEmails; + private long maxSms; private double warnThreshold; @@ -58,6 +60,10 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura return maxREExecutions; case STORAGE_DP_COUNT: return maxDPStorageDays; + case EMAIL_EXEC_COUNT: + return maxEmails; + case SMS_EXEC_COUNT: + return maxSms; } return 0L; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index e863de1edc..e55f55eef2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -450,6 +450,8 @@ public class ModelConstants { public static final String API_USAGE_STATE_DB_STORAGE_COLUMN = "db_storage"; public static final String API_USAGE_STATE_RE_EXEC_COLUMN = "re_exec"; public static final String API_USAGE_STATE_JS_EXEC_COLUMN = "js_exec"; + public static final String API_USAGE_STATE_EMAIL_EXEC_COLUMN = "email_exec"; + public static final String API_USAGE_STATE_SMS_EXEC_COLUMN = "sms_exec"; /** * Cassandra attributes and timeseries constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java index bc4aed6398..4b07d02321 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java @@ -63,6 +63,12 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements @Enumerated(EnumType.STRING) @Column(name = ModelConstants.API_USAGE_STATE_JS_EXEC_COLUMN) private ApiUsageStateValue jsExecState = ApiUsageStateValue.ENABLED; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.API_USAGE_STATE_EMAIL_EXEC_COLUMN) + private ApiUsageStateValue emailExecState = ApiUsageStateValue.ENABLED; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.API_USAGE_STATE_SMS_EXEC_COLUMN) + private ApiUsageStateValue smsExecState = ApiUsageStateValue.ENABLED; public ApiUsageStateEntity() { } @@ -83,6 +89,8 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements this.dbStorageState = ur.getDbStorageState(); this.reExecState = ur.getReExecState(); this.jsExecState = ur.getJsExecState(); + this.emailExecState = ur.getEmailExecState(); + this.smsExecState = ur.getSmsExecState(); } @Override @@ -99,6 +107,8 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements ur.setDbStorageState(dbStorageState); ur.setReExecState(reExecState); ur.setJsExecState(jsExecState); + ur.setEmailExecState(emailExecState); + ur.setSmsExecState(smsExecState); return ur; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index 7836154c62..5fc1c293af 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -78,6 +78,8 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A apiUsageState.setReExecState(ApiUsageStateValue.ENABLED); apiUsageState.setJsExecState(ApiUsageStateValue.ENABLED); apiUsageState.setDbStorageState(ApiUsageStateValue.ENABLED); + apiUsageState.setSmsExecState(ApiUsageStateValue.ENABLED); + apiUsageState.setEmailExecState(ApiUsageStateValue.ENABLED); apiUsageStateValidator.validate(apiUsageState, ApiUsageState::getTenantId); ApiUsageState saved = apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index 0e8f8a7791..998e268bdc 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -416,5 +416,7 @@ CREATE TABLE IF NOT EXISTS api_usage_state ( db_storage varchar(32), re_exec varchar(32), js_exec varchar(32), + email_exec varchar(32), + sms_exec varchar(32), CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) ); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index db0362a877..329b486aff 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -442,6 +442,8 @@ CREATE TABLE IF NOT EXISTS api_usage_state ( db_storage varchar(32), re_exec varchar(32), js_exec varchar(32), + email_exec varchar(32), + sms_exec varchar(32), CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) ); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java index 4a4228731c..2add686349 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiUsageStateMailMessage; import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.TenantId; import javax.mail.MessagingException; @@ -27,7 +28,7 @@ public interface MailService { void updateMailConfiguration(); - void sendEmail(String email, String subject, String message) throws ThingsboardException; + void sendEmail(TenantId tenantId, String email, String subject, String message) throws ThingsboardException; void sendTestMail(JsonNode config, String email) throws ThingsboardException; @@ -39,7 +40,7 @@ public interface MailService { void sendPasswordWasResetEmail(String loginLink, String email) throws ThingsboardException; - void send(String from, String to, String cc, String bcc, String subject, String body) throws MessagingException; + void send(TenantId tenantId, String from, String to, String cc, String bcc, String subject, String body) throws MessagingException; void sendAccountLockoutEmail( String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java index a041ad9390..fb34383a9d 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java @@ -17,14 +17,13 @@ package org.thingsboard.rule.engine.api; import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.TenantId; public interface SmsService { void updateSmsConfiguration(); - void sendSms(String numberTo, String message) throws ThingsboardException; - - void sendSms(String[] numbersTo, String message) throws ThingsboardException;; + void sendSms(TenantId tenantId, String[] numbersTo, String message) throws ThingsboardException;; void sendTestSms(TestSmsRequest testSmsRequest) throws ThingsboardException; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index b104c6f07b..eb494af569 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -19,6 +19,7 @@ import io.netty.channel.EventLoopGroup; import org.springframework.data.redis.core.RedisTemplate; import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java index 3ca2d5dbab..7c1bc67e21 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java @@ -26,6 +26,7 @@ import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -87,7 +88,7 @@ public class TbSendEmailNode implements TbNode { private void sendEmail(TbContext ctx, EmailPojo email) throws Exception { if (this.config.isUseSystemSmtpSettings()) { - ctx.getMailService().send(email.getFrom(), email.getTo(), email.getCc(), + ctx.getMailService().send(ctx.getTenantId(), email.getFrom(), email.getTo(), email.getCc(), email.getBcc(), email.getSubject(), email.getBody()); } else { MimeMessage mailMsg = mailSender.createMimeMessage(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java index f2c6ad3725..d06ecdd3fe 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java @@ -23,6 +23,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.sms.SmsSender; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -75,7 +76,7 @@ public class TbSendSmsNode implements TbNode { String message = TbNodeUtils.processPattern(this.config.getSmsMessageTemplate(), msg.getMetaData()); String[] numbersToList = numbersTo.split(","); if (this.config.isUseSystemSmsSettings()) { - ctx.getSmsService().sendSms(numbersToList, message); + ctx.getSmsService().sendSms(ctx.getTenantId(), numbersToList, message); } else { for (String numberTo : numbersToList) { this.smsSender.sendSms(numberTo, message); From d5cf27cdbf0359f3ade857b1a9b6ec0118fe3e7b Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 20 Nov 2020 18:19:53 +0200 Subject: [PATCH 397/463] Sms provider configuration component improvements --- ui-ngx/src/app/core/utils.ts | 4 +++ ...ws-sns-provider-configuration.component.ts | 7 +++- .../sms-provider-configuration.component.ts | 4 +-- ...io-sms-provider-configuration.component.ts | 7 +++- .../src/app/shared/models/settings.models.ts | 32 +++++++++++++++++++ 5 files changed, 50 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index 4650caca77..bf58d8c2c8 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -92,6 +92,10 @@ export function isEmptyStr(value: any): boolean { return value === ''; } +export function isNotEmptyStr(value: any): boolean { + return value !== null && typeof value === 'string' && value.trim().length > 0; +} + export function isFunction(value: any): boolean { return typeof value === 'function'; } diff --git a/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.ts b/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.ts index 6b4a2e23ad..a223df4bb1 100644 --- a/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.ts @@ -20,7 +20,11 @@ import { Store } from '@ngrx/store'; import { AppState } from '@app/core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { isDefinedAndNotNull } from '@core/utils'; -import { AwsSnsSmsProviderConfiguration } from '@shared/models/settings.models'; +import { + AwsSnsSmsProviderConfiguration, + SmsProviderConfiguration, + SmsProviderType +} from '@shared/models/settings.models'; @Component({ selector: 'tb-aws-sns-provider-configuration', @@ -93,6 +97,7 @@ export class AwsSnsProviderConfigurationComponent implements ControlValueAccesso let configuration: AwsSnsSmsProviderConfiguration = null; if (this.awsSnsProviderConfigurationFormGroup.valid) { configuration = this.awsSnsProviderConfigurationFormGroup.value; + (configuration as SmsProviderConfiguration).type = SmsProviderType.AWS_SNS; } this.propagateChange(configuration); } diff --git a/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.ts b/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.ts index 4aafff9369..feea8b9939 100644 --- a/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.ts @@ -27,7 +27,7 @@ import { import { deepClone } from '@core/utils'; import { createSmsProviderConfiguration, - SmsProviderConfiguration, + SmsProviderConfiguration, smsProviderConfigurationValidator, SmsProviderType, smsProviderTypeTranslationMap } from '@shared/models/settings.models'; @@ -78,7 +78,7 @@ export class SmsProviderConfigurationComponent implements ControlValueAccessor, ngOnInit() { this.smsProviderConfigurationFormGroup = this.fb.group({ type: [null, Validators.required], - configuration: [null, Validators.required] + configuration: [null, smsProviderConfigurationValidator(true)] }); this.smsProviderConfigurationFormGroup.valueChanges.subscribe(() => { this.updateModel(); diff --git a/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts b/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts index 9d2c1a154b..5577a16a00 100644 --- a/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts @@ -20,7 +20,11 @@ import { Store } from '@ngrx/store'; import { AppState } from '@app/core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { isDefinedAndNotNull } from '@core/utils'; -import { phoneNumberPattern, TwilioSmsProviderConfiguration } from '@shared/models/settings.models'; +import { + phoneNumberPattern, + SmsProviderConfiguration, SmsProviderType, + TwilioSmsProviderConfiguration +} from '@shared/models/settings.models'; @Component({ selector: 'tb-twilio-sms-provider-configuration', @@ -95,6 +99,7 @@ export class TwilioSmsProviderConfigurationComponent implements ControlValueAcce let configuration: TwilioSmsProviderConfiguration = null; if (this.twilioSmsProviderConfigurationFormGroup.valid) { configuration = this.twilioSmsProviderConfigurationFormGroup.value; + (configuration as SmsProviderConfiguration).type = SmsProviderType.TWILIO; } this.propagateChange(configuration); } diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index 313b92d603..582b077cac 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -15,6 +15,8 @@ /// import { DeviceTransportType } from '@shared/models/device.models'; +import { ValidatorFn } from '@angular/forms'; +import { isNotEmptyStr } from '@core/utils'; export const smtpPortPattern: RegExp = /^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/; @@ -95,6 +97,36 @@ export interface SmsProviderConfiguration extends SmsProviderConfigurations { type: SmsProviderType; } +export function smsProviderConfigurationValidator(required: boolean): ValidatorFn { + return control => { + const configuration: SmsProviderConfiguration = control.value; + let errors = null; + if (required) { + let valid = false; + if (configuration && configuration.type) { + switch (configuration.type) { + case SmsProviderType.AWS_SNS: + const awsSnsConfiguration: AwsSnsSmsProviderConfiguration = configuration; + valid = isNotEmptyStr(awsSnsConfiguration.accessKeyId) && isNotEmptyStr(awsSnsConfiguration.secretAccessKey) + && isNotEmptyStr(awsSnsConfiguration.region); + break; + case SmsProviderType.TWILIO: + const twilioConfiguration: TwilioSmsProviderConfiguration = configuration; + valid = isNotEmptyStr(twilioConfiguration.numberFrom) && isNotEmptyStr(twilioConfiguration.accountSid) + && isNotEmptyStr(twilioConfiguration.accountToken); + break; + } + } + if (!valid) { + errors = { + invalid: true + }; + } + } + return errors; + }; +} + export interface TestSmsRequest { providerConfiguration: SmsProviderConfiguration; numberTo: string; From 6f780b8fa540068807382ce3aa7386c5577c91ba Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 20 Nov 2020 18:36:02 +0200 Subject: [PATCH 398/463] Minor improvements --- ui-ngx/src/app/shared/models/settings.models.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index 582b077cac..64c5dda85c 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -14,7 +14,6 @@ /// limitations under the License. /// -import { DeviceTransportType } from '@shared/models/device.models'; import { ValidatorFn } from '@angular/forms'; import { isNotEmptyStr } from '@core/utils'; From d2f63c762f0ce0327d9179b1dd2a784511c386b8 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 20 Nov 2020 19:01:02 +0200 Subject: [PATCH 399/463] Fix circular dependency --- .../server/service/mail/DefaultMailService.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 5f4ea6ad9d..057eb098e4 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -21,7 +21,9 @@ import freemarker.template.Template; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Lazy; import org.springframework.core.NestedRuntimeException; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.mail.javamail.MimeMessageHelper; @@ -63,18 +65,20 @@ public class DefaultMailService implements MailService { private final MessageSource messages; private final Configuration freemarkerConfig; private final AdminSettingsService adminSettingsService; - private final TbApiUsageStateService apiUsageStateService; private final TbApiUsageClient apiUsageClient; + @Lazy + @Autowired + private TbApiUsageStateService apiUsageStateService; + private JavaMailSenderImpl mailSender; private String mailFrom; - public DefaultMailService(MessageSource messages, Configuration freemarkerConfig, AdminSettingsService adminSettingsService, TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient) { + public DefaultMailService(MessageSource messages, Configuration freemarkerConfig, AdminSettingsService adminSettingsService, TbApiUsageClient apiUsageClient) { this.messages = messages; this.freemarkerConfig = freemarkerConfig; this.adminSettingsService = adminSettingsService; - this.apiUsageStateService = apiUsageStateService; this.apiUsageClient = apiUsageClient; } From eb4f4baafb174067b8e68f39a3636b5cc4151431 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 20 Nov 2020 19:09:42 +0200 Subject: [PATCH 400/463] UI: Update Tenant Profile - add max emails and sms --- ...enant-profile-configuration.component.html | 24 +++++++++++++++++++ ...-tenant-profile-configuration.component.ts | 4 +++- ui-ngx/src/app/shared/models/tenant.model.ts | 6 ++++- .../assets/locale/locale.constant-en_US.json | 8 ++++++- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html index ae9fd8ea9c..fa0029b46f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html @@ -160,6 +160,30 @@ {{ 'tenant-profile.max-rule-node-executions-per-message-range' | translate}} + + tenant-profile.max-emails + + + {{ 'tenant-profile.max-emails-required' | translate}} + + + {{ 'tenant-profile.max-emails-range' | translate}} + + + + tenant-profile.max-sms + + + {{ 'tenant-profile.max-sms-required' | translate}} + + + {{ 'tenant-profile.max-sms-range' | translate}} + + tenant-profile.transport-tenant-msg-rate-limit diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts index e88537736b..641f14d52c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts @@ -70,7 +70,9 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA maxREExecutions: [null, [Validators.required, Validators.min(0)]], maxJSExecutions: [null, [Validators.required, Validators.min(0)]], maxDPStorageDays: [null, [Validators.required, Validators.min(0)]], - maxRuleNodeExecutionsPerMessage: [null, [Validators.required, Validators.min(0)]] + maxRuleNodeExecutionsPerMessage: [null, [Validators.required, Validators.min(0)]], + maxEmails: [null, [Validators.required, Validators.min(0)]], + maxSms: [null, [Validators.required, Validators.min(0)]] }); this.defaultTenantProfileConfigurationFormGroup.valueChanges.subscribe(() => { this.updateModel(); diff --git a/ui-ngx/src/app/shared/models/tenant.model.ts b/ui-ngx/src/app/shared/models/tenant.model.ts index ce22c27fac..f682bf1d39 100644 --- a/ui-ngx/src/app/shared/models/tenant.model.ts +++ b/ui-ngx/src/app/shared/models/tenant.model.ts @@ -44,6 +44,8 @@ export interface DefaultTenantProfileConfiguration { maxJSExecutions: number; maxDPStorageDays: number; maxRuleNodeExecutionsPerMessage: number; + maxEmails: number; + maxSms: number; } export type TenantProfileConfigurations = DefaultTenantProfileConfiguration; @@ -69,7 +71,9 @@ export function createTenantProfileConfiguration(type: TenantProfileType): Tenan maxREExecutions: 0, maxJSExecutions: 0, maxDPStorageDays: 0, - maxRuleNodeExecutionsPerMessage: 0 + maxRuleNodeExecutionsPerMessage: 0, + maxEmails: 0, + maxSms: 0 }; configuration = {...defaultConfiguration, type: TenantProfileType.DEFAULT}; break; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index ca3717eeff..41243f885e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2017,7 +2017,13 @@ "max-d-p-storage-days-range": "Minimum number of data points storage days can't be negative", "max-rule-node-executions-per-message": "Maximum number of rule node executions per message (0 - unlimited)", "max-rule-node-executions-per-message-required": "Maximum number of rule node executions per message is required.", - "max-rule-node-executions-per-message-range": "Minimum number of rule node executions per message can't be negative" + "max-rule-node-executions-per-message-range": "Minimum number of rule node executions per message can't be negative", + "max-emails": "Maximum number of emails sent (0 - unlimited)", + "max-emails-required": "Maximum number of emails sent is required.", + "max-emails-range": "Maximum number of emails sent can't be negative", + "max-sms": "Maximum number of SMS sent (0 - unlimited)", + "max-sms-required": "Maximum number of SMS sent is required.", + "max-sms-range": "Maximum number of SMS sent can't be negative" }, "timeinterval": { "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", From 9d359a8047daace17d037cd0af526771ae680bab Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Mon, 23 Nov 2020 11:37:56 +0200 Subject: [PATCH 401/463] Fixed inactivitiy timeout for gw sessions --- .gitignore | 2 ++ .../common/transport/service/DefaultTransportService.java | 4 ++-- pom.xml | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index da8569baa3..ae3b49af21 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ pom.xml.versionsBackup **/.env .instance_id rebuild-docker.sh +.run/** +.run \ No newline at end of file diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 6fbc5d8e8e..b4d768f397 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -418,8 +418,8 @@ public class DefaultTransportService implements TransportService { sessions.forEach((uuid, sessionMD) -> { long lastActivityTime = sessionMD.getLastActivityTime(); TransportProtos.SessionInfoProto sessionInfo = sessionMD.getSessionInfo(); - if (sessionInfo.getGwSessionIdMSB() > 0 && - sessionInfo.getGwSessionIdLSB() > 0) { + if (sessionInfo.getGwSessionIdMSB() != 0 && + sessionInfo.getGwSessionIdLSB() != 0) { SessionMetaData gwMetaData = sessions.get(new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB())); if (gwMetaData != null) { lastActivityTime = Math.max(gwMetaData.getLastActivityTime(), lastActivityTime); diff --git a/pom.xml b/pom.xml index 03b815635d..6974497db2 100755 --- a/pom.xml +++ b/pom.xml @@ -725,6 +725,7 @@ **/*.proto.js docker/haproxy/** docker/tb-node/** + .run/** JAVADOC_STYLE From 205be4e5cea74108564a6f4fd38973d1cb588713 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 23 Nov 2020 12:13:05 +0200 Subject: [PATCH 402/463] UI: Added new component copy-device-credentials --- .../copy-device-credentials.component.html | 26 ++++ .../copy-device-credentials.component.ts | 128 ++++++++++++++++++ .../home/components/home-components.module.ts | 5 +- .../home/pages/device/device.component.html | 13 +- .../home/pages/device/device.component.ts | 30 +--- .../device/devices-table-config.resolver.ts | 9 +- .../assets/locale/locale.constant-en_US.json | 2 + 7 files changed, 179 insertions(+), 34 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.html create mode 100644 ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.ts diff --git a/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.html b/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.html new file mode 100644 index 0000000000..77c3d10c4a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.html @@ -0,0 +1,26 @@ + + diff --git a/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.ts b/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.ts new file mode 100644 index 0000000000..99b4a82960 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.ts @@ -0,0 +1,128 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { EntityId } from '@shared/models/id/entity-id'; +import { DeviceService } from '@core/http/device.service'; +import { DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models'; +import { isDefinedAndNotNull, isEqual } from '@core/utils'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { distinctUntilChanged, filter, mergeMap, tap } from 'rxjs/operators'; +import { EntityType } from '@shared/models/entity-type.models'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'tb-copy-device-credentials', + templateUrl: './copy-device-credentials.component.html', + styleUrls: [] +}) +export class CopyDeviceCredentialsComponent implements OnDestroy { + + private deviceId$ = new BehaviorSubject(null); + + private credentials$ = new Subject(); + + private tooltipMessage: string; + + public hideButton = true; + + public credential: string; + + public loading = false; + + public buttonLabel: string; + + @Input() + set deviceId(deviceId: EntityId) { + this.deviceId$.next(deviceId); + } + + @Input() disabled: boolean; + + @Input() + set credentials(credential: DeviceCredentials) { + this.credentials$.next(credential); + } + + constructor(private store: Store, + private translate: TranslateService, + private deviceService: DeviceService + ) { + this.deviceId$.pipe( + filter(device => isDefinedAndNotNull(device) && device.entityType === EntityType.DEVICE), + distinctUntilChanged((prev, curr) => prev.id === curr.id), + tap(() => this.loading = true), + mergeMap(device => this.deviceService.getDeviceCredentials(device.id)) + ).subscribe(deviceCredentials => { + this.processingValue(deviceCredentials); + this.loading = false; + }); + + this.credentials$.pipe( + filter(credential => isDefinedAndNotNull(credential)), + distinctUntilChanged((prev, curr) => isEqual(prev, curr)) + ).subscribe(deviceCredentials => { + console.warn(deviceCredentials); + this.processingValue(deviceCredentials); + }); + } + + ngOnDestroy(): void { + this.deviceId$.unsubscribe(); + this.credentials$.unsubscribe(); + } + + private processingValue(credential: DeviceCredentials): void { + switch (credential.credentialsType) { + case DeviceCredentialsType.ACCESS_TOKEN: + this.hideButton = false; + this.credential = credential.credentialsId; + this.buttonLabel = this.translate.instant('device.copyAccessToken'); + this.tooltipMessage = this.translate.instant('device.accessTokenCopiedMessage'); + break; + case DeviceCredentialsType.MQTT_BASIC: + this.hideButton = false; + this.credential = this.convertObjectToString(JSON.parse(credential.credentialsValue)); + this.buttonLabel = this.translate.instant('device.copy-mqtt-authentication'); + this.tooltipMessage = this.translate.instant('device.mqtt-authentication-copied-message'); + break; + default: + this.hideButton = true; + this.credential = null; + this.buttonLabel = ''; + this.tooltipMessage = ''; + } + } + + private convertObjectToString(obj: object): string { + Object.keys(obj).forEach(k => (!obj[k] && obj[k] !== undefined) && delete obj[k]); + return JSON.stringify(obj).replace(/"([^"]+)":/g, '$1:'); + } + + onCopyCredential() { + this.store.dispatch(new ActionNotificationShow( + { + message: this.tooltipMessage, + type: 'success', + duration: 750, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); + } +} diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index ec7ccea660..54d15c1ee1 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -105,7 +105,7 @@ import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-conditio import { FilterTextComponent } from './filter/filter-text.component'; import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component'; import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component'; -import { DeviceProfileProvisionConfigurationComponent } from "./profile/device-profile-provision-configuration.component"; +import { DeviceProfileProvisionConfigurationComponent } from './profile/device-profile-provision-configuration.component'; import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component'; import { DeviceWizardDialogComponent } from './wizard/device-wizard-dialog.component'; import { DeviceCredentialsComponent } from './device/device-credentials.component'; @@ -115,6 +115,7 @@ import { EditAlarmDetailsDialogComponent } from './profile/alarm/edit-alarm-deta import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alarm/alarm-rule-condition-dialog.component'; import { DefaultTenantProfileConfigurationComponent } from './profile/tenant/default-tenant-profile-configuration.component'; import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-profile-configuration.component'; +import { CopyDeviceCredentialsComponent } from './device/copy-device-credentials.component'; @NgModule({ declarations: @@ -211,6 +212,7 @@ import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-pro AlarmScheduleComponent, DeviceWizardDialogComponent, DeviceCredentialsComponent, + CopyDeviceCredentialsComponent, AlarmScheduleDialogComponent, EditAlarmDetailsDialogComponent ], @@ -293,6 +295,7 @@ import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-pro RuleChainAutocompleteComponent, DeviceWizardDialogComponent, DeviceCredentialsComponent, + CopyDeviceCredentialsComponent, AlarmScheduleInfoComponent, AlarmScheduleComponent, AlarmScheduleDialogComponent, diff --git a/ui-ngx/src/app/modules/home/pages/device/device.component.html b/ui-ngx/src/app/modules/home/pages/device/device.component.html index e9371b4708..d781aa1373 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.html @@ -51,16 +51,17 @@ ngxClipboard (cbOnSuccess)="onDeviceIdCopied($event)" [cbContent]="entity?.id?.id" + [disabled]="(isLoading$ | async)" [fxShow]="!isEdit"> device.copyId - + +
diff --git a/ui-ngx/src/app/modules/home/pages/device/device.component.ts b/ui-ngx/src/app/modules/home/pages/device/device.component.ts index 833919eb78..74503bfddb 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.ts @@ -21,10 +21,9 @@ import { EntityComponent } from '../../components/entity/entity.component'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { createDeviceConfiguration, - createDeviceProfileConfiguration, createDeviceTransportConfiguration, + createDeviceTransportConfiguration, DeviceData, DeviceInfo, - DeviceProfileData, DeviceProfileInfo, DeviceProfileType, DeviceTransportType @@ -33,8 +32,6 @@ import { EntityType } from '@shared/models/entity-type.models'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core'; -import { DeviceService } from '@core/http/device.service'; -import { ClipboardService } from 'ngx-clipboard'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; @Component({ @@ -46,12 +43,12 @@ export class DeviceComponent extends EntityComponent { entityType = EntityType; + componentsData: any; + deviceScope: 'tenant' | 'customer' | 'customer_user'; constructor(protected store: Store, protected translate: TranslateService, - private deviceService: DeviceService, - private clipboardService: ClipboardService, @Inject('entity') protected entityValue: DeviceInfo, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, public fb: FormBuilder) { @@ -60,6 +57,7 @@ export class DeviceComponent extends EntityComponent { ngOnInit() { this.deviceScope = this.entitiesTableConfig.componentsData.deviceScope; + this.componentsData = this.entitiesTableConfigValue.componentsData; super.ngOnInit(); } @@ -114,26 +112,6 @@ export class DeviceComponent extends EntityComponent { })); } - copyAccessToken($event) { - if (this.entity.id) { - this.deviceService.getDeviceCredentials(this.entity.id.id, true).subscribe( - (deviceCredentials) => { - const credentialsId = deviceCredentials.credentialsId; - if (this.clipboardService.copyFromContent(credentialsId)) { - this.store.dispatch(new ActionNotificationShow( - { - message: this.translate.instant('device.accessTokenCopiedMessage'), - type: 'success', - duration: 750, - verticalPosition: 'bottom', - horizontalPosition: 'right' - })); - } - } - ); - } - } - onDeviceProfileUpdated() { this.entitiesTableConfig.table.updateData(false); } diff --git a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts index 0aaf8e62e7..60dffcc5fe 100644 --- a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts @@ -63,6 +63,7 @@ import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component'; import { HomeDialogsService } from '@home/dialogs/home-dialogs.service'; import { DeviceWizardDialogComponent } from '@home/components/wizard/device-wizard-dialog.component'; import { BaseData, HasId } from '@shared/models/base-data'; +import { isDefinedAndNotNull } from '@core/utils'; @Injectable() export class DevicesTableConfigResolver implements Resolve> { @@ -115,7 +116,8 @@ export class DevicesTableConfigResolver implements Resolve { + if (isDefinedAndNotNull(deviceCredential)) { + this.config.table.onEntityUpdated(device); + this.config.componentsData.deviceCredential = deviceCredential; + } }); } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index cddc73415e..6c51d65424 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -814,8 +814,10 @@ "details": "Details", "copyId": "Copy device Id", "copyAccessToken": "Copy access token", + "copy-mqtt-authentication": "Copy MQTT authentication", "idCopiedMessage": "Device Id has been copied to clipboard", "accessTokenCopiedMessage": "Device access token has been copied to clipboard", + "mqtt-authentication-copied-message": "Device MQTT authentication has been copied to clipboard", "assignedToCustomer": "Assigned to customer", "unable-delete-device-alias-title": "Unable to delete device alias", "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", From e1b9124ee48de2d9ef3be9dcf9494ffa4af939fe Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 23 Nov 2020 12:17:20 +0200 Subject: [PATCH 403/463] Refactoring --- .../modules/home/pages/device/devices-table-config.resolver.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts index 60dffcc5fe..1aefffa184 100644 --- a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts @@ -483,7 +483,6 @@ export class DevicesTableConfigResolver implements Resolve { if (isDefinedAndNotNull(deviceCredential)) { - this.config.table.onEntityUpdated(device); this.config.componentsData.deviceCredential = deviceCredential; } }); From 7f9bbb24047095d65ba769905d7e0debd397eebb Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 23 Nov 2020 12:19:34 +0200 Subject: [PATCH 404/463] Revert change after merge --- .../home/components/home-components.module.ts | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 27992d981a..113f4ae4bb 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -81,35 +81,35 @@ import { FilterSelectComponent } from '@home/components/filter/filter-select.com import { FiltersEditComponent } from '@home/components/filter/filters-edit.component'; import { FiltersEditPanelComponent } from '@home/components/filter/filters-edit-panel.component'; import { UserFilterDialogComponent } from '@home/components/filter/user-filter-dialog.component'; -import { FilterUserInfoComponent } from './filter/filter-user-info.component'; -import { FilterUserInfoDialogComponent } from './filter/filter-user-info-dialog.component'; -import { FilterPredicateValueComponent } from './filter/filter-predicate-value.component'; -import { TenantProfileAutocompleteComponent } from './profile/tenant-profile-autocomplete.component'; -import { TenantProfileComponent } from './profile/tenant-profile.component'; -import { TenantProfileDialogComponent } from './profile/tenant-profile-dialog.component'; -import { TenantProfileDataComponent } from './profile/tenant-profile-data.component'; -import { DefaultDeviceProfileConfigurationComponent } from './profile/device/default-device-profile-configuration.component'; -import { DeviceProfileConfigurationComponent } from './profile/device/device-profile-configuration.component'; -import { DeviceProfileComponent } from './profile/device-profile.component'; -import { DefaultDeviceProfileTransportConfigurationComponent } from './profile/device/default-device-profile-transport-configuration.component'; -import { DeviceProfileTransportConfigurationComponent } from './profile/device/device-profile-transport-configuration.component'; -import { DeviceProfileDialogComponent } from './profile/device-profile-dialog.component'; -import { DeviceProfileAutocompleteComponent } from './profile/device-profile-autocomplete.component'; -import { MqttDeviceProfileTransportConfigurationComponent } from './profile/device/mqtt-device-profile-transport-configuration.component'; -import { Lwm2mDeviceProfileTransportConfigurationComponent } from './profile/device/lwm2m-device-profile-transport-configuration.component'; -import { DeviceProfileAlarmsComponent } from './profile/alarm/device-profile-alarms.component'; -import { DeviceProfileAlarmComponent } from './profile/alarm/device-profile-alarm.component'; -import { CreateAlarmRulesComponent } from './profile/alarm/create-alarm-rules.component'; -import { AlarmRuleComponent } from './profile/alarm/alarm-rule.component'; -import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-condition.component'; -import { FilterTextComponent } from './filter/filter-text.component'; -import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component'; -import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component'; -import { DeviceProfileProvisionConfigurationComponent } from './profile/device-profile-provision-configuration.component'; -import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component'; -import { DeviceWizardDialogComponent } from './wizard/device-wizard-dialog.component'; -import { DeviceCredentialsComponent } from './device/device-credentials.component'; -import { AlarmScheduleInfoComponent } from './profile/alarm/alarm-schedule-info.component'; +import { FilterUserInfoComponent } from '@home/components/filter/filter-user-info.component'; +import { FilterUserInfoDialogComponent } from '@home/components/filter/filter-user-info-dialog.component'; +import { FilterPredicateValueComponent } from '@home/components/filter/filter-predicate-value.component'; +import { TenantProfileAutocompleteComponent } from '@home/components/profile/tenant-profile-autocomplete.component'; +import { TenantProfileComponent } from '@home/components/profile/tenant-profile.component'; +import { TenantProfileDialogComponent } from '@home/components/profile/tenant-profile-dialog.component'; +import { TenantProfileDataComponent } from '@home/components/profile/tenant-profile-data.component'; +import { DefaultDeviceProfileConfigurationComponent } from '@home/components/profile/device/default-device-profile-configuration.component'; +import { DeviceProfileConfigurationComponent } from '@home/components/profile/device/device-profile-configuration.component'; +import { DeviceProfileComponent } from '@home/components/profile/device-profile.component'; +import { DefaultDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/default-device-profile-transport-configuration.component'; +import { DeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/device-profile-transport-configuration.component'; +import { DeviceProfileDialogComponent } from '@home/components/profile/device-profile-dialog.component'; +import { DeviceProfileAutocompleteComponent } from '@home/components/profile/device-profile-autocomplete.component'; +import { MqttDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/mqtt-device-profile-transport-configuration.component'; +import { Lwm2mDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/lwm2m-device-profile-transport-configuration.component'; +import { DeviceProfileAlarmsComponent } from '@home/components/profile/alarm/device-profile-alarms.component'; +import { DeviceProfileAlarmComponent } from '@home/components/profile/alarm/device-profile-alarm.component'; +import { CreateAlarmRulesComponent } from '@home/components/profile/alarm/create-alarm-rules.component'; +import { AlarmRuleComponent } from '@home/components/profile/alarm/alarm-rule.component'; +import { AlarmRuleConditionComponent } from '@home/components/profile/alarm/alarm-rule-condition.component'; +import { FilterTextComponent } from '@home/components/filter/filter-text.component'; +import { AddDeviceProfileDialogComponent } from '@home/components/profile/add-device-profile-dialog.component'; +import { RuleChainAutocompleteComponent } from '@home/components/rule-chain/rule-chain-autocomplete.component'; +import { DeviceProfileProvisionConfigurationComponent } from '@home/components/profile/device-profile-provision-configuration.component'; +import { AlarmScheduleComponent } from '@home/components/profile/alarm/alarm-schedule.component'; +import { DeviceWizardDialogComponent } from '@home/components/wizard/device-wizard-dialog.component'; +import { DeviceCredentialsComponent } from '@home/components/device/device-credentials.component'; +import { AlarmScheduleInfoComponent } from '@home/components/profile/alarm/alarm-schedule-info.component'; import { AlarmScheduleDialogComponent } from '@home/components/profile/alarm/alarm-schedule-dialog.component'; import { EditAlarmDetailsDialogComponent } from '@home/components/profile/alarm/edit-alarm-details-dialog.component'; import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alarm/alarm-rule-condition-dialog.component'; From 5436316d1e075252bbf2c8136c4f4c0c94c4e1d5 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 23 Nov 2020 13:02:10 +0200 Subject: [PATCH 405/463] Fixed updated button --- .../modules/home/pages/device/devices-table-config.resolver.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts index 1aefffa184..b04bf13d44 100644 --- a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts @@ -484,6 +484,7 @@ export class DevicesTableConfigResolver implements Resolve { if (isDefinedAndNotNull(deviceCredential)) { this.config.componentsData.deviceCredential = deviceCredential; + this.config.table.onEntityUpdated(device); } }); } From 70fdb9061888471fbea2c52df51a690040cf135e Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 23 Nov 2020 16:01:23 +0200 Subject: [PATCH 406/463] Remove api usage state for system sendEmail method --- .../thingsboard/server/service/mail/DefaultMailService.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 057eb098e4..a0ff937e27 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -160,10 +160,8 @@ public class DefaultMailService implements MailService { @Override public void sendEmail(TenantId tenantId, String email, String subject, String message) throws ThingsboardException { - if (apiUsageStateService.getApiUsageState(tenantId).isEmailSendEnabled()) { - sendMail(mailSender, mailFrom, email, subject, message); - apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1); - } + sendMail(mailSender, mailFrom, email, subject, message); + apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1); } @Override From b681abfe42c46c88afd3274a4ad4dcdadd8d8d44 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 23 Nov 2020 16:02:40 +0200 Subject: [PATCH 407/463] Remove api usage state for system sendEmail method --- .../org/thingsboard/server/service/mail/DefaultMailService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index a0ff937e27..37910bddd1 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -161,7 +161,6 @@ public class DefaultMailService implements MailService { @Override public void sendEmail(TenantId tenantId, String email, String subject, String message) throws ThingsboardException { sendMail(mailSender, mailFrom, email, subject, message); - apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1); } @Override From 4f0253a33a4ca37c673f3550b5d0d6903291b789 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 23 Nov 2020 16:24:07 +0200 Subject: [PATCH 408/463] Ack messages that we ignore --- .../transport/mqtt/MqttTransportHandler.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index a8bfc3d938..cdd5f1b3ca 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -232,7 +232,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement if (topicName.startsWith(MqttTopics.BASE_GATEWAY_API_TOPIC)) { if (gatewaySessionHandler != null) { - handleGatewayPublishMsg(topicName, msgId, mqttMsg); + handleGatewayPublishMsg(ctx, topicName, msgId, mqttMsg); transportService.reportActivity(deviceSessionCtx.getSessionInfo()); } } else { @@ -240,7 +240,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } - private void handleGatewayPublishMsg(String topicName, int msgId, MqttPublishMessage mqttMsg) { + private void handleGatewayPublishMsg(ChannelHandlerContext ctx, String topicName, int msgId, MqttPublishMessage mqttMsg) { try { switch (topicName) { case MqttTopics.GATEWAY_TELEMETRY_TOPIC: @@ -264,6 +264,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement case MqttTopics.GATEWAY_DISCONNECT_TOPIC: gatewaySessionHandler.onDeviceDisconnect(mqttMsg); break; + default: + ack(ctx, msgId); } } catch (RuntimeException | AdaptorException e) { log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); @@ -293,6 +295,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement transportService.process(deviceSessionCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(ctx, msgId, claimDeviceMsg)); } else { transportService.reportActivity(deviceSessionCtx.getSessionInfo()); + ack(ctx, msgId); } } catch (AdaptorException e) { log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); @@ -301,15 +304,18 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } + private void ack(ChannelHandlerContext ctx, int msgId) { + if (msgId > 0) { + ctx.writeAndFlush(createMqttPubAckMsg(msgId)); + } + } private TransportServiceCallback getPubAckCallback(final ChannelHandlerContext ctx, final int msgId, final T msg) { return new TransportServiceCallback() { @Override public void onSuccess(Void dummy) { log.trace("[{}] Published msg: {}", sessionId, msg); - if (msgId > 0) { - ctx.writeAndFlush(createMqttPubAckMsg(msgId)); - } + ack(ctx, msgId); } @Override @@ -334,9 +340,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @Override public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg provisionResponseMsg) { log.trace("[{}] Published msg: {}", sessionId, msg); - if (msgId > 0) { - ctx.writeAndFlush(createMqttPubAckMsg(msgId)); - } + ack(ctx, msgId); try { if (deviceSessionCtx.getProvisionPayloadType().equals(TransportPayloadType.JSON)) { deviceSessionCtx.getContext().getJsonMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); From d6c6a2e343c9e9116354afa47ae7fa744b1b4081 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 23 Nov 2020 18:13:25 +0200 Subject: [PATCH 409/463] Add missing columns of api_usage_state table to DB upgrade script. --- .../server/service/install/SqlDatabaseUpgradeService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 19727684a3..20c00dcb09 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -367,6 +367,8 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService " db_storage varchar(32)," + " re_exec varchar(32)," + " js_exec varchar(32)," + + " email_exec varchar(32)," + + " sms_exec varchar(32)," + " CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id)\n" + ");"); } catch (Exception e) { From 04bb1e5f4f396efb62985bb274d7577620d82d4c Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 23 Nov 2020 18:28:02 +0200 Subject: [PATCH 410/463] Add missing cases for email and sms api limits --- .../service/apiusage/TenantApiUsageState.java | 10 ++++++++++ .../server/service/mail/DefaultMailService.java | 14 ++++++++++++++ .../dao/usagerecord/ApiUsageStateServiceImpl.java | 4 ++++ 3 files changed, 28 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java index a43afcd2c5..fd4ef96122 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -125,6 +125,10 @@ public class TenantApiUsageState { return apiUsageState.getDbStorageState(); case JS: return apiUsageState.getJsExecState(); + case EMAIL: + return apiUsageState.getEmailExecState(); + case SMS: + return apiUsageState.getSmsExecState(); default: return ApiUsageStateValue.ENABLED; } @@ -145,6 +149,12 @@ public class TenantApiUsageState { case JS: apiUsageState.setJsExecState(value); break; + case EMAIL: + apiUsageState.setEmailExecState(value); + break; + case SMS: + apiUsageState.setSmsExecState(value); + break; } return !currentValue.equals(value); } diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 37910bddd1..7865321e67 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -304,6 +304,9 @@ public class DefaultMailService implements MailService { return "invoke"; case RE: return "process"; + case EMAIL: + case SMS: + return "send"; default: throw new RuntimeException("Not implemented!"); } @@ -319,6 +322,9 @@ public class DefaultMailService implements MailService { return "invoked"; case RE: return "processed"; + case EMAIL: + case SMS: + return "sent"; default: throw new RuntimeException("Not implemented!"); } @@ -337,6 +343,10 @@ public class DefaultMailService implements MailService { return valueInM + " out of " + thresholdInM + " allowed JavaScript functions"; case RE_EXEC_COUNT: return valueInM + " out of " + thresholdInM + " allowed Rule Engine messages"; + case EMAIL_EXEC_COUNT: + return valueInM + " out of " + thresholdInM + " allowed Email messages"; + case SMS_EXEC_COUNT: + return valueInM + " out of " + thresholdInM + " allowed SMS messages"; default: throw new RuntimeException("Not implemented!"); } @@ -353,6 +363,10 @@ public class DefaultMailService implements MailService { return "JavaScript functions " + getValueAsString(value) + " times"; case RE_EXEC_COUNT: return getValueAsString(value) + " Rule Engine messages"; + case EMAIL_EXEC_COUNT: + return getValueAsString(value) + " Email messages"; + case SMS_EXEC_COUNT: + return getValueAsString(value) + " SMS messages"; default: throw new RuntimeException("Not implemented!"); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index 5fc1c293af..5677b0d73b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -96,6 +96,10 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A new StringDataEntry(ApiFeature.RE.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), new StringDataEntry(ApiFeature.JS.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), + new StringDataEntry(ApiFeature.EMAIL.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), + new StringDataEntry(ApiFeature.SMS.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); tsService.save(tenantId, saved.getId(), apiUsageStates, 0L); List profileThresholds = new ArrayList<>(); From 241a45cce41ec5d4cc22c6af21250e8cb15b5baf Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 23 Nov 2020 19:22:28 +0200 Subject: [PATCH 411/463] Fix sort by originator type --- .../server/dao/sql/query/DefaultAlarmQueryRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java index e9a5372894..c1a419381a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java @@ -72,8 +72,8 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { alarmFieldColumnMap.put("status", ModelConstants.ALARM_STATUS_PROPERTY); alarmFieldColumnMap.put("type", ModelConstants.ALARM_TYPE_PROPERTY); alarmFieldColumnMap.put("severity", ModelConstants.ALARM_SEVERITY_PROPERTY); - alarmFieldColumnMap.put("originator_id", ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY); - alarmFieldColumnMap.put("originator_type", ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY); + alarmFieldColumnMap.put("originatorId", ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY); + alarmFieldColumnMap.put("originatorType", ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY); alarmFieldColumnMap.put("originator", "originator_name"); } From aa816d61ab93b06305b28dba421ff73a4ee8c61b Mon Sep 17 00:00:00 2001 From: Viacheslav Kukhtyn Date: Fri, 20 Nov 2020 18:27:06 +0200 Subject: [PATCH 412/463] Handle device update or delete event in device's session --- .../server/controller/DeviceController.java | 4 ++++ .../queue/DefaultTbClusterService.java | 12 ++++++++++- .../service/queue/TbClusterService.java | 5 +++++ .../common/msg/session/SessionContext.java | 3 +++ .../common/transport/SessionMsgListener.java | 3 +++ .../service/DefaultTransportService.java | 20 +++++++++++++++++++ .../session/DeviceAwareSessionContext.java | 7 ++++++- 7 files changed, 52 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 03ccc9fb93..da5d705ef8 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -118,6 +118,7 @@ public class DeviceController extends BaseController { Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken)); + tbClusterService.onDeviceChange(savedDevice, null); tbClusterService.pushMsgToCore(new DeviceNameOrTypeUpdateMsg(savedDevice.getTenantId(), savedDevice.getId(), savedDevice.getName(), savedDevice.getType()), null); tbClusterService.onEntityStateChange(savedDevice.getTenantId(), savedDevice.getId(), @@ -150,6 +151,9 @@ public class DeviceController extends BaseController { Device device = checkDeviceId(deviceId, Operation.DELETE); deviceService.deleteDevice(getCurrentUser().getTenantId(), deviceId); + tbClusterService.onDeviceDeleted(device, null); + tbClusterService.onEntityStateChange(device.getTenantId(), deviceId, ComponentLifecycleEvent.DELETED); + logEntityAction(deviceId, device, device.getCustomerId(), ActionType.DELETED, null, strDeviceId); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index edd53c6727..a8a29f09e0 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -22,6 +22,7 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasName; @@ -32,7 +33,6 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; @@ -237,6 +237,16 @@ public class DefaultTbClusterService implements TbClusterService { onEntityDelete(TenantId.SYS_TENANT_ID, entity.getId(), entity.getName(), callback); } + @Override + public void onDeviceChange(Device entity, TbQueueCallback callback) { + onEntityChange(entity.getTenantId(), entity.getId(), entity, callback); + } + + @Override + public void onDeviceDeleted(Device entity, TbQueueCallback callback) { + onEntityDelete(entity.getTenantId(), entity.getId(), entity.getName(), callback); + } + public void onEntityChange(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) { String entityName = (entity instanceof HasName) ? ((HasName) entity).getName() : entity.getClass().getName(); log.trace("[{}][{}][{}] Processing [{}] change event", tenantId, entityid.getEntityType(), entityid.getId(), entityName); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java index b36af16716..1547a5e3f0 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.queue; import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; @@ -66,4 +67,8 @@ public interface TbClusterService { void onTenantDelete(Tenant tenant, TbQueueCallback callback); void onApiStateChange(ApiUsageState apiUsageState, TbQueueCallback callback); + + void onDeviceChange(Device device, TbQueueCallback callback); + + void onDeviceDeleted(Device device, TbQueueCallback callback); } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java index d124fd48b0..a8500b8e09 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.msg.session; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import java.util.UUID; @@ -26,4 +27,6 @@ public interface SessionContext { int nextMsgId(); void onProfileUpdate(DeviceProfile deviceProfile); + + void onDeviceUpdate(Device device); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java index ccd63ca430..c7f1a254f7 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.transport; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; @@ -40,4 +41,6 @@ public interface SessionMsgListener { default void onProfileUpdate(DeviceProfile deviceProfile) { } + default void onDeviceUpdate(Device device) { + } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 1afcfb5ebf..5bef9ddeb5 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -27,6 +27,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; @@ -643,6 +644,9 @@ public class DefaultTransportService implements TransportService { rateLimitService.update(apiUsageState.getTenantId(), apiUsageState.isTransportEnabled()); //TODO: if transport is disabled, we should close all sessions and not to check credentials. } + } else if (EntityType.DEVICE.equals(entityType)) { + Optional deviceOpt = dataDecodingEncodingService.decode(msg.getData().toByteArray()); + deviceOpt.ifPresent(this::onDeviceUpdate); } } else if (toSessionMsg.hasEntityDeleteMsg()) { TransportProtos.EntityDeleteMsg msg = toSessionMsg.getEntityDeleteMsg(); @@ -675,6 +679,22 @@ public class DefaultTransportService implements TransportService { }); } + private void onDeviceUpdate(Device device) { + long deviceIdMSB = device.getId().getId().getMostSignificantBits(); + long deviceIdLSB = device.getId().getId().getLeastSignificantBits(); + sessions.forEach((id, md) -> { + if (md.getSessionInfo().getDeviceIdMSB() == deviceIdMSB + && md.getSessionInfo().getDeviceIdLSB() == deviceIdLSB) { + long deviceProfileIdMSB = device.getDeviceProfileId().getId().getMostSignificantBits(); + long deviceProfileIdLSB = device.getDeviceProfileId().getId().getLeastSignificantBits(); + if (md.getSessionInfo().getDeviceProfileIdMSB() != deviceProfileIdMSB + && md.getSessionInfo().getDeviceProfileIdLSB() != deviceProfileIdLSB) { + transportCallbackExecutor.submit(() -> md.getListener().onDeviceUpdate(device)); + } + } + }); + } + protected UUID toSessionId(TransportProtos.SessionInfoProto sessionInfo) { return new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java index 2f7f2d69e6..46c7ca67c7 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java @@ -18,12 +18,12 @@ package org.thingsboard.server.common.transport.session; import lombok.Data; import lombok.Getter; import lombok.Setter; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.msg.session.SessionContext; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; import java.util.UUID; @@ -65,6 +65,11 @@ public abstract class DeviceAwareSessionContext implements SessionContext { this.sessionInfo = TransportProtos.SessionInfoProto.newBuilder().mergeFrom(sessionInfo).setDeviceType(deviceProfile.getName()).build(); } + @Override + public void onDeviceUpdate(Device device) { + this.deviceInfo.setDeviceProfileId(device.getDeviceProfileId()); + } + public boolean isConnected() { return connected; } From 379ba80b2ca9513917249dd436bcb06d8011cea3 Mon Sep 17 00:00:00 2001 From: Viacheslav Kukhtyn Date: Fri, 20 Nov 2020 20:05:52 +0200 Subject: [PATCH 413/463] Get device profile id before the foreach --- .../service/DefaultTransportService.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 5bef9ddeb5..c9336862a2 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -682,15 +682,14 @@ public class DefaultTransportService implements TransportService { private void onDeviceUpdate(Device device) { long deviceIdMSB = device.getId().getId().getMostSignificantBits(); long deviceIdLSB = device.getId().getId().getLeastSignificantBits(); + long deviceProfileIdMSB = device.getDeviceProfileId().getId().getMostSignificantBits(); + long deviceProfileIdLSB = device.getDeviceProfileId().getId().getLeastSignificantBits(); sessions.forEach((id, md) -> { - if (md.getSessionInfo().getDeviceIdMSB() == deviceIdMSB - && md.getSessionInfo().getDeviceIdLSB() == deviceIdLSB) { - long deviceProfileIdMSB = device.getDeviceProfileId().getId().getMostSignificantBits(); - long deviceProfileIdLSB = device.getDeviceProfileId().getId().getLeastSignificantBits(); - if (md.getSessionInfo().getDeviceProfileIdMSB() != deviceProfileIdMSB - && md.getSessionInfo().getDeviceProfileIdLSB() != deviceProfileIdLSB) { - transportCallbackExecutor.submit(() -> md.getListener().onDeviceUpdate(device)); - } + if ((md.getSessionInfo().getDeviceIdMSB() == deviceIdMSB + && md.getSessionInfo().getDeviceIdLSB() == deviceIdLSB) + && (md.getSessionInfo().getDeviceProfileIdMSB() != deviceProfileIdMSB + && md.getSessionInfo().getDeviceProfileIdLSB() != deviceProfileIdLSB)) { + transportCallbackExecutor.submit(() -> md.getListener().onDeviceUpdate(device)); } }); } From c36fc46e2ce9fa18fb078eaa1e7565f8237f25d7 Mon Sep 17 00:00:00 2001 From: Viacheslav Kukhtyn Date: Sat, 21 Nov 2020 16:01:04 +0200 Subject: [PATCH 414/463] Override default implementation onDeviceUpdate() --- .../server/transport/mqtt/MqttTransportHandler.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index cdd5f1b3ca..eadfab729e 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -40,6 +40,7 @@ import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -703,4 +704,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement public void onProfileUpdate(DeviceProfile deviceProfile) { deviceSessionCtx.onProfileUpdate(deviceProfile); } + + @Override + public void onDeviceUpdate(Device device) { + deviceSessionCtx.onDeviceUpdate(device); + } } From 52d7d790f6f5c2b2e54af45dd99190157ef0a443 Mon Sep 17 00:00:00 2001 From: Viacheslav Kukhtyn Date: Sat, 21 Nov 2020 17:18:19 +0200 Subject: [PATCH 415/463] Update device and session infos in context --- .../server/common/msg/session/SessionContext.java | 2 +- .../server/transport/mqtt/MqttTransportHandler.java | 4 ++-- .../transport/mqtt/session/DeviceSessionCtx.java | 5 +++++ .../server/common/transport/SessionMsgListener.java | 2 +- .../transport/service/DefaultTransportService.java | 2 +- .../transport/session/DeviceAwareSessionContext.java | 10 ++++++++-- 6 files changed, 18 insertions(+), 7 deletions(-) diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java index a8500b8e09..47f2af1c84 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java @@ -28,5 +28,5 @@ public interface SessionContext { void onProfileUpdate(DeviceProfile deviceProfile); - void onDeviceUpdate(Device device); + void onDeviceProfileUpdate(Device device); } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index eadfab729e..335158af32 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -706,7 +706,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } @Override - public void onDeviceUpdate(Device device) { - deviceSessionCtx.onDeviceUpdate(device); + public void onDeviceProfileUpdate(Device device) { + deviceSessionCtx.onDeviceProfileUpdate(device); } } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index b45dc5add5..4451045899 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -20,6 +20,7 @@ import io.netty.channel.ChannelHandlerContext; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -113,6 +114,10 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { updateTopicFilters(deviceProfile); } + @Override + public void onDeviceProfileUpdate(Device device) { + super.onDeviceProfileUpdate(device); + } private void updateTopicFilters(DeviceProfile deviceProfile) { DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java index c7f1a254f7..28a9d6d727 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java @@ -41,6 +41,6 @@ public interface SessionMsgListener { default void onProfileUpdate(DeviceProfile deviceProfile) { } - default void onDeviceUpdate(Device device) { + default void onDeviceProfileUpdate(Device device) { } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index c9336862a2..68040b3d67 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -689,7 +689,7 @@ public class DefaultTransportService implements TransportService { && md.getSessionInfo().getDeviceIdLSB() == deviceIdLSB) && (md.getSessionInfo().getDeviceProfileIdMSB() != deviceProfileIdMSB && md.getSessionInfo().getDeviceProfileIdLSB() != deviceProfileIdLSB)) { - transportCallbackExecutor.submit(() -> md.getListener().onDeviceUpdate(device)); + transportCallbackExecutor.submit(() -> md.getListener().onDeviceProfileUpdate(device)); } }); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java index 46c7ca67c7..73b6694666 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.transport.session; import lombok.Data; import lombok.Getter; import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.id.DeviceId; @@ -65,9 +66,14 @@ public abstract class DeviceAwareSessionContext implements SessionContext { this.sessionInfo = TransportProtos.SessionInfoProto.newBuilder().mergeFrom(sessionInfo).setDeviceType(deviceProfile.getName()).build(); } - @Override - public void onDeviceUpdate(Device device) { + public void onDeviceProfileUpdate(Device device) { this.deviceInfo.setDeviceProfileId(device.getDeviceProfileId()); + this.deviceInfo.setDeviceType(device.getType()); + this.sessionInfo = TransportProtos.SessionInfoProto.newBuilder().mergeFrom(sessionInfo) + .setDeviceProfileIdMSB(device.getDeviceProfileId().getId().getMostSignificantBits()) + .setDeviceProfileIdLSB(device.getDeviceProfileId().getId().getLeastSignificantBits()) + .setDeviceType(device.getType()) + .build(); } public boolean isConnected() { From 7834b9221771a7cca346e5870d800f817604f27d Mon Sep 17 00:00:00 2001 From: Viacheslav Kukhtyn Date: Sat, 21 Nov 2020 17:19:56 +0200 Subject: [PATCH 416/463] Remove unused import --- .../common/transport/session/DeviceAwareSessionContext.java | 1 - 1 file changed, 1 deletion(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java index 73b6694666..db798e5b7f 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java @@ -18,7 +18,6 @@ package org.thingsboard.server.common.transport.session; import lombok.Data; import lombok.Getter; import lombok.Setter; -import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.id.DeviceId; From 63406b010f4c462fa0ef333cf76e3b5de9139fc2 Mon Sep 17 00:00:00 2001 From: Viacheslav Kukhtyn Date: Mon, 23 Nov 2020 13:08:45 +0200 Subject: [PATCH 417/463] Use the same device session info in transport service and in context --- .../transport/mqtt/MqttTransportHandler.java | 4 ++-- .../mqtt/session/DeviceSessionCtx.java | 6 ------ .../common/transport/SessionMsgListener.java | 3 ++- .../common/transport/TransportAdaptor.java | 2 +- .../service/DefaultTransportService.java | 20 ++++++++++++++++--- .../session/DeviceAwareSessionContext.java | 9 ++------- .../transport}/session/SessionContext.java | 5 +++-- 7 files changed, 27 insertions(+), 22 deletions(-) rename common/{message/src/main/java/org/thingsboard/server/common/msg => transport/transport-api/src/main/java/org/thingsboard/server/common/transport}/session/SessionContext.java (80%) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 335158af32..091c656462 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -706,7 +706,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } @Override - public void onDeviceProfileUpdate(Device device) { - deviceSessionCtx.onDeviceProfileUpdate(device); + public void onDeviceProfileUpdate(Device device, TransportProtos.SessionInfoProto sessionInfo) { + deviceSessionCtx.onDeviceProfileUpdate(device, sessionInfo); } } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index 4451045899..c2c2d258fc 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -20,7 +20,6 @@ import io.netty.channel.ChannelHandlerContext; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -114,11 +113,6 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { updateTopicFilters(deviceProfile); } - @Override - public void onDeviceProfileUpdate(Device device) { - super.onDeviceProfileUpdate(device); - } - private void updateTopicFilters(DeviceProfile deviceProfile) { DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); if (transportConfiguration.getType().equals(DeviceTransportType.MQTT) && diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java index 28a9d6d727..85c162930d 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java @@ -17,6 +17,7 @@ package org.thingsboard.server.common.transport; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; @@ -41,6 +42,6 @@ public interface SessionMsgListener { default void onProfileUpdate(DeviceProfile deviceProfile) { } - default void onDeviceProfileUpdate(Device device) { + default void onDeviceProfileUpdate(Device device, TransportProtos.SessionInfoProto sessionInfo) { } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java index de37856985..5ffc5abc7d 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.transport; -import org.thingsboard.server.common.msg.session.SessionContext; +import org.thingsboard.server.common.transport.session.SessionContext; public interface TransportAdaptor { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 68040b3d67..70a4817079 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -684,14 +684,28 @@ public class DefaultTransportService implements TransportService { long deviceIdLSB = device.getId().getId().getLeastSignificantBits(); long deviceProfileIdMSB = device.getDeviceProfileId().getId().getMostSignificantBits(); long deviceProfileIdLSB = device.getDeviceProfileId().getId().getLeastSignificantBits(); - sessions.forEach((id, md) -> { + for (Map.Entry entry : sessions.entrySet()) { + SessionMetaData md = entry.getValue(); if ((md.getSessionInfo().getDeviceIdMSB() == deviceIdMSB && md.getSessionInfo().getDeviceIdLSB() == deviceIdLSB) && (md.getSessionInfo().getDeviceProfileIdMSB() != deviceProfileIdMSB && md.getSessionInfo().getDeviceProfileIdLSB() != deviceProfileIdLSB)) { - transportCallbackExecutor.submit(() -> md.getListener().onDeviceProfileUpdate(device)); + updateSessionMetadata(device, entry, md); } - }); + } + } + + private void updateSessionMetadata(Device device, Map.Entry entry, SessionMetaData md) { + TransportProtos.SessionInfoProto newSessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .mergeFrom(md.getSessionInfo()) + .setDeviceProfileIdMSB(device.getDeviceProfileId().getId().getMostSignificantBits()) + .setDeviceProfileIdLSB(device.getDeviceProfileId().getId().getLeastSignificantBits()) + .setDeviceType(device.getType()) + .build(); + SessionMetaData newSessionMetaData = new SessionMetaData(newSessionInfo, md.getSessionType(), md.getListener()); + entry.setValue(newSessionMetaData); + transportCallbackExecutor.submit(() -> newSessionMetaData.getListener().onDeviceProfileUpdate(device, + newSessionMetaData.getSessionInfo())); } protected UUID toSessionId(TransportProtos.SessionInfoProto sessionInfo) { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java index db798e5b7f..5d33dccff6 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java @@ -21,7 +21,6 @@ import lombok.Setter; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.msg.session.SessionContext; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.gen.transport.TransportProtos; @@ -65,14 +64,10 @@ public abstract class DeviceAwareSessionContext implements SessionContext { this.sessionInfo = TransportProtos.SessionInfoProto.newBuilder().mergeFrom(sessionInfo).setDeviceType(deviceProfile.getName()).build(); } - public void onDeviceProfileUpdate(Device device) { + public void onDeviceProfileUpdate(Device device, TransportProtos.SessionInfoProto sessionInfo) { this.deviceInfo.setDeviceProfileId(device.getDeviceProfileId()); this.deviceInfo.setDeviceType(device.getType()); - this.sessionInfo = TransportProtos.SessionInfoProto.newBuilder().mergeFrom(sessionInfo) - .setDeviceProfileIdMSB(device.getDeviceProfileId().getId().getMostSignificantBits()) - .setDeviceProfileIdLSB(device.getDeviceProfileId().getId().getLeastSignificantBits()) - .setDeviceType(device.getType()) - .build(); + this.sessionInfo = sessionInfo; } public boolean isConnected() { diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/SessionContext.java similarity index 80% rename from common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/SessionContext.java index 47f2af1c84..9b5870cace 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/SessionContext.java @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.msg.session; +package org.thingsboard.server.common.transport.session; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.gen.transport.TransportProtos; import java.util.UUID; @@ -28,5 +29,5 @@ public interface SessionContext { void onProfileUpdate(DeviceProfile deviceProfile); - void onDeviceProfileUpdate(Device device); + void onDeviceProfileUpdate(Device device, TransportProtos.SessionInfoProto sessionInfo); } From 97ef261ad2609f6364d014683a4075b1d8dd336b Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 24 Nov 2020 10:57:59 +0200 Subject: [PATCH 418/463] UI: fixed show alarm fields in data keys widget with unresolved alias --- .../components/widget/widget-config.component.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts index ce37c46326..ba8cdaf5d2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts @@ -813,7 +813,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont return dataKeys; } ), - catchError(val => of([])) + catchError(() => of([])) )); } return forkJoin(fetchEntityTasks).pipe( @@ -825,11 +825,23 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont return result; } )); + } else if (dataKeyTypes.includes(DataKeyType.alarm)) { + return this.entityService.getEntityKeys(null, query, DataKeyType.alarm).pipe( + map((keys) => { + const dataKeys: Array = []; + for (const key of keys) { + dataKeys.push({name: key, type: DataKeyType.alarm}); + } + return dataKeys; + } + ), + catchError(() => of([])) + ); } else { return of([]); } }), - catchError(val => of([] as Array)) + catchError(() => of([] as Array)) ); } From 529608e60fffd83484685cc18b1e641701d89b99 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 24 Nov 2020 12:11:29 +0200 Subject: [PATCH 419/463] Improved transport cache invalidation for device updates --- .../transport/mqtt/MqttTransportHandler.java | 9 ++-- .../mqtt/session/DeviceSessionCtx.java | 5 +- .../common/transport/SessionMsgListener.java | 6 ++- .../service/DefaultTransportService.java | 51 +++++++++++-------- .../transport/service/SessionMetaData.java | 19 ++++--- .../session/DeviceAwareSessionContext.java | 12 +++-- .../transport/session/SessionContext.java | 5 +- 7 files changed, 61 insertions(+), 46 deletions(-) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 091c656462..cabc61cab6 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -72,6 +72,7 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -701,12 +702,12 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } @Override - public void onProfileUpdate(DeviceProfile deviceProfile) { - deviceSessionCtx.onProfileUpdate(deviceProfile); + public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) { + deviceSessionCtx.onDeviceProfileUpdate(sessionInfo, deviceProfile); } @Override - public void onDeviceProfileUpdate(Device device, TransportProtos.SessionInfoProto sessionInfo) { - deviceSessionCtx.onDeviceProfileUpdate(device, sessionInfo); + public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt) { + deviceSessionCtx.onDeviceUpdate(sessionInfo, device, deviceProfileOpt); } } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index c2c2d258fc..487aa8a207 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportC import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.mqtt.MqttTransportContext; import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.util.MqttTopicFilter; @@ -108,8 +109,8 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { } @Override - public void onProfileUpdate(DeviceProfile deviceProfile) { - super.onProfileUpdate(deviceProfile); + public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) { + super.onDeviceProfileUpdate(sessionInfo, deviceProfile); updateTopicFilters(deviceProfile); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java index 85c162930d..9eb0fbcd6c 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java @@ -24,6 +24,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponse import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto; import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; +import java.util.Optional; + /** * Created by ashvayka on 04.10.18. */ @@ -39,9 +41,9 @@ public interface SessionMsgListener { void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse); - default void onProfileUpdate(DeviceProfile deviceProfile) { + default void onDeviceProfileUpdate(TransportProtos.SessionInfoProto newSessionInfo, DeviceProfile deviceProfile) { } - default void onDeviceProfileUpdate(Device device, TransportProtos.SessionInfoProto sessionInfo) { + default void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt) { } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 70a4817079..3e619f8d6a 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -672,9 +672,17 @@ public class DefaultTransportService implements TransportService { long deviceProfileIdMSB = deviceProfile.getId().getId().getMostSignificantBits(); long deviceProfileIdLSB = deviceProfile.getId().getId().getLeastSignificantBits(); sessions.forEach((id, md) -> { + //TODO: if transport types are different - we should close the session. if (md.getSessionInfo().getDeviceProfileIdMSB() == deviceProfileIdMSB && md.getSessionInfo().getDeviceProfileIdLSB() == deviceProfileIdLSB) { - transportCallbackExecutor.submit(() -> md.getListener().onProfileUpdate(deviceProfile)); + TransportProtos.SessionInfoProto newSessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .mergeFrom(md.getSessionInfo()) + .setDeviceProfileIdMSB(deviceProfileIdMSB) + .setDeviceProfileIdLSB(deviceProfileIdLSB) + .setDeviceType(deviceProfile.getName()) + .build(); + md.setSessionInfo(newSessionInfo); + transportCallbackExecutor.submit(() -> md.getListener().onDeviceProfileUpdate(newSessionInfo, deviceProfile)); } }); } @@ -684,28 +692,27 @@ public class DefaultTransportService implements TransportService { long deviceIdLSB = device.getId().getId().getLeastSignificantBits(); long deviceProfileIdMSB = device.getDeviceProfileId().getId().getMostSignificantBits(); long deviceProfileIdLSB = device.getDeviceProfileId().getId().getLeastSignificantBits(); - for (Map.Entry entry : sessions.entrySet()) { - SessionMetaData md = entry.getValue(); - if ((md.getSessionInfo().getDeviceIdMSB() == deviceIdMSB - && md.getSessionInfo().getDeviceIdLSB() == deviceIdLSB) - && (md.getSessionInfo().getDeviceProfileIdMSB() != deviceProfileIdMSB - && md.getSessionInfo().getDeviceProfileIdLSB() != deviceProfileIdLSB)) { - updateSessionMetadata(device, entry, md); + sessions.forEach((id, md) -> { + if ((md.getSessionInfo().getDeviceIdMSB() == deviceIdMSB && md.getSessionInfo().getDeviceIdLSB() == deviceIdLSB)) { + DeviceProfile newDeviceProfile; + if (md.getSessionInfo().getDeviceProfileIdMSB() != deviceProfileIdMSB + && md.getSessionInfo().getDeviceProfileIdLSB() != deviceProfileIdLSB) { + //TODO: if transport types are different - we should close the session. + newDeviceProfile = deviceProfileCache.get(new DeviceProfileId(new UUID(deviceProfileIdMSB, deviceProfileIdLSB))); + } else { + newDeviceProfile = null; + } + TransportProtos.SessionInfoProto newSessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .mergeFrom(md.getSessionInfo()) + .setDeviceProfileIdMSB(deviceProfileIdMSB) + .setDeviceProfileIdLSB(deviceProfileIdLSB) + .setDeviceName(device.getName()) + .setDeviceType(device.getType()) + .build(); + md.setSessionInfo(newSessionInfo); + transportCallbackExecutor.submit(() -> md.getListener().onDeviceUpdate(newSessionInfo, device, Optional.ofNullable(newDeviceProfile))); } - } - } - - private void updateSessionMetadata(Device device, Map.Entry entry, SessionMetaData md) { - TransportProtos.SessionInfoProto newSessionInfo = TransportProtos.SessionInfoProto.newBuilder() - .mergeFrom(md.getSessionInfo()) - .setDeviceProfileIdMSB(device.getDeviceProfileId().getId().getMostSignificantBits()) - .setDeviceProfileIdLSB(device.getDeviceProfileId().getId().getLeastSignificantBits()) - .setDeviceType(device.getType()) - .build(); - SessionMetaData newSessionMetaData = new SessionMetaData(newSessionInfo, md.getSessionType(), md.getListener()); - entry.setValue(newSessionMetaData); - transportCallbackExecutor.submit(() -> newSessionMetaData.getListener().onDeviceProfileUpdate(device, - newSessionMetaData.getSessionInfo())); + }); } protected UUID toSessionId(TransportProtos.SessionInfoProto sessionInfo) { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java index 5181d8e781..ac293307a9 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java @@ -27,22 +27,17 @@ import java.util.concurrent.ScheduledFuture; @Data class SessionMetaData { - private final TransportProtos.SessionInfoProto sessionInfo; + private volatile TransportProtos.SessionInfoProto sessionInfo; private final TransportProtos.SessionType sessionType; private final SessionMsgListener listener; - private ScheduledFuture scheduledFuture; - + private volatile ScheduledFuture scheduledFuture; private volatile long lastActivityTime; private volatile long lastReportedActivityTime; private volatile boolean subscribedToAttributes; private volatile boolean subscribedToRPC; - SessionMetaData( - TransportProtos.SessionInfoProto sessionInfo, - TransportProtos.SessionType sessionType, - SessionMsgListener listener - ) { + SessionMetaData(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SessionType sessionType, SessionMsgListener listener) { this.sessionInfo = sessionInfo; this.sessionType = sessionType; this.listener = listener; @@ -54,11 +49,15 @@ class SessionMetaData { this.lastActivityTime = System.currentTimeMillis(); } - void setScheduledFuture(ScheduledFuture scheduledFuture) { this.scheduledFuture = scheduledFuture; } + void setScheduledFuture(ScheduledFuture scheduledFuture) { + this.scheduledFuture = scheduledFuture; + } public ScheduledFuture getScheduledFuture() { return scheduledFuture; } - public boolean hasScheduledFuture() { return null != this.scheduledFuture; } + public boolean hasScheduledFuture() { + return null != this.scheduledFuture; + } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java index 5d33dccff6..168a20ba67 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.gen.transport.TransportProtos; +import java.util.Optional; import java.util.UUID; /** @@ -58,16 +59,19 @@ public abstract class DeviceAwareSessionContext implements SessionContext { } @Override - public void onProfileUpdate(DeviceProfile deviceProfile) { + public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) { + this.sessionInfo = sessionInfo; this.deviceProfile = deviceProfile; this.deviceInfo.setDeviceType(deviceProfile.getName()); - this.sessionInfo = TransportProtos.SessionInfoProto.newBuilder().mergeFrom(sessionInfo).setDeviceType(deviceProfile.getName()).build(); + } - public void onDeviceProfileUpdate(Device device, TransportProtos.SessionInfoProto sessionInfo) { + @Override + public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt) { + this.sessionInfo = sessionInfo; this.deviceInfo.setDeviceProfileId(device.getDeviceProfileId()); this.deviceInfo.setDeviceType(device.getType()); - this.sessionInfo = sessionInfo; + deviceProfileOpt.ifPresent(profile -> this.deviceProfile = profile); } public boolean isConnected() { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/SessionContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/SessionContext.java index 9b5870cace..fc82974764 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/SessionContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/SessionContext.java @@ -19,6 +19,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.gen.transport.TransportProtos; +import java.util.Optional; import java.util.UUID; public interface SessionContext { @@ -27,7 +28,7 @@ public interface SessionContext { int nextMsgId(); - void onProfileUpdate(DeviceProfile deviceProfile); + void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile); - void onDeviceProfileUpdate(Device device, TransportProtos.SessionInfoProto sessionInfo); + void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt); } From f2a045fd5037bfd87e9107a316915ce7f4399e41 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 24 Nov 2020 16:04:28 +0200 Subject: [PATCH 420/463] UI: Typo delete invalid alarm-type hint --- .../profile/alarm/device-profile-alarm.component.html | 2 -- ui-ngx/src/assets/locale/locale.constant-en_US.json | 1 - 2 files changed, 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html index 3468442b50..a803f4ad53 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html @@ -44,8 +44,6 @@ {{ 'device-profile.alarm-type-unique' | translate }} -
diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index a8ed24f03d..89950a11e0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -943,7 +943,6 @@ "alarm-type": "Alarm type", "alarm-type-required": "Alarm type is required.", "alarm-type-unique": "Alarm type must be unique within the device profile alarm rules.", - "alarm-type-pattern-hint": "Alarm type pattern, use ${metaKeyName} to substitute variables from metadata", "create-alarm-pattern": "Create {{alarmType}} alarm", "create-alarm-rules": "Create alarm rules", "no-create-alarm-rules": "No create conditions configured", From 0a0ba1ab544e8e63d49d88475e7fe7548a76984f Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 24 Nov 2020 16:29:56 +0200 Subject: [PATCH 421/463] Remove console warn --- .../home/components/device/copy-device-credentials.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.ts b/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.ts index 99b4a82960..9477436cd0 100644 --- a/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.ts +++ b/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.ts @@ -78,7 +78,6 @@ export class CopyDeviceCredentialsComponent implements OnDestroy { filter(credential => isDefinedAndNotNull(credential)), distinctUntilChanged((prev, curr) => isEqual(prev, curr)) ).subscribe(deviceCredentials => { - console.warn(deviceCredentials); this.processingValue(deviceCredentials); }); } From 8cc07f05d4227326e33dac99673ca1d091dfb7bd Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 24 Nov 2020 16:48:45 +0200 Subject: [PATCH 422/463] updated mail templates --- .../main/resources/i18n/messages.properties | 2 +- .../resources/templates/state.disabled.ftl | 48 ++++++------------ .../resources/templates/state.enabled.ftl | 43 ++++++---------- .../resources/templates/state.warning.ftl | 49 ++++++------------- 4 files changed, 48 insertions(+), 94 deletions(-) diff --git a/application/src/main/resources/i18n/messages.properties b/application/src/main/resources/i18n/messages.properties index 9de9c4b789..bdbd64384b 100644 --- a/application/src/main/resources/i18n/messages.properties +++ b/application/src/main/resources/i18n/messages.properties @@ -4,4 +4,4 @@ account.activated.subject=Thingsboard - your account has been activated reset.password.subject=Thingsboard - Password reset has been requested password.was.reset.subject=Thingsboard - your account password has been reset account.lockout.subject=Thingsboard - User account has been lockout -api.usage.state=Thingsboard - Api Usage State for tenant has been updated \ No newline at end of file +api.usage.state=Thingsboard - Account limits \ No newline at end of file diff --git a/application/src/main/resources/templates/state.disabled.ftl b/application/src/main/resources/templates/state.disabled.ftl index f14bdd2235..5218ed8872 100644 --- a/application/src/main/resources/templates/state.disabled.ftl +++ b/application/src/main/resources/templates/state.disabled.ftl @@ -103,40 +103,30 @@ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;" bgcolor="#f6f6f6"> -
-
- - -
- - - - - - - - <#list apiUsageStateMailMessages as msg> - - - - - - - - - -
-

Thingsboard Api Usage State for tenant has been updated

-
- Thingsboard Usage state ${apiFeature} was updated to status WARNING. -
- ${msg.key.apiLimitKey} = ${msg.threshold} -
- ${msg.key.apiCountKey} = ${msg.value} -
- — The Thingsboard -
- -
+ + + + + + + + +
+ + + + + + + + + + + + + + + +
+

Your ThingsBoard account feature may be disabled

+
+ Your ${apiFeature} limit (${apiLimitValueLabel}) is almost exhausted.
ThingsBoard has already ${apiValueLabel}.
${apiFeature} will be disabled for your account when limit will be reached. +
Please contact your system administrator to resolve the issue. +
— The ThingsBoard +
+
+ + + + - +
This email was sent to ${targetEmail} by ThingsBoard.
From 031f579bd6cd4986dcd9bddd42771b2368842dd7 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 20 Nov 2020 15:44:42 +0200 Subject: [PATCH 395/463] Email notifications for API state --- .../service/mail/DefaultMailService.java | 49 ++++++++++++++++--- .../resources/templates/state.disabled.ftl | 3 +- .../resources/templates/state.enabled.ftl | 2 +- .../resources/templates/state.warning.ftl | 4 +- .../server/common/data/ApiFeature.java | 2 +- 5 files changed, 46 insertions(+), 14 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 3045fe0cce..a26363c5ff 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -20,6 +20,7 @@ import freemarker.template.Configuration; import freemarker.template.Template; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.core.NestedRuntimeException; @@ -55,6 +56,8 @@ public class DefaultMailService implements MailService { public static final String MAIL_PROP = "mail."; public static final String TARGET_EMAIL = "targetEmail"; public static final String UTF_8 = "UTF-8"; + public static final int _10K = 10000; + public static final int _1M = 1000000; @Autowired private MessageSource messages; @@ -266,8 +269,7 @@ public class DefaultMailService implements MailService { message = mergeTemplateIntoString("state.enabled.ftl", model); break; case WARNING: - model.put("apiLimitValueLabel", toDisabledValueLabel(msg.getKey(), msg.getThreshold())); - model.put("apiValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(msg.getKey(), msg.getValue())); + model.put("apiValueLabel", toDisabledValueLabel(apiFeature) + " " + toWarningValueLabel(msg.getKey(), msg.getValue(), msg.getThreshold())); message = mergeTemplateIntoString("state.warning.ftl", model); break; case DISABLED: @@ -285,8 +287,9 @@ public class DefaultMailService implements MailService { case TRANSPORT: return "receive"; case JS: - case RE: return "invoke"; + case RE: + return "process"; default: throw new RuntimeException("Not implemented!"); } @@ -299,8 +302,27 @@ public class DefaultMailService implements MailService { case TRANSPORT: return "received"; case JS: - case RE: return "invoked"; + case RE: + return "processed"; + default: + throw new RuntimeException("Not implemented!"); + } + } + + private String toWarningValueLabel(ApiUsageRecordKey key, long value, long threshold) { + String valueInM = getValueAsString(value); + String thresholdInM = getValueAsString(threshold); + switch (key) { + case STORAGE_DP_COUNT: + case TRANSPORT_DP_COUNT: + return valueInM + " out of " + thresholdInM + " allowed data points"; + case TRANSPORT_MSG_COUNT: + return valueInM + " out of " + thresholdInM + " allowed messages"; + case JS_EXEC_COUNT: + return valueInM + " out of " + thresholdInM + " allowed JavaScript functions"; + case RE_EXEC_COUNT: + return valueInM + " out of " + thresholdInM + " allowed Rule Engine messages"; default: throw new RuntimeException("Not implemented!"); } @@ -310,18 +332,29 @@ public class DefaultMailService implements MailService { switch (key) { case STORAGE_DP_COUNT: case TRANSPORT_DP_COUNT: - return (value / 1000000) + "M data points"; + return getValueAsString(value) + " data points"; case TRANSPORT_MSG_COUNT: - return (value / 1000000) + "M messages"; + return getValueAsString(value) + " messages"; case JS_EXEC_COUNT: - return (value / 1000000) + "M JavaScript functions"; + return "JavaScript functions " + getValueAsString(value) + " times"; case RE_EXEC_COUNT: - return (value / 1000000) + "M Rule Engine nodes"; + return getValueAsString(value) + " Rule Engine messages"; default: throw new RuntimeException("Not implemented!"); } } + @NotNull + private String getValueAsString(long value) { + if (value > _1M && value % _1M < _10K) { + return value / _1M + "M"; + } else if (value > _10K) { + return String.format("%.2fM", ((double) value) / 1000000); + } else { + return value + ""; + } + } + private void sendMail(JavaMailSenderImpl mailSender, String mailFrom, String email, String subject, String message) throws ThingsboardException { diff --git a/application/src/main/resources/templates/state.disabled.ftl b/application/src/main/resources/templates/state.disabled.ftl index 33a368cceb..f14bdd2235 100644 --- a/application/src/main/resources/templates/state.disabled.ftl +++ b/application/src/main/resources/templates/state.disabled.ftl @@ -123,8 +123,7 @@
We have disabled the ${apiFeature} for your account because ThingsBoard has already  ${apiLimitValueLabel} - . + valign="top">We have disabled the ${apiFeature} for your account because ThingsBoard has already ${apiLimitValueLabel}.
We have enabled the ${apiFeature} for your account and ThingsBoard already able to ${apiLabel} messages. + valign="top">We have enabled the ${apiFeature} for your account and ThingsBoard is already able to ${apiLabel} messages.
-

Your ThingsBoard account feature may be disabled

+

Your ThingsBoard account feature may be disabled soon

- Your ${apiFeature} limit (${apiLimitValueLabel}) is almost exhausted.
ThingsBoard has already ${apiValueLabel}.
${apiFeature} will be disabled for your account when limit will be reached. + ThingsBoard has already ${apiValueLabel}.
${apiFeature} will be disabled for your account once the limit will be reached.
+
-
- +
+ - + - - + + + + - + - +
-

Your ThingsBoard account feature was disabled

-
We have disabled the ${apiFeature} for your account because ThingsBoard has already ${apiLimitValueLabel}. +
Your ThingsBoard account feature was disabled
+
+
We have disabled the ${apiFeature} for your account because ThingsBoard has already ${apiLimitValueLabel}.
+
Please contact your system administrator to resolve the issue. - Please contact your system administrator to resolve the issue.
— The ThingsBoard - — The ThingsBoard
@@ -144,16 +134,10 @@
- +
- +
This email was sent to ${targetEmail} by ThingsBoard. - This email was sent to ${targetEmail} by ThingsBoard.
diff --git a/application/src/main/resources/templates/state.enabled.ftl b/application/src/main/resources/templates/state.enabled.ftl index 0a7841f871..d57bff5a7e 100644 --- a/application/src/main/resources/templates/state.enabled.ftl +++ b/application/src/main/resources/templates/state.enabled.ftl @@ -103,34 +103,27 @@ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;" bgcolor="#f6f6f6"> - +
-
- +
+ - + - - + + + + - +
-

Your ThingsBoard account feature was enabled

-
We have enabled the ${apiFeature} for your account and ThingsBoard is already able to ${apiLabel} messages. +
Your ThingsBoard account feature was enabled
+
+
We have enabled the ${apiFeature} for your account and ThingsBoard is already able to ${apiLabel} messages.
+
— The ThingsBoard - — The ThingsBoard
@@ -138,16 +131,10 @@
- +
- +
This email was sent to ${targetEmail} by ThingsBoard. - This email was sent to ${targetEmail} by ThingsBoard.
diff --git a/application/src/main/resources/templates/state.warning.ftl b/application/src/main/resources/templates/state.warning.ftl index 1ce29100a7..ffd300d614 100644 --- a/application/src/main/resources/templates/state.warning.ftl +++ b/application/src/main/resources/templates/state.warning.ftl @@ -103,41 +103,30 @@ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;" bgcolor="#f6f6f6"> - +
-
- +
+ - + - - + + + + - + - +
-

Your ThingsBoard account feature may be disabled soon

-
- ThingsBoard has already ${apiValueLabel}.
${apiFeature} will be disabled for your account once the limit will be reached. +
Warning: your ThingsBoard account feature may be disabled soon
+
+
ThingsBoard has already ${apiValueLabel}.
${apiFeature} will be disabled for your account once the limit will be reached.
+
Please contact your system administrator to resolve the issue. - Please contact your system administrator to resolve the issue.
— The ThingsBoard - — The ThingsBoard
@@ -145,16 +134,10 @@
- +
- +
This email was sent to ${targetEmail} by ThingsBoard. - This email was sent to ${targetEmail} by ThingsBoard.
From 9ac872221202e9b6c3ed977edd4b161fcdeb245a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 24 Nov 2020 17:11:19 +0200 Subject: [PATCH 423/463] Fix copy device credentials --- .../copy-device-credentials.component.ts | 27 +++++++++++-------- .../home/pages/device/device.component.html | 2 +- .../home/pages/device/device.component.ts | 7 ++--- .../device/devices-table-config.resolver.ts | 11 ++++---- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.ts b/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.ts index 9477436cd0..1297802fc3 100644 --- a/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.ts +++ b/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.ts @@ -14,12 +14,12 @@ /// limitations under the License. /// -import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; import { EntityId } from '@shared/models/id/entity-id'; import { DeviceService } from '@core/http/device.service'; import { DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models'; import { isDefinedAndNotNull, isEqual } from '@core/utils'; -import { BehaviorSubject, Subject } from 'rxjs'; +import { BehaviorSubject, Subject, Subscription } from 'rxjs'; import { distinctUntilChanged, filter, mergeMap, tap } from 'rxjs/operators'; import { EntityType } from '@shared/models/entity-type.models'; import { ActionNotificationShow } from '@core/notification/notification.actions'; @@ -32,12 +32,10 @@ import { TranslateService } from '@ngx-translate/core'; templateUrl: './copy-device-credentials.component.html', styleUrls: [] }) -export class CopyDeviceCredentialsComponent implements OnDestroy { +export class CopyDeviceCredentialsComponent implements OnInit, OnDestroy { private deviceId$ = new BehaviorSubject(null); - private credentials$ = new Subject(); - private tooltipMessage: string; public hideButton = true; @@ -56,13 +54,14 @@ export class CopyDeviceCredentialsComponent implements OnDestroy { @Input() disabled: boolean; @Input() - set credentials(credential: DeviceCredentials) { - this.credentials$.next(credential); - } + credentials$: Subject; + + private credentialsSubscription: Subscription = null; constructor(private store: Store, private translate: TranslateService, - private deviceService: DeviceService + private deviceService: DeviceService, + private cd: ChangeDetectorRef ) { this.deviceId$.pipe( filter(device => isDefinedAndNotNull(device) && device.entityType === EntityType.DEVICE), @@ -73,18 +72,23 @@ export class CopyDeviceCredentialsComponent implements OnDestroy { this.processingValue(deviceCredentials); this.loading = false; }); + } - this.credentials$.pipe( + ngOnInit(): void { + this.credentialsSubscription = this.credentials$.pipe( filter(credential => isDefinedAndNotNull(credential)), distinctUntilChanged((prev, curr) => isEqual(prev, curr)) ).subscribe(deviceCredentials => { this.processingValue(deviceCredentials); + this.cd.detectChanges(); }); } ngOnDestroy(): void { this.deviceId$.unsubscribe(); - this.credentials$.unsubscribe(); + if (this.credentialsSubscription !== null) { + this.credentialsSubscription.unsubscribe(); + } } private processingValue(credential: DeviceCredentials): void { @@ -124,4 +128,5 @@ export class CopyDeviceCredentialsComponent implements OnDestroy { horizontalPosition: 'right' })); } + } diff --git a/ui-ngx/src/app/modules/home/pages/device/device.component.html b/ui-ngx/src/app/modules/home/pages/device/device.component.html index d781aa1373..d3bf07e204 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.html @@ -59,7 +59,7 @@ diff --git a/ui-ngx/src/app/modules/home/pages/device/device.component.ts b/ui-ngx/src/app/modules/home/pages/device/device.component.ts index 74503bfddb..f092d71fc1 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.ts @@ -21,7 +21,7 @@ import { EntityComponent } from '../../components/entity/entity.component'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { createDeviceConfiguration, - createDeviceTransportConfiguration, + createDeviceTransportConfiguration, DeviceCredentials, DeviceData, DeviceInfo, DeviceProfileInfo, @@ -33,6 +33,7 @@ import { NULL_UUID } from '@shared/models/id/has-uuid'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; +import { Subject } from 'rxjs'; @Component({ selector: 'tb-device', @@ -43,7 +44,7 @@ export class DeviceComponent extends EntityComponent { entityType = EntityType; - componentsData: any; + deviceCredentials$: Subject; deviceScope: 'tenant' | 'customer' | 'customer_user'; @@ -57,7 +58,7 @@ export class DeviceComponent extends EntityComponent { ngOnInit() { this.deviceScope = this.entitiesTableConfig.componentsData.deviceScope; - this.componentsData = this.entitiesTableConfigValue.componentsData; + this.deviceCredentials$ = this.entitiesTableConfigValue.componentsData.deviceCredentials$; super.ngOnInit(); } diff --git a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts index b04bf13d44..c2671459a7 100644 --- a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts @@ -32,7 +32,7 @@ import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared import { AddEntityDialogData, EntityAction } from '@home/models/entity/entity-component.models'; import { Device, DeviceCredentials, DeviceInfo } from '@app/shared/models/device.models'; import { DeviceComponent } from '@modules/home/pages/device/device.component'; -import { forkJoin, Observable, of } from 'rxjs'; +import { forkJoin, Observable, of, Subject } from 'rxjs'; import { select, Store } from '@ngrx/store'; import { selectAuthUser } from '@core/auth/auth.selectors'; import { map, mergeMap, take, tap } from 'rxjs/operators'; @@ -117,7 +117,7 @@ export class DevicesTableConfigResolver implements Resolve() }; this.customerId = routeParams.customerId; return this.store.pipe(select(selectAuthUser), take(1)).pipe( @@ -481,10 +481,9 @@ export class DevicesTableConfigResolver implements Resolve { - if (isDefinedAndNotNull(deviceCredential)) { - this.config.componentsData.deviceCredential = deviceCredential; - this.config.table.onEntityUpdated(device); + }).afterClosed().subscribe(deviceCredentials => { + if (isDefinedAndNotNull(deviceCredentials)) { + this.config.componentsData.deviceCredentials$.next(deviceCredentials); } }); } From 7298b001d2d329d934534f6f8e52a64b4174519e Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 24 Nov 2020 18:21:01 +0200 Subject: [PATCH 424/463] Update mail templates --- application/src/main/resources/templates/state.disabled.ftl | 2 +- application/src/main/resources/templates/state.enabled.ftl | 2 +- application/src/main/resources/templates/state.warning.ftl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/application/src/main/resources/templates/state.disabled.ftl b/application/src/main/resources/templates/state.disabled.ftl index 5218ed8872..2e341dbe20 100644 --- a/application/src/main/resources/templates/state.disabled.ftl +++ b/application/src/main/resources/templates/state.disabled.ftl @@ -107,7 +107,7 @@
- +
diff --git a/application/src/main/resources/templates/state.enabled.ftl b/application/src/main/resources/templates/state.enabled.ftl index d57bff5a7e..e03f38bba3 100644 --- a/application/src/main/resources/templates/state.enabled.ftl +++ b/application/src/main/resources/templates/state.enabled.ftl @@ -107,7 +107,7 @@
- +
diff --git a/application/src/main/resources/templates/state.warning.ftl b/application/src/main/resources/templates/state.warning.ftl index ffd300d614..67fc557e87 100644 --- a/application/src/main/resources/templates/state.warning.ftl +++ b/application/src/main/resources/templates/state.warning.ftl @@ -107,7 +107,7 @@
- +
From 2db54fdb9486ebc510252595f3427454fa429263 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 24 Nov 2020 18:53:26 +0200 Subject: [PATCH 425/463] UI: Fixed multiple attributes data-key settings --- .../src/main/data/json/system/widget_bundles/input_widgets.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/data/json/system/widget_bundles/input_widgets.json b/application/src/main/data/json/system/widget_bundles/input_widgets.json index 097c31dac4..4ed3c2fab6 100644 --- a/application/src/main/data/json/system/widget_bundles/input_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/input_widgets.json @@ -33,7 +33,7 @@ "templateCss": ".tb-toast {\n min-width: 0;\n font-size: 14px !important;\n}", "controllerScript": "self.onInit = function() {\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n self.ctx.$scope.multipleInputWidget.onDataUpdated();\r\n}\r\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"MultipleInput\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showActionButtons\":{\n \"title\":\"Show action buttons\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"updateAllValues\": {\n \"title\":\"Update all values, not only modified\",\n \"type\":\"boolean\",\n \"default\": false\n },\n \"saveButtonLabel\": {\n \"title\": \"'SAVE' button label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"resetButtonLabel\": {\n \"title\": \"'UNDO' button label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"showGroupTitle\": {\n \"title\":\"Show title for group of fields, related to different entities\",\n \"type\":\"boolean\",\n \"default\": false\n },\n \"groupTitle\": {\n \"title\": \"Group title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"fieldsAlignment\": {\n \"title\": \"Fields alignment\",\n \"type\": \"string\",\n \"default\": \"row\"\n },\n \"fieldsInRow\": {\n \"title\": \"Number of fields in the row\",\n \"type\": \"number\",\n \"default\": \"2\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showActionButtons\",\n {\n \"key\": \"updateAllValues\",\n \"condition\": \"model.showActionButtons === true\"\n },\n {\n \"key\": \"saveButtonLabel\",\n \"condition\": \"model.showActionButtons === true\"\n },\n {\n \"key\": \"resetButtonLabel\",\n \"condition\": \"model.showActionButtons === true\"\n },\n \"showResultMessage\",\n \"showGroupTitle\",\n \"groupTitle\",\n {\n \"key\": \"fieldsAlignment\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"row\",\n \"label\": \"Row (default)\"\n },\n {\n \"value\": \"column\",\n \"label\": \"Column\"\n }\n ]\n },\n {\n \"key\": \"fieldsInRow\",\n \"condition\": \"model.fieldsAlignment === 'row'\"\n }\n ]\n}", - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"dataKeyType\": {\n \"title\": \"Datakey type\",\n \"type\": \"string\",\n \"default\": \"server\"\n },\n \"dataKeyValueType\": {\n \"title\": \"Datakey value type\",\n \"type\": \"string\",\n \"default\": \"string\"\n },\n \"step\": {\n \"title\": \"Step interval between values\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\"\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\"\n },\n \"required\": {\n \"title\": \"Value is required\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"minValueErrorMessage\": {\n \"title\": \"'Min Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValueErrorMessage\": {\n \"title\": \"'Max Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"invalidDateErrorMessage\": {\n \"title\": \"'Invalid Date' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"selectOptionsType\": {\n \"title\": \"Select options type\",\n \"type\": \"string\",\n \"default\": \"valueWithLabel\"\n },\n \"selectOptionsList\": {\n \"title\": \"Select options list\",\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"value\": {\n \"title\": \"Value\",\n \"type\": \"string\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n }\n },\n \"required\": [\"value\", \"label\"]\n }\n },\n \"isEditable\": {\n \"title\": \"Ability to edit attribute\",\n \"type\": \"string\",\n \"default\": \"editable\"\n },\n \"disabledOnDataKey\": {\n \"title\": \"Disable on false value of another datakey (specify datakey name)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"dataKeyHidden\": {\n \"title\": \"Hide input field\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"icon\": {\n \"title\": \"Icon to show before input cell\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"dataKeyType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"server\",\n \"label\": \"Server attribute (default)\"\n },\n {\n \"value\": \"shared\",\n \"label\": \"Shared attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Timeseries\"\n }\n ]\n },\n {\n \"key\": \"dataKeyValueType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"string\",\n \"label\": \"String\"\n },\n {\n \"value\": \"double\",\n \"label\": \"Double\"\n },\n {\n \"value\": \"integer\",\n \"label\": \"Integer\"\n },\n {\n \"value\": \"booleanCheckbox\",\n \"label\": \"Boolean (Checkbox)\"\n },\n {\n \"value\": \"booleanSwitch\",\n \"label\": \"Boolean (Switch)\"\n },\n {\n \"value\": \"dateTime\",\n \"label\": \"Date & Time\"\n },\n {\n \"value\": \"date\",\n \"label\": \"Date\"\n },\n {\n \"value\": \"time\",\n \"label\": \"Time\"\n },\n {\n \"value\": \"selectOption\",\n \"label\": \"Selectable option\"\n }\n ]\n },\n {\n \"key\": \"selectOptionsType\",\n \"condition\": \"model.dataKeyValueType === 'selectOption'\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"valueWithLabel\",\n \"label\": \"Values with labels\"\n },\n {\n \"value\": \"rawValue\",\n \"label\": \"Raw values\"\n }\n ]\n },\n {\n \"key\": \"selectOptionsList\",\n \"type\": \"array\",\n \"condition\": \"model.dataKeyValueType === 'selectOption'\",\n \"items\": [\n \"selectOptionsList[].value\",\n {\n \"key\": \"selectOptionsList[].label\",\n \"condition\": \"model.selectOptionsType === 'valueWithLabel'\"\n }\n ]\n },\n {\n \"key\": \"step\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"minValue\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"maxValue\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n \"required\",\n {\n \"key\": \"requiredErrorMessage\",\n \"condition\": \"model.required === true\"\n },\n {\n \"key\": \"invalidDateErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'dateTime' || model.dataKeyValueType === 'date' || model.dataKeyValueType === 'time'\"\n },\n {\n \"key\": \"minValueErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"maxValueErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"isEditable\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"editable\",\n \"label\": \"Editable (default)\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n },\n {\n \"value\": \"readonly\",\n \"label\": \"Read-only\"\n }\n ]\n },\n \"disabledOnDataKey\",\n \"dataKeyHidden\",\n\t\t{\n \t\t\"key\": \"icon\",\n\t\t\t\"type\": \"icon\"\n\t\t}\n ]\n}\n", + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"dataKeyType\": {\n \"title\": \"Datakey type\",\n \"type\": \"string\",\n \"default\": \"server\"\n },\n \"dataKeyValueType\": {\n \"title\": \"Datakey value type\",\n \"type\": \"string\",\n \"default\": \"string\"\n },\n \"step\": {\n \"title\": \"Step interval between values\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\"\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\"\n },\n \"required\": {\n \"title\": \"Value is required\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"minValueErrorMessage\": {\n \"title\": \"'Min Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValueErrorMessage\": {\n \"title\": \"'Max Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"invalidDateErrorMessage\": {\n \"title\": \"'Invalid Date' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"isEditable\": {\n \"title\": \"Ability to edit attribute\",\n \"type\": \"string\",\n \"default\": \"editable\"\n },\n \"disabledOnDataKey\": {\n \"title\": \"Disable on false value of another datakey (specify datakey name)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"dataKeyHidden\": {\n \"title\": \"Hide input field\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"icon\": {\n \"title\": \"Icon to show before input cell\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"dataKeyType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"server\",\n \"label\": \"Server attribute (default)\"\n },\n {\n \"value\": \"shared\",\n \"label\": \"Shared attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Timeseries\"\n }\n ]\n },\n {\n \"key\": \"dataKeyValueType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"string\",\n \"label\": \"String\"\n },\n {\n \"value\": \"double\",\n \"label\": \"Double\"\n },\n {\n \"value\": \"integer\",\n \"label\": \"Integer\"\n },\n {\n \"value\": \"booleanCheckbox\",\n \"label\": \"Boolean (Checkbox)\"\n },\n {\n \"value\": \"booleanSwitch\",\n \"label\": \"Boolean (Switch)\"\n },\n {\n \"value\": \"dateTime\",\n \"label\": \"Date & Time\"\n },\n {\n \"value\": \"date\",\n \"label\": \"Date\"\n },\n {\n \"value\": \"time\",\n \"label\": \"Time\"\n }\n ]\n },\n {\n \"key\": \"step\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"minValue\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"maxValue\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n \"required\",\n {\n \"key\": \"requiredErrorMessage\",\n \"condition\": \"model.required === true\"\n },\n {\n \"key\": \"invalidDateErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'dateTime' || model.dataKeyValueType === 'date' || model.dataKeyValueType === 'time'\"\n },\n {\n \"key\": \"minValueErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"maxValueErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"isEditable\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"editable\",\n \"label\": \"Editable (default)\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n },\n {\n \"value\": \"readonly\",\n \"label\": \"Read-only\"\n }\n ]\n },\n \"disabledOnDataKey\",\n \"dataKeyHidden\",\n\t\t{\n \t\t\"key\": \"icon\",\n\t\t\t\"type\": \"icon\"\n\t\t}\n ]\n}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update Multiple Attributes\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } }, From 66a4a1c00826d2eb92ba12867bdae2e2804061eb Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 24 Nov 2020 20:21:24 +0200 Subject: [PATCH 426/463] removed thermostat rule chain from load demo, refactored thermostat attributes --- .../json/demo/dashboards/thermostats.json | 20 +-- .../demo/rule_chains/root_rule_chain.json | 104 ++++--------- .../demo/rule_chains/thermostat_alarms.json | 138 ------------------ .../DefaultSystemDataLoaderService.java | 63 ++++---- .../service/install/InstallScripts.java | 10 -- 5 files changed, 63 insertions(+), 272 deletions(-) delete mode 100644 application/src/main/data/json/demo/rule_chains/thermostat_alarms.json diff --git a/application/src/main/data/json/demo/dashboards/thermostats.json b/application/src/main/data/json/demo/dashboards/thermostats.json index 07b542c3aa..4f486effdf 100644 --- a/application/src/main/data/json/demo/dashboards/thermostats.json +++ b/application/src/main/data/json/demo/dashboards/thermostats.json @@ -147,9 +147,9 @@ "name": "Add", "icon": "add", "type": "customPretty", - "customHtml": "\n \n

Add thermostat

\n \n \n
\n \n \n
\n
\n \n Thermostat name\n \n \n Thermostat name is required.\n \n \n
\n \n High temperature alarm\n \n \n High temperature threshold, °C\n \n \n High temperature threshold is required.\n \n \n \n \n Low humidity alarm\n \n \n \n Low humidity threshold, %\n \n \n Low humidity threshold is required.\n \n \n
\n
\n
\n \n \n
\n", + "customHtml": "
\n \n

Add thermostat

\n \n \n
\n \n \n
\n
\n \n Thermostat name\n \n \n Thermostat name is required.\n \n \n
\n \n High temperature alarm\n \n \n High temperature threshold, °C\n \n \n High temperature threshold is required.\n \n \n \n \n Low humidity alarm\n \n \n \n Low humidity threshold, %\n \n \n Low humidity threshold is required.\n \n \n
\n
\n
\n \n \n
\n", "customCss": ".add-entity-form{\n width: 300px;\n}\n", - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenAddEntityDialog();\n\nfunction openAddEntityDialog() {\n customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();\n}\n\nfunction AddEntityDialogController(instance) {\n let vm = instance;\n \n vm.addEntityFormGroup = vm.fb.group({\n entityName: ['', [vm.validators.required]],\n attributes: vm.fb.group({\n alarmTemperature: [false],\n thresholdTemperature: [{value: null, disabled: true}],\n alarmHumidity: [false],\n thresholdHumidity: [{value: null, disabled: true}]\n })\n });\n \n vm.addEntityFormGroup.get('attributes').get('alarmTemperature').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.addEntityFormGroup.get('attributes').get('thresholdTemperature').enable();\n } else {\n vm.addEntityFormGroup.get('attributes').get('thresholdTemperature').disable();\n }\n });\n \n vm.addEntityFormGroup.get('attributes').get('alarmHumidity').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.addEntityFormGroup.get('attributes').get('thresholdHumidity').enable();\n } else {\n vm.addEntityFormGroup.get('attributes').get('thresholdHumidity').disable();\n }\n });\n\n vm.save = function() {\n vm.addEntityFormGroup.markAsPristine();\n saveEntityObservable().subscribe(\n function (entity) {\n saveAttributes(entity.id).subscribe(\n function () {\n widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n }\n );\n };\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n function saveEntityObservable() {\n const formValues = vm.addEntityFormGroup.value;\n let entity = {\n name: formValues.entityName,\n type: \"thermostat\"\n };\n return deviceService.saveDevice(entity);\n }\n \n function saveAttributes(entityId) {\n let attributes = vm.addEntityFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n if(attributes[key] !== null) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, \"SERVER_SCOPE\", attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenAddEntityDialog();\n\nfunction openAddEntityDialog() {\n customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();\n}\n\nfunction AddEntityDialogController(instance) {\n let vm = instance;\n \n vm.addEntityFormGroup = vm.fb.group({\n entityName: ['', [vm.validators.required]],\n attributes: vm.fb.group({\n temperatureAlarmFlag: [false],\n temperatureAlarmThreshold: [{value: null, disabled: true}],\n humidityAlarmFlag: [false],\n humidityAlarmThreshold: [{value: null, disabled: true}]\n })\n });\n \n vm.addEntityFormGroup.get('attributes').get('temperatureAlarmFlag').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.addEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').enable();\n } else {\n vm.addEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').disable();\n }\n });\n \n vm.addEntityFormGroup.get('attributes').get('humidityAlarmFlag').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.addEntityFormGroup.get('attributes').get('humidityAlarmThreshold').enable();\n } else {\n vm.addEntityFormGroup.get('attributes').get('humidityAlarmThreshold').disable();\n }\n });\n\n vm.save = function() {\n vm.addEntityFormGroup.markAsPristine();\n saveEntityObservable().subscribe(\n function (entity) {\n saveAttributes(entity.id).subscribe(\n function () {\n widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n }\n );\n };\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n function saveEntityObservable() {\n const formValues = vm.addEntityFormGroup.value;\n let entity = {\n name: formValues.entityName,\n type: \"thermostat\"\n };\n return deviceService.saveDevice(entity);\n }\n \n function saveAttributes(entityId) {\n let attributes = vm.addEntityFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n if(attributes[key] !== null) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, \"SERVER_SCOPE\", attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}", "customResources": [], "id": "8ab5a518-67d2-b6a2-956d-81fd512294b2" } @@ -167,9 +167,9 @@ "name": "Edit", "icon": "edit", "type": "customPretty", - "customHtml": "
\n \n

Edit thermostat {{entityName}}

\n \n \n
\n \n \n
\n
\n \n Thermostat name\n \n \n
\n \n High temperature alarm\n \n \n High temperature threshold, °C\n \n \n High temperature threshold is required.\n \n \n\n \n Low humidity alarm\n \n\n \n Low humidity threshold, %\n \n \n Low humidity threshold is required.\n \n \n
\n
\n
\n \n \n
\n", + "customHtml": "
\n \n

Edit thermostat {{entityName}}

\n \n \n
\n \n \n
\n
\n \n Thermostat name\n \n \n
\n \n High temperature alarm\n \n \n High temperature threshold, °C\n \n \n High temperature threshold is required.\n \n \n\n \n Low humidity alarm\n \n\n \n Low humidity threshold, %\n \n \n Low humidity threshold is required.\n \n \n
\n
\n
\n \n \n
\n", "customCss": ".edit-entity-form{\n width: 300px;\n}", - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n \n vm.entityId = entityId;\n vm.entityName = entityName;\n vm.attributes = {};\n \n vm.editEntityFormGroup = vm.fb.group({\n entityName: [''],\n attributes: vm.fb.group({\n alarmTemperature: [false],\n thresholdTemperature: [{value: null, disabled: true}],\n alarmHumidity: [false],\n thresholdHumidity: [{value: null, disabled: true}]\n })\n });\n \n vm.editEntityFormGroup.get('attributes').get('alarmTemperature').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.editEntityFormGroup.get('attributes').get('thresholdTemperature').enable();\n } else {\n vm.editEntityFormGroup.get('attributes').get('thresholdTemperature').disable();\n }\n });\n \n vm.editEntityFormGroup.get('attributes').get('alarmHumidity').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.editEntityFormGroup.get('attributes').get('thresholdHumidity').enable();\n } else {\n vm.editEntityFormGroup.get('attributes').get('thresholdHumidity').disable();\n }\n });\n \n \n getEntityInfo();\n \n \n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveAttributes(entityId).subscribe(\n function () {\n vm.dialogRef.close(null);\n }\n );\n };\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n function getEntityAttributes(attributes) {\n for (var i = 0; i < attributes.length; i++) {\n vm.attributes[attributes[i].key] = attributes[i].value;\n }\n }\n \n function getEntityInfo() {\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE').subscribe(\n function (attributes) {\n getEntityAttributes(attributes);\n vm.editEntityFormGroup.patchValue({\n entityName: vm.entityName,\n attributes: vm.attributes\n });\n // if(vm.attributes.alarmTemperature) {\n // vm.editEntityFormGroup.get('attributes').get('thresholdTemperature').enable();\n // }\n // if(vm.attributes.alarmHumidity) {\n // vm.editEntityFormGroup.get('attributes').get('thresholdHumidity').enable();\n // }\n }\n );\n }\n \n function saveAttributes(entityId) {\n let attributes = vm.editEntityFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n if (attributes[key] !== vm.attributes[key]) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, \"SERVER_SCOPE\", attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n \n vm.entityId = entityId;\n vm.entityName = entityName;\n vm.attributes = {};\n \n vm.editEntityFormGroup = vm.fb.group({\n entityName: [''],\n attributes: vm.fb.group({\n temperatureAlarmFlag: [false],\n temperatureAlarmThreshold: [{value: null, disabled: true}],\n humidityAlarmFlag: [false],\n humidityAlarmThreshold: [{value: null, disabled: true}]\n })\n });\n \n vm.editEntityFormGroup.get('attributes').get('temperatureAlarmFlag').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.editEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').enable();\n } else {\n vm.editEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').disable();\n }\n });\n \n vm.editEntityFormGroup.get('attributes').get('humidityAlarmFlag').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.editEntityFormGroup.get('attributes').get('humidityAlarmThreshold').enable();\n } else {\n vm.editEntityFormGroup.get('attributes').get('humidityAlarmThreshold').disable();\n }\n });\n \n \n getEntityInfo();\n \n \n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveAttributes(entityId).subscribe(\n function () {\n vm.dialogRef.close(null);\n }\n );\n };\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n function getEntityAttributes(attributes) {\n for (var i = 0; i < attributes.length; i++) {\n vm.attributes[attributes[i].key] = attributes[i].value;\n }\n }\n \n function getEntityInfo() {\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE').subscribe(\n function (attributes) {\n getEntityAttributes(attributes);\n vm.editEntityFormGroup.patchValue({\n entityName: vm.entityName,\n attributes: vm.attributes\n });\n // if(vm.attributes.temperatureAlarmFlag) {\n // vm.editEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').enable();\n // }\n // if(vm.attributes.humidityAlarmFlag) {\n // vm.editEntityFormGroup.get('attributes').get('humidityAlarmThreshold').enable();\n // }\n }\n );\n }\n \n function saveAttributes(entityId) {\n let attributes = vm.editEntityFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n if (attributes[key] !== vm.attributes[key]) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, \"SERVER_SCOPE\", attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}", "customResources": [], "id": "7506576f-87ba-d3a0-88fb-e304d451776d" }, @@ -550,7 +550,7 @@ "type": "entity", "dataKeys": [ { - "name": "alarmTemperature", + "name": "temperatureAlarmFlag", "type": "attribute", "label": "High temperature alarm", "color": "#4caf50", @@ -565,7 +565,7 @@ "_hash": 0.8725278440159361 }, { - "name": "thresholdTemperature", + "name": "temperatureAlarmThreshold", "type": "attribute", "label": "High temperature threshold, °C", "color": "#f44336", @@ -576,12 +576,12 @@ "isEditable": "editable", "dataKeyHidden": false, "step": 1, - "disabledOnDataKey": "alarmTemperature" + "disabledOnDataKey": "temperatureAlarmFlag" }, "_hash": 0.7316078472857874 }, { - "name": "alarmHumidity", + "name": "humidityAlarmFlag", "type": "attribute", "label": "Low humidity alarm", "color": "#ffc107", @@ -596,7 +596,7 @@ "_hash": 0.5339673667431057 }, { - "name": "thresholdHumidity", + "name": "humidityAlarmThreshold", "type": "attribute", "label": "Low humidity threshold, %", "color": "#607d8b", @@ -607,7 +607,7 @@ "isEditable": "editable", "dataKeyHidden": false, "step": 1, - "disabledOnDataKey": "alarmHumidity" + "disabledOnDataKey": "humidityAlarmFlag" }, "_hash": 0.2687091190358901 } diff --git a/application/src/main/data/json/demo/rule_chains/root_rule_chain.json b/application/src/main/data/json/demo/rule_chains/root_rule_chain.json index 97ad46c756..a37b1fc865 100644 --- a/application/src/main/data/json/demo/rule_chains/root_rule_chain.json +++ b/application/src/main/data/json/demo/rule_chains/root_rule_chain.json @@ -8,20 +8,8 @@ "configuration": null }, "metadata": { - "firstNodeIndex": 3, + "firstNodeIndex": 6, "nodes": [ - { - "additionalInfo": { - "layoutX": 1069, - "layoutY": 267 - }, - "type": "org.thingsboard.rule.engine.filter.TbJsFilterNode", - "name": "Is Thermostat?", - "debugMode": false, - "configuration": { - "jsScript": "return msg.id.entityType === \"DEVICE\" && msg.type === \"thermostat\";" - } - }, { "additionalInfo": { "layoutX": 824, @@ -61,8 +49,8 @@ }, { "additionalInfo": { - "layoutX": 839, - "layoutY": 345 + "layoutX": 825, + "layoutY": 266 }, "type": "org.thingsboard.rule.engine.action.TbLogNode", "name": "Log RPC from Device", @@ -73,8 +61,8 @@ }, { "additionalInfo": { - "layoutX": 832, - "layoutY": 407 + "layoutX": 825, + "layoutY": 379 }, "type": "org.thingsboard.rule.engine.action.TbLogNode", "name": "Log Other", @@ -97,93 +85,51 @@ }, { "additionalInfo": { - "layoutX": 1069, - "layoutY": 90 + "description": "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type.", + "layoutX": 204, + "layoutY": 240 }, - "type": "org.thingsboard.rule.engine.filter.TbJsFilterNode", - "name": "Is Thermostat?", + "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode", + "name": "Device Profile Node", "debugMode": false, "configuration": { - "jsScript": "return metadata[\"deviceType\"] === \"thermostat\";" - } - }, - { - "additionalInfo": { - "layoutX": 1090, - "layoutY": 360 - }, - "type": "org.thingsboard.rule.engine.action.TbCreateRelationNode", - "name": "Relate to Asset", - "debugMode": false, - "configuration": { - "direction": "FROM", - "relationType": "ToAlarmPropagationAsset", - "entityType": "ASSET", - "entityNamePattern": "Thermostat Alarms", - "entityTypePattern": "AlarmPropagationAsset", - "entityCacheExpiration": 300, - "createEntityIfNotExists": true, - "changeOriginatorToRelatedEntity": false, - "removeCurrentRelations": false + "persistAlarmRulesState": false, + "fetchAlarmRulesStateOnStart": false } } ], "connections": [ { - "fromIndex": 0, - "toIndex": 8, - "type": "True" - }, - { - "fromIndex": 1, - "toIndex": 7, + "fromIndex": 6, + "toIndex": 2, "type": "Success" }, { - "fromIndex": 3, - "toIndex": 5, + "fromIndex": 2, + "toIndex": 4, "type": "Other" }, { - "fromIndex": 3, - "toIndex": 2, + "fromIndex": 2, + "toIndex": 1, "type": "Post attributes" }, { - "fromIndex": 3, - "toIndex": 1, + "fromIndex": 2, + "toIndex": 0, "type": "Post telemetry" }, { - "fromIndex": 3, - "toIndex": 4, + "fromIndex": 2, + "toIndex": 3, "type": "RPC Request from Device" }, { - "fromIndex": 3, - "toIndex": 6, + "fromIndex": 2, + "toIndex": 5, "type": "RPC Request to Device" - }, - { - "fromIndex": 3, - "toIndex": 0, - "type": "Entity Created" } ], - "ruleChainConnections": [ - { - "fromIndex": 7, - "targetRuleChainId": { - "entityType": "RULE_CHAIN", - "id": "25e26570-89ed-11ea-a650-cd6e14e633bd" - }, - "additionalInfo": { - "layoutX": 1109, - "layoutY": 182, - "ruleChainNodeId": "rule-chain-node-10" - }, - "type": "True" - } - ] + "ruleChainConnections": null } } \ No newline at end of file diff --git a/application/src/main/data/json/demo/rule_chains/thermostat_alarms.json b/application/src/main/data/json/demo/rule_chains/thermostat_alarms.json deleted file mode 100644 index 30812d6003..0000000000 --- a/application/src/main/data/json/demo/rule_chains/thermostat_alarms.json +++ /dev/null @@ -1,138 +0,0 @@ -{ - "ruleChain": { - "additionalInfo": { - "description": "" - }, - "name": "Thermostat Alarms", - "firstRuleNodeId": null, - "root": false, - "debugMode": false, - "configuration": null - }, - "metadata": { - "firstNodeIndex": 6, - "nodes": [ - { - "additionalInfo": { - "layoutX": 822, - "layoutY": 294 - }, - "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", - "name": "Save Timeseries", - "debugMode": false, - "configuration": { - "defaultTTL": 0 - } - }, - { - "additionalInfo": { - "description": null, - "layoutX": 824, - "layoutY": 221 - }, - "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", - "name": "Save Client Attributes", - "debugMode": false, - "configuration": { - "scope": "SERVER_SCOPE", - "notifyDevice": null - } - }, - { - "additionalInfo": { - "layoutX": 494, - "layoutY": 309 - }, - "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", - "name": "Message Type Switch", - "debugMode": false, - "configuration": { - "version": 0 - } - }, - { - "additionalInfo": { - "layoutX": 824, - "layoutY": 383 - }, - "type": "org.thingsboard.rule.engine.action.TbLogNode", - "name": "Log RPC from Device", - "debugMode": false, - "configuration": { - "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" - } - }, - { - "additionalInfo": { - "layoutX": 823, - "layoutY": 444 - }, - "type": "org.thingsboard.rule.engine.action.TbLogNode", - "name": "Log Other", - "debugMode": false, - "configuration": { - "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" - } - }, - { - "additionalInfo": { - "layoutX": 822, - "layoutY": 507 - }, - "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode", - "name": "RPC Call Request", - "debugMode": false, - "configuration": { - "timeoutInSeconds": 60 - } - }, - { - "additionalInfo": { - "description": "", - "layoutX": 209, - "layoutY": 307 - }, - "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode", - "name": "Device Profile Node", - "debugMode": false, - "configuration": { - "persistAlarmRulesState": false, - "fetchAlarmRulesStateOnStart": false - } - } - ], - "connections": [ - { - "fromIndex": 2, - "toIndex": 4, - "type": "Other" - }, - { - "fromIndex": 2, - "toIndex": 1, - "type": "Post attributes" - }, - { - "fromIndex": 2, - "toIndex": 0, - "type": "Post telemetry" - }, - { - "fromIndex": 2, - "toIndex": 3, - "type": "RPC Request from Device" - }, - { - "fromIndex": 2, - "toIndex": 5, - "type": "RPC Request to Device" - }, - { - "fromIndex": 6, - "toIndex": 2, - "type": "Success" - } - ], - "ruleChainConnections": null - } -} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index ef25eeefdb..2c6f2fa939 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -51,7 +51,6 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; -import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.query.BooleanFilterPredicate; import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValueSourceType; @@ -73,7 +72,6 @@ import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; @@ -129,9 +127,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Autowired private DeviceCredentialsService deviceCredentialsService; - @Autowired - private RuleChainService ruleChainService; - @Bean protected BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); @@ -271,8 +266,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { thermostatDeviceProfile.setTransportType(DeviceTransportType.DEFAULT); thermostatDeviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); thermostatDeviceProfile.setDescription("Thermostat device profile"); - thermostatDeviceProfile.setDefaultRuleChainId(ruleChainService.findTenantRuleChains( - demoTenant.getId(), new PageLink(1, 0, "Thermostat Alarms")).getData().get(0).getId()); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); @@ -290,13 +283,13 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { AlarmCondition temperatureCondition = new AlarmCondition(); temperatureCondition.setSpec(new SimpleAlarmConditionSpec()); - KeyFilter alarmTemperatureAttributeFilter = new KeyFilter(); - alarmTemperatureAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "alarmTemperature")); - alarmTemperatureAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN); - BooleanFilterPredicate alarmTemperatureAttributePredicate = new BooleanFilterPredicate(); - alarmTemperatureAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL); - alarmTemperatureAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE)); - alarmTemperatureAttributeFilter.setPredicate(alarmTemperatureAttributePredicate); + KeyFilter temperatureAlarmFlagAttributeFilter = new KeyFilter(); + temperatureAlarmFlagAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperatureAlarmFlag")); + temperatureAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN); + BooleanFilterPredicate temperatureAlarmFlagAttributePredicate = new BooleanFilterPredicate(); + temperatureAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL); + temperatureAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE)); + temperatureAlarmFlagAttributeFilter.setPredicate(temperatureAlarmFlagAttributePredicate); KeyFilter temperatureTimeseriesFilter = new KeyFilter(); temperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); @@ -305,10 +298,10 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { temperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); FilterPredicateValue temperatureTimeseriesPredicateValue = new FilterPredicateValue<>(25.0, null, - new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdTemperature")); + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "temperatureAlarmThreshold")); temperatureTimeseriesFilterPredicate.setValue(temperatureTimeseriesPredicateValue); temperatureTimeseriesFilter.setPredicate(temperatureTimeseriesFilterPredicate); - temperatureCondition.setCondition(Arrays.asList(alarmTemperatureAttributeFilter, temperatureTimeseriesFilter)); + temperatureCondition.setCondition(Arrays.asList(temperatureAlarmFlagAttributeFilter, temperatureTimeseriesFilter)); temperatureRule.setAlarmDetails("Current temperature = ${temperature}"); temperatureRule.setCondition(temperatureCondition); highTemperature.setCreateRules(new LinkedHashMap<>(Collections.singletonMap(AlarmSeverity.MAJOR, temperatureRule))); @@ -324,7 +317,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { clearTemperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS_OR_EQUAL); FilterPredicateValue clearTemperatureTimeseriesPredicateValue = new FilterPredicateValue<>(25.0, null, - new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdTemperature")); + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "temperatureAlarmThreshold")); clearTemperatureTimeseriesFilterPredicate.setValue(clearTemperatureTimeseriesPredicateValue); clearTemperatureTimeseriesFilter.setPredicate(clearTemperatureTimeseriesFilterPredicate); @@ -340,13 +333,13 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { AlarmCondition humidityCondition = new AlarmCondition(); humidityCondition.setSpec(new SimpleAlarmConditionSpec()); - KeyFilter alarmHumidityAttributeFilter = new KeyFilter(); - alarmHumidityAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "alarmHumidity")); - alarmHumidityAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN); - BooleanFilterPredicate alarmHumidityAttributePredicate = new BooleanFilterPredicate(); - alarmHumidityAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL); - alarmHumidityAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE)); - alarmHumidityAttributeFilter.setPredicate(alarmHumidityAttributePredicate); + KeyFilter humidityAlarmFlagAttributeFilter = new KeyFilter(); + humidityAlarmFlagAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "humidityAlarmFlag")); + humidityAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN); + BooleanFilterPredicate humidityAlarmFlagAttributePredicate = new BooleanFilterPredicate(); + humidityAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL); + humidityAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE)); + humidityAlarmFlagAttributeFilter.setPredicate(humidityAlarmFlagAttributePredicate); KeyFilter humidityTimeseriesFilter = new KeyFilter(); humidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity")); @@ -355,10 +348,10 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { humidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS); FilterPredicateValue humidityTimeseriesPredicateValue = new FilterPredicateValue<>(60.0, null, - new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdHumidity")); + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "humidityAlarmThreshold")); humidityTimeseriesFilterPredicate.setValue(humidityTimeseriesPredicateValue); humidityTimeseriesFilter.setPredicate(humidityTimeseriesFilterPredicate); - humidityCondition.setCondition(Arrays.asList(alarmHumidityAttributeFilter, humidityTimeseriesFilter)); + humidityCondition.setCondition(Arrays.asList(humidityAlarmFlagAttributeFilter, humidityTimeseriesFilter)); humidityRule.setCondition(humidityCondition); humidityRule.setAlarmDetails("Current humidity = ${humidity}"); @@ -375,7 +368,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { clearHumidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER_OR_EQUAL); FilterPredicateValue clearHumidityTimeseriesPredicateValue = new FilterPredicateValue<>(60.0, null, - new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "thresholdHumidity")); + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "humidityAlarmThreshold")); clearHumidityTimeseriesFilterPredicate.setValue(clearHumidityTimeseriesPredicateValue); clearHumidityTimeseriesFilter.setPredicate(clearHumidityTimeseriesFilterPredicate); @@ -394,18 +387,18 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { attributesService.save(demoTenant.getId(), t1Id, DataConstants.SERVER_SCOPE, Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.3948)), new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", -122.1503)), - new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmTemperature", true)), - new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmHumidity", true)), - new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdTemperature", (long) 20)), - new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdHumidity", (long) 50)))); + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("temperatureAlarmFlag", true)), + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("humidityAlarmFlag", true)), + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("temperatureAlarmThreshold", (long) 20)), + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("humidityAlarmThreshold", (long) 50)))); attributesService.save(demoTenant.getId(), t2Id, DataConstants.SERVER_SCOPE, Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.493801)), new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", -121.948769)), - new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmTemperature", true)), - new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmHumidity", true)), - new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdTemperature", (long) 25)), - new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdHumidity", (long) 30)))); + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("temperatureAlarmFlag", true)), + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("humidityAlarmFlag", true)), + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("temperatureAlarmThreshold", (long) 25)), + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("humidityAlarmThreshold", (long) 30)))); installScripts.loadDashboards(demoTenant.getId(), null); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index ac047060ae..550e5f28bd 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -212,20 +212,10 @@ public class InstallScripts { public void loadDemoRuleChains(TenantId tenantId) throws Exception { Path ruleChainsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, RULE_CHAINS_DIR); try { - JsonNode ruleChainJson = objectMapper.readTree(ruleChainsDir.resolve("thermostat_alarms.json").toFile()); - RuleChain ruleChain = objectMapper.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class); - RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class); - ruleChain.setTenantId(tenantId); - ruleChain = ruleChainService.saveRuleChain(ruleChain); - ruleChainMetaData.setRuleChainId(ruleChain.getId()); - ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), ruleChainMetaData); - JsonNode rootChainJson = objectMapper.readTree(ruleChainsDir.resolve("root_rule_chain.json").toFile()); RuleChain rootChain = objectMapper.treeToValue(rootChainJson.get("ruleChain"), RuleChain.class); RuleChainMetaData rootChainMetaData = objectMapper.treeToValue(rootChainJson.get("metadata"), RuleChainMetaData.class); - RuleChainId thermostatsRuleChainId = ruleChain.getId(); - rootChainMetaData.getRuleChainConnections().forEach(connection -> connection.setTargetRuleChainId(thermostatsRuleChainId)); rootChain.setTenantId(tenantId); rootChain = ruleChainService.saveRuleChain(rootChain); rootChainMetaData.setRuleChainId(rootChain.getId()); From 680eb89fa08c6be50ee3db0299338bf5784bf676 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Wed, 25 Nov 2020 10:11:38 +0200 Subject: [PATCH 427/463] Added validation for device name in provision request, if it is present. Added session closing when provision client tries to use topics not allowed for provisioning feature --- .../service/device/DeviceProvisionServiceImpl.java | 9 +++++++-- .../server/dao/device/DeviceProvisionService.java | 3 --- .../server/transport/mqtt/MqttTransportHandler.java | 1 + 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 583139f05c..cfb51b4627 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -18,9 +18,7 @@ package org.thingsboard.server.service.device; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -114,6 +112,13 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { public ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) { String provisionRequestKey = provisionRequest.getCredentials().getProvisionDeviceKey(); String provisionRequestSecret = provisionRequest.getCredentials().getProvisionDeviceSecret(); + if (provisionRequest.getDeviceName() != null) { + provisionRequest.setDeviceName(provisionRequest.getDeviceName().trim()); + if (StringUtils.isEmpty(provisionRequest.getDeviceName())) { + log.warn("Provision request contains empty device name!"); + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); + } + } if (StringUtils.isEmpty(provisionRequestKey) || StringUtils.isEmpty(provisionRequestSecret)) { throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java index 5d038b2d31..660f1a5f35 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java @@ -15,9 +15,6 @@ */ package org.thingsboard.server.dao.device; -import com.google.common.util.concurrent.ListenableFuture; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index cabc61cab6..0df14d4655 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -179,6 +179,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } } else { + ctx.close(); throw new RuntimeException("Unsupported topic for provisioning requests!"); } } catch (RuntimeException | AdaptorException e) { From 1895fd833025d893a15122d9bc33704280ccce1b Mon Sep 17 00:00:00 2001 From: zbeacon Date: Wed, 25 Nov 2020 11:23:36 +0200 Subject: [PATCH 428/463] Added translates for provisioning action types --- ui-ngx/src/app/shared/models/audit-log.models.ts | 8 ++++++-- ui-ngx/src/assets/locale/locale.constant-en_US.json | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/shared/models/audit-log.models.ts b/ui-ngx/src/app/shared/models/audit-log.models.ts index 1b450d5568..46efd740a3 100644 --- a/ui-ngx/src/app/shared/models/audit-log.models.ts +++ b/ui-ngx/src/app/shared/models/audit-log.models.ts @@ -51,7 +51,9 @@ export enum ActionType { LOGOUT = 'LOGOUT', LOCKOUT = 'LOCKOUT', ASSIGNED_FROM_TENANT = 'ASSIGNED_FROM_TENANT', - ASSIGNED_TO_TENANT = 'ASSIGNED_TO_TENANT' + ASSIGNED_TO_TENANT = 'ASSIGNED_TO_TENANT', + PROVISION_SUCCESS = 'PROVISION_SUCCESS', + PROVISION_FAILURE = 'PROVISION_FAILURE' } export enum ActionStatus { @@ -83,7 +85,9 @@ export const actionTypeTranslations = new Map( [ActionType.LOGOUT, 'audit-log.type-logout'], [ActionType.LOCKOUT, 'audit-log.type-lockout'], [ActionType.ASSIGNED_FROM_TENANT, 'audit-log.type-assigned-from-tenant'], - [ActionType.ASSIGNED_TO_TENANT, 'audit-log.type-assigned-to-tenant'] + [ActionType.ASSIGNED_TO_TENANT, 'audit-log.type-assigned-to-tenant'], + [ActionType.PROVISION_SUCCESS, 'audit-log.type-provision-success'], + [ActionType.PROVISION_FAILURE, 'audit-log.type-provision-failure'] ] ); diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 89950a11e0..5ee61fa277 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -472,7 +472,9 @@ "search": "Search audit logs", "clear-search": "Clear search", "type-assigned-from-tenant": "Assigned from Tenant", - "type-assigned-to-tenant": "Assigned to Tenant" + "type-assigned-to-tenant": "Assigned to Tenant", + "type-provision-success": "Device provisioned", + "type-provision-failure": "Device provisioning was failed" }, "confirm-on-exit": { "message": "You have unsaved changes. Are you sure you want to leave this page?", From 254905c2b3b902d5e2bc76b85dedc7e315f28f5e Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 25 Nov 2020 12:53:33 +0200 Subject: [PATCH 429/463] Minor improvements --- application/src/main/data/json/demo/dashboards/gateways.json | 2 +- .../thingsboard/rule/engine/profile/TbDeviceProfileNode.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/application/src/main/data/json/demo/dashboards/gateways.json b/application/src/main/data/json/demo/dashboards/gateways.json index 34474ea890..0a18b10a52 100644 --- a/application/src/main/data/json/demo/dashboards/gateways.json +++ b/application/src/main/data/json/demo/dashboards/gateways.json @@ -955,7 +955,7 @@ }, "methodName": "gateway_restart", "methodParams": "{}", - "buttonText": "gateway restart" + "buttonText": "GATEWAY RESTART" }, "title": "New RPC Button", "dropShadow": true, diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java index fa7538bce6..b4bb19438f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java @@ -54,7 +54,8 @@ import java.util.concurrent.TimeUnit; relationTypes = {"Alarm Created", "Alarm Updated", "Alarm Severity Updated", "Alarm Cleared", "Success", "Failure"}, configClazz = TbDeviceProfileNodeConfiguration.class, nodeDescription = "Process device messages based on device profile settings", - nodeDetails = "Create and clear alarms based on alarm rules defined in device profile. Generates ", + nodeDetails = "Create and clear alarms based on alarm rules defined in device profile. The output relation type is either " + + "'Alarm Created', 'Alarm Updated', 'Alarm Severity Updated' and 'Alarm Cleared' or simply 'Success' if no alarms were affected.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbDeviceProfileConfig" ) @@ -119,7 +120,6 @@ public class TbDeviceProfileNode implements TbNode { } else { removeDeviceState(deviceId); } - } else { if (EntityType.DEVICE.equals(originatorType)) { DeviceId deviceId = new DeviceId(msg.getOriginator().getId()); From 9263e53c1d12a51d6e9fbbf9b813cf122019ccd2 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 25 Nov 2020 12:55:23 +0200 Subject: [PATCH 430/463] Throw exception on email or sms api limits. --- .../org/thingsboard/server/service/mail/DefaultMailService.java | 2 ++ .../org/thingsboard/server/service/sms/DefaultSmsService.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 7865321e67..e9cef05dd5 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -250,6 +250,8 @@ public class DefaultMailService implements MailService { helper.setText(body); mailSender.send(helper.getMimeMessage()); apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1); + } else { + throw new RuntimeException("Email sending is disabled due to API limits!"); } } diff --git a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java index 203fc1127d..7f61476cd0 100644 --- a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java +++ b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java @@ -106,6 +106,8 @@ public class DefaultSmsService implements SmsService { apiUsageClient.report(tenantId, ApiUsageRecordKey.SMS_EXEC_COUNT, smsCount); } } + } else { + throw new RuntimeException("SMS sending is disabled due to API limits!"); } } From 7cd2a870f56bf0f4687ff756b47c7a89ed1ce311 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 25 Nov 2020 11:05:36 +0200 Subject: [PATCH 431/463] count assets by tenantId improvements --- .../org/thingsboard/server/dao/sql/asset/AssetRepository.java | 2 +- .../java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java index a6e016c418..da2bc9e687 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java @@ -122,5 +122,5 @@ public interface AssetRepository extends PagingAndSortingRepository findTenantAssetTypes(@Param("tenantId") UUID tenantId); - Long countByTenantId(UUID tenantId); + Long countByTenantIdAndTypeIsNot(UUID tenantId, String type); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java index d2a5167b43..fd73aa2481 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java @@ -44,6 +44,7 @@ import java.util.UUID; */ @Component public class JpaAssetDao extends JpaAbstractSearchTextDao implements AssetDao { + public static final String TB_SERVICE_QUEUE = "TbServiceQueue"; @Autowired private AssetRepository assetRepository; @@ -179,6 +180,6 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao im @Override public Long countByTenantId(TenantId tenantId) { - return assetRepository.countByTenantId(tenantId.getId()); + return assetRepository.countByTenantIdAndTypeIsNot(tenantId.getId(), TB_SERVICE_QUEUE); } } From 80b1ffa73649bbe380abe747cb81823871c41f82 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 25 Nov 2020 11:40:04 +0200 Subject: [PATCH 432/463] added thermostat rule chain, removed "Temperature & Humidity Demo Dashboard" & "Raspberry PI GPIO Demo Dashboard" --- .../raspberry_pi_gpio_demo_dashboard.json | 700 ------------------ ...temperature___humidity_demo_dashboard.json | 264 ------- .../rule_chains/thermostat_rule_chain.json | 135 ++++ .../DefaultSystemDataLoaderService.java | 7 + .../service/install/InstallScripts.java | 9 +- 5 files changed, 150 insertions(+), 965 deletions(-) delete mode 100644 application/src/main/data/json/demo/dashboards/raspberry_pi_gpio_demo_dashboard.json delete mode 100644 application/src/main/data/json/demo/dashboards/temperature___humidity_demo_dashboard.json create mode 100644 application/src/main/data/json/demo/rule_chains/thermostat_rule_chain.json diff --git a/application/src/main/data/json/demo/dashboards/raspberry_pi_gpio_demo_dashboard.json b/application/src/main/data/json/demo/dashboards/raspberry_pi_gpio_demo_dashboard.json deleted file mode 100644 index 1dfba7c8d4..0000000000 --- a/application/src/main/data/json/demo/dashboards/raspberry_pi_gpio_demo_dashboard.json +++ /dev/null @@ -1,700 +0,0 @@ -{ - "title": "Raspberry PI GPIO Demo Dashboard", - "configuration": { - "description": "Demo dashboard for Raspberry PI GPIO Demo", - "widgets": { - "602177f6-267b-cb87-4e8f-e23d7fb2f61c": { - "isSystemType": true, - "bundleAlias": "gpio_widgets", - "typeAlias": "raspberry_pi_gpio_control", - "type": "rpc", - "title": "New widget", - "sizeX": 6, - "sizeY": 10, - "config": { - "targetDeviceAliases": [], - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "0px", - "settings": { - "parseGpioStatusFunction": "return body[pin] === true;", - "gpioStatusChangeRequest": { - "method": "setGpioStatus", - "paramsBody": "{\n \"pin\": \"{$pin}\",\n \"enabled\": \"{$enabled}\"\n}" - }, - "requestTimeout": 500, - "switchPanelBackgroundColor": "#008a00", - "gpioStatusRequest": { - "method": "getGpioStatus", - "paramsBody": "{}" - }, - "gpioList": [ - { - "pin": 7, - "label": "GPIO 4 (GPCLK0)", - "row": 3, - "col": 0, - "_uniqueKey": 0 - }, - { - "pin": 11, - "label": "GPIO 17", - "row": 5, - "col": 0, - "_uniqueKey": 1 - }, - { - "pin": 12, - "label": "GPIO 18", - "row": 5, - "col": 1, - "_uniqueKey": 2 - }, - { - "_uniqueKey": 3, - "pin": 13, - "label": "GPIO 27", - "row": 6, - "col": 0 - }, - { - "_uniqueKey": 4, - "pin": 15, - "label": "GPIO 22", - "row": 7, - "col": 0 - }, - { - "_uniqueKey": 5, - "pin": 16, - "label": "GPIO 23", - "row": 7, - "col": 1 - }, - { - "_uniqueKey": 6, - "pin": 18, - "label": "GPIO 24", - "row": 8, - "col": 1 - }, - { - "_uniqueKey": 7, - "pin": 22, - "label": "GPIO 25", - "row": 10, - "col": 1 - }, - { - "_uniqueKey": 8, - "pin": 29, - "label": "GPIO 5", - "row": 14, - "col": 0 - }, - { - "_uniqueKey": 9, - "pin": 31, - "label": "GPIO 6", - "row": 15, - "col": 0 - }, - { - "_uniqueKey": 10, - "pin": 32, - "label": "GPIO 12", - "row": 15, - "col": 1 - }, - { - "_uniqueKey": 11, - "pin": 33, - "label": "GPIO 13", - "row": 16, - "col": 0 - }, - { - "_uniqueKey": 12, - "pin": 35, - "label": "GPIO 19", - "row": 17, - "col": 0 - }, - { - "_uniqueKey": 13, - "pin": 36, - "label": "GPIO 16", - "row": 17, - "col": 1 - }, - { - "_uniqueKey": 14, - "pin": 37, - "label": "GPIO 26", - "row": 18, - "col": 0 - }, - { - "_uniqueKey": 15, - "pin": 38, - "label": "GPIO 20", - "row": 18, - "col": 1 - }, - { - "_uniqueKey": 16, - "pin": 40, - "label": "GPIO 21", - "row": 19, - "col": 1 - } - ] - }, - "title": "Raspberry Pi GPIO Control Panel", - "datasources": [], - "targetDeviceAliasIds": [ - "f26b12b6-6938-e1a0-85ec-d88a1f23e382" - ] - }, - "row": 0, - "col": 0, - "id": "602177f6-267b-cb87-4e8f-e23d7fb2f61c" - }, - "3cca52a5-e874-eb43-b444-8efa01e663c8": { - "isSystemType": true, - "bundleAlias": "gpio_widgets", - "typeAlias": "raspberry_pi_gpio_panel", - "type": "latest", - "title": "New widget", - "sizeX": 7, - "sizeY": 10, - "config": { - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "0px", - "settings": { - "gpioList": [ - { - "pin": 1, - "label": "3.3V", - "row": 0, - "col": 0, - "color": "#fc9700", - "_uniqueKey": 0 - }, - { - "pin": 2, - "label": "5V", - "row": 0, - "col": 1, - "color": "#fb0000", - "_uniqueKey": 1 - }, - { - "pin": 3, - "label": "GPIO 2 (I2C1_SDA)", - "row": 1, - "col": 0, - "color": "#02fefb", - "_uniqueKey": 2 - }, - { - "color": "#fb0000", - "pin": 4, - "label": "5V", - "row": 1, - "col": 1 - }, - { - "color": "#02fefb", - "pin": 5, - "label": "GPIO 3 (I2C1_SCL)", - "row": 2, - "col": 0 - }, - { - "color": "#000000", - "pin": 6, - "label": "GND", - "row": 2, - "col": 1 - }, - { - "color": "#00fd00", - "pin": 7, - "label": "GPIO 4 (GPCLK0)", - "row": 3, - "col": 0 - }, - { - "color": "#fdfb00", - "pin": 8, - "label": "GPIO 14 (UART_TXD)", - "row": 3, - "col": 1 - }, - { - "color": "#000000", - "pin": 9, - "label": "GND", - "row": 4, - "col": 0 - }, - { - "color": "#fdfb00", - "pin": 10, - "label": "GPIO 15 (UART_RXD)", - "row": 4, - "col": 1 - }, - { - "color": "#00fd00", - "pin": 11, - "label": "GPIO 17", - "row": 5, - "col": 0 - }, - { - "color": "#00fd00", - "pin": 12, - "label": "GPIO 18", - "row": 5, - "col": 1 - }, - { - "color": "#00fd00", - "pin": 13, - "label": "GPIO 27", - "row": 6, - "col": 0 - }, - { - "color": "#000000", - "pin": 14, - "label": "GND", - "row": 6, - "col": 1 - }, - { - "color": "#00fd00", - "pin": 15, - "label": "GPIO 22", - "row": 7, - "col": 0 - }, - { - "color": "#00fd00", - "pin": 16, - "label": "GPIO 23", - "row": 7, - "col": 1 - }, - { - "color": "#fc9700", - "pin": 17, - "label": "3.3V", - "row": 8, - "col": 0 - }, - { - "color": "#00fd00", - "pin": 18, - "label": "GPIO 24", - "row": 8, - "col": 1 - }, - { - "color": "#fd01fd", - "pin": 19, - "label": "GPIO 10 (SPI_MOSI)", - "row": 9, - "col": 0 - }, - { - "color": "#000000", - "pin": 20, - "label": "GND", - "row": 9, - "col": 1 - }, - { - "color": "#fd01fd", - "pin": 21, - "label": "GPIO 9 (SPI_MISO)", - "row": 10, - "col": 0 - }, - { - "color": "#00fd00", - "pin": 22, - "label": "GPIO 25", - "row": 10, - "col": 1 - }, - { - "color": "#fd01fd", - "pin": 23, - "label": "GPIO 11 (SPI_SCLK)", - "row": 11, - "col": 0 - }, - { - "color": "#fd01fd", - "pin": 24, - "label": "GPIO 8 (SPI_CE0)", - "row": 11, - "col": 1 - }, - { - "color": "#000000", - "pin": 25, - "label": "GND", - "row": 12, - "col": 0 - }, - { - "color": "#fd01fd", - "pin": 26, - "label": "GPIO 7 (SPI_CE1)", - "row": 12, - "col": 1 - }, - { - "color": "#ffffff", - "pin": 27, - "label": "ID_SD", - "row": 13, - "col": 0 - }, - { - "color": "#ffffff", - "pin": 28, - "label": "ID_SC", - "row": 13, - "col": 1 - }, - { - "color": "#00fd00", - "pin": 29, - "label": "GPIO 5", - "row": 14, - "col": 0 - }, - { - "color": "#000000", - "pin": 30, - "label": "GND", - "row": 14, - "col": 1 - }, - { - "color": "#00fd00", - "pin": 31, - "label": "GPIO 6", - "row": 15, - "col": 0 - }, - { - "color": "#00fd00", - "pin": 32, - "label": "GPIO 12", - "row": 15, - "col": 1 - }, - { - "color": "#00fd00", - "pin": 33, - "label": "GPIO 13", - "row": 16, - "col": 0 - }, - { - "color": "#000000", - "pin": 34, - "label": "GND", - "row": 16, - "col": 1 - }, - { - "color": "#00fd00", - "pin": 35, - "label": "GPIO 19", - "row": 17, - "col": 0 - }, - { - "color": "#00fd00", - "pin": 36, - "label": "GPIO 16", - "row": 17, - "col": 1 - }, - { - "color": "#00fd00", - "pin": 37, - "label": "GPIO 26", - "row": 18, - "col": 0 - }, - { - "color": "#00fd00", - "pin": 38, - "label": "GPIO 20", - "row": 18, - "col": 1 - }, - { - "color": "#000000", - "pin": 39, - "label": "GND", - "row": 19, - "col": 0 - }, - { - "color": "#00fd00", - "pin": 40, - "label": "GPIO 21", - "row": 19, - "col": 1 - } - ], - "ledPanelBackgroundColor": "#008a00" - }, - "title": "Raspberry Pi GPIO Status Panel", - "datasources": [ - { - "type": "entity", - "dataKeys": [ - { - "name": "7", - "type": "attribute", - "label": "7", - "color": "#2196f3", - "settings": {}, - "_hash": 0.20925966435886978 - }, - { - "name": "11", - "type": "attribute", - "label": "11", - "color": "#4caf50", - "settings": {}, - "_hash": 0.330267349594344 - }, - { - "name": "12", - "type": "attribute", - "label": "12", - "color": "#f44336", - "settings": {}, - "_hash": 0.5040578704481748 - }, - { - "name": "13", - "type": "attribute", - "label": "13", - "color": "#ffc107", - "settings": {}, - "_hash": 0.588956328191639 - }, - { - "name": "15", - "type": "attribute", - "label": "15", - "color": "#607d8b", - "settings": {}, - "_hash": 0.9229040530336119 - }, - { - "name": "16", - "type": "attribute", - "label": "16", - "color": "#9c27b0", - "settings": {}, - "_hash": 0.8692315253041654 - }, - { - "name": "18", - "type": "attribute", - "label": "18", - "color": "#8bc34a", - "settings": {}, - "_hash": 0.41465562857521543 - }, - { - "name": "22", - "type": "attribute", - "label": "22", - "color": "#3f51b5", - "settings": {}, - "_hash": 0.36135260043112827 - }, - { - "name": "29", - "type": "attribute", - "label": "29", - "color": "#e91e63", - "settings": {}, - "_hash": 0.9904592276182183 - }, - { - "name": "31", - "type": "attribute", - "label": "31", - "color": "#ffeb3b", - "settings": {}, - "_hash": 0.038330985429919195 - }, - { - "name": "32", - "type": "attribute", - "label": "32", - "color": "#03a9f4", - "settings": {}, - "_hash": 0.4334683890135089 - }, - { - "name": "33", - "type": "attribute", - "label": "33", - "color": "#ff9800", - "settings": {}, - "_hash": 0.6487255992492305 - }, - { - "name": "35", - "type": "attribute", - "label": "35", - "color": "#673ab7", - "settings": {}, - "_hash": 0.971555321150732 - }, - { - "name": "36", - "type": "attribute", - "label": "36", - "color": "#cddc39", - "settings": {}, - "_hash": 0.7826129728424382 - }, - { - "name": "37", - "type": "attribute", - "label": "37", - "color": "#009688", - "settings": {}, - "_hash": 0.44925676517537627 - }, - { - "name": "38", - "type": "attribute", - "label": "38", - "color": "#795548", - "settings": {}, - "_hash": 0.051518155759787465 - }, - { - "name": "40", - "type": "attribute", - "label": "40", - "color": "#00bcd4", - "settings": {}, - "_hash": 0.8733296686871144 - } - ], - "name": "RPi", - "entityAliasId": "f26b12b6-6938-e1a0-85ec-d88a1f23e382" - } - ], - "timewindow": { - "realtime": { - "timewindowMs": 60000 - } - } - }, - "row": 0, - "col": 6, - "id": "3cca52a5-e874-eb43-b444-8efa01e663c8" - } - }, - "states": { - "default": { - "name": "Default", - "root": true, - "layouts": { - "main": { - "widgets": { - "602177f6-267b-cb87-4e8f-e23d7fb2f61c": { - "sizeX": 6, - "sizeY": 10, - "row": 0, - "col": 0 - }, - "3cca52a5-e874-eb43-b444-8efa01e663c8": { - "sizeX": 7, - "sizeY": 10, - "row": 0, - "col": 6 - } - }, - "gridSettings": { - "backgroundColor": "#eeeeee", - "color": "rgba(0,0,0,0.870588)", - "columns": 24, - "margins": [ - 10, - 10 - ], - "backgroundSizeMode": "100%" - } - } - } - } - }, - "entityAliases": { - "f26b12b6-6938-e1a0-85ec-d88a1f23e382": { - "id": "f26b12b6-6938-e1a0-85ec-d88a1f23e382", - "alias": "RPi", - "filter": { - "type": "entityName", - "resolveMultiple": false, - "entityType": "DEVICE", - "entityNameFilter": "Raspberry Pi Demo Device" - } - } - }, - "timewindow": { - "displayValue": "", - "selectedTab": 0, - "realtime": { - "interval": 1000, - "timewindowMs": 60000 - }, - "history": { - "historyType": 0, - "interval": 1000, - "timewindowMs": 60000, - "fixedTimewindow": { - "startTimeMs": 1498653734150, - "endTimeMs": 1498740134150 - } - }, - "aggregation": { - "type": "AVG", - "limit": 200 - } - }, - "settings": { - "stateControllerId": "default", - "showTitle": true, - "showDashboardsSelect": true, - "showEntitiesSelect": true, - "showDashboardTimewindow": true, - "showDashboardExport": true, - "toolbarAlwaysOpen": false - } - }, - "name": "Raspberry PI GPIO Demo Dashboard" -} \ No newline at end of file diff --git a/application/src/main/data/json/demo/dashboards/temperature___humidity_demo_dashboard.json b/application/src/main/data/json/demo/dashboards/temperature___humidity_demo_dashboard.json deleted file mode 100644 index 4d4f901558..0000000000 --- a/application/src/main/data/json/demo/dashboards/temperature___humidity_demo_dashboard.json +++ /dev/null @@ -1,264 +0,0 @@ -{ - "title": "Temperature & Humidity Demo Dashboard", - "configuration": { - "description": "Demo dashboard for sample applications that upload temperature and humidity received from DHT11 or DHT22 sensors", - "widgets": { - "03e06986-1c50-e9e4-267c-2bae930ad9a2": { - "isSystemType": true, - "bundleAlias": "digital_gauges", - "typeAlias": "digital_thermometer", - "type": "latest", - "title": "New widget", - "sizeX": 5, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "dataKeys": [ - { - "name": "temperature", - "type": "timeseries", - "label": "temperature", - "color": "#2196f3", - "settings": {}, - "_hash": 0.3720839051412099 - } - ], - "name": "DHT11", - "entityAliasId": "63a93238-c13f-4403-4bcc-9ccc86bd6a62" - } - ], - "timewindow": { - "realtime": { - "timewindowMs": 60000 - } - }, - "showTitle": false, - "backgroundColor": "#000000", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "0px", - "settings": { - "maxValue": 50, - "donutStartAngle": 90, - "showValue": true, - "showMinMax": true, - "gaugeWidthScale": 1, - "levelColors": [ - "#304ffe", - "#7e57c2", - "#ff4081", - "#d32f2f" - ], - "refreshAnimationType": "<>", - "refreshAnimationTime": 700, - "startAnimationType": "<>", - "startAnimationTime": 700, - "titleFont": { - "family": "RobotoDraft", - "size": 12, - "style": "normal", - "weight": "500" - }, - "labelFont": { - "family": "RobotoDraft", - "size": 8, - "style": "normal", - "weight": "500" - }, - "valueFont": { - "family": "Segment7Standard", - "style": "normal", - "weight": "500", - "size": 18 - }, - "minMaxFont": { - "family": "Segment7Standard", - "size": 12, - "style": "normal", - "weight": "500" - }, - "dashThickness": 1.5, - "decimals": 0, - "minValue": 0, - "units": "°C", - "gaugeColor": "#333333", - "neonGlowBrightness": 35, - "gaugeType": "donut", - "showTitle": false - }, - "title": "Temperature" - }, - "row": 0, - "col": 0, - "id": "03e06986-1c50-e9e4-267c-2bae930ad9a2" - }, - "88808eb1-d381-9970-c852-e3499df68bd8": { - "isSystemType": true, - "bundleAlias": "digital_gauges", - "typeAlias": "digital_vertical_bar", - "type": "latest", - "title": "New widget", - "sizeX": 3, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "dataKeys": [ - { - "name": "humidity", - "type": "timeseries", - "label": "humidity", - "color": "#2196f3", - "settings": {}, - "_hash": 0.9492802776509441 - } - ], - "name": "DHT11", - "entityAliasId": "63a93238-c13f-4403-4bcc-9ccc86bd6a62" - } - ], - "timewindow": { - "realtime": { - "timewindowMs": 60000 - } - }, - "showTitle": false, - "backgroundColor": "#000000", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "0px", - "settings": { - "maxValue": 100, - "donutStartAngle": 90, - "showValue": true, - "showMinMax": true, - "gaugeWidthScale": 0.75, - "levelColors": [ - "#3d5afe", - "#f44336" - ], - "refreshAnimationType": "<>", - "refreshAnimationTime": 700, - "startAnimationType": "<>", - "startAnimationTime": 700, - "titleFont": { - "family": "RobotoDraft", - "size": 12, - "style": "normal", - "weight": "500" - }, - "labelFont": { - "family": "RobotoDraft", - "size": 8, - "style": "normal", - "weight": "500" - }, - "valueFont": { - "family": "Segment7Standard", - "style": "normal", - "weight": "500", - "size": 14 - }, - "minMaxFont": { - "family": "Segment7Standard", - "size": 8, - "style": "normal", - "weight": "normal", - "color": "#cccccc" - }, - "neonGlowBrightness": 20, - "decimals": 0, - "showUnitTitle": true, - "gaugeColor": "#171a1c", - "gaugeType": "verticalBar", - "showTitle": false, - "minValue": 0, - "dashThickness": 1.2 - }, - "title": "Humidity" - }, - "row": 0, - "col": 5, - "id": "88808eb1-d381-9970-c852-e3499df68bd8" - } - }, - "states": { - "default": { - "name": "Default", - "root": true, - "layouts": { - "main": { - "widgets": { - "03e06986-1c50-e9e4-267c-2bae930ad9a2": { - "sizeX": 5, - "sizeY": 5, - "row": 0, - "col": 0 - }, - "88808eb1-d381-9970-c852-e3499df68bd8": { - "sizeX": 3, - "sizeY": 5, - "row": 0, - "col": 5 - } - }, - "gridSettings": { - "backgroundColor": "#eeeeee", - "color": "rgba(0,0,0,0.870588)", - "columns": 24, - "margins": [ - 10, - 10 - ], - "backgroundSizeMode": "100%" - } - } - } - } - }, - "entityAliases": { - "63a93238-c13f-4403-4bcc-9ccc86bd6a62": { - "id": "63a93238-c13f-4403-4bcc-9ccc86bd6a62", - "alias": "DHT11", - "filter": { - "type": "entityName", - "resolveMultiple": false, - "entityType": "DEVICE", - "entityNameFilter": "DHT11 Demo Device" - } - } - }, - "timewindow": { - "displayValue": "", - "selectedTab": 0, - "realtime": { - "interval": 1000, - "timewindowMs": 60000 - }, - "history": { - "historyType": 0, - "interval": 1000, - "timewindowMs": 60000, - "fixedTimewindow": { - "startTimeMs": 1498653790019, - "endTimeMs": 1498740190019 - } - }, - "aggregation": { - "type": "AVG", - "limit": 200 - } - }, - "settings": { - "stateControllerId": "default", - "showTitle": true, - "showDashboardsSelect": true, - "showEntitiesSelect": true, - "showDashboardTimewindow": true, - "showDashboardExport": true, - "toolbarAlwaysOpen": false - } - }, - "name": "Temperature & Humidity Demo Dashboard" -} \ No newline at end of file diff --git a/application/src/main/data/json/demo/rule_chains/thermostat_rule_chain.json b/application/src/main/data/json/demo/rule_chains/thermostat_rule_chain.json new file mode 100644 index 0000000000..6e22e90ca5 --- /dev/null +++ b/application/src/main/data/json/demo/rule_chains/thermostat_rule_chain.json @@ -0,0 +1,135 @@ +{ + "ruleChain": { + "additionalInfo": null, + "name": "Thermostat", + "firstRuleNodeId": null, + "root": false, + "debugMode": false, + "configuration": null + }, + "metadata": { + "firstNodeIndex": 6, + "nodes": [ + { + "additionalInfo": { + "layoutX": 824, + "layoutY": 156 + }, + "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", + "name": "Save Timeseries", + "debugMode": false, + "configuration": { + "defaultTTL": 0 + } + }, + { + "additionalInfo": { + "layoutX": 825, + "layoutY": 52 + }, + "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", + "name": "Save Client Attributes", + "debugMode": false, + "configuration": { + "scope": "CLIENT_SCOPE", + "notifyDevice": "false" + } + }, + { + "additionalInfo": { + "layoutX": 347, + "layoutY": 149 + }, + "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", + "name": "Message Type Switch", + "debugMode": false, + "configuration": { + "version": 0 + } + }, + { + "additionalInfo": { + "layoutX": 825, + "layoutY": 266 + }, + "type": "org.thingsboard.rule.engine.action.TbLogNode", + "name": "Log RPC from Device", + "debugMode": false, + "configuration": { + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" + } + }, + { + "additionalInfo": { + "layoutX": 825, + "layoutY": 379 + }, + "type": "org.thingsboard.rule.engine.action.TbLogNode", + "name": "Log Other", + "debugMode": false, + "configuration": { + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" + } + }, + { + "additionalInfo": { + "layoutX": 825, + "layoutY": 468 + }, + "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode", + "name": "RPC Call Request", + "debugMode": false, + "configuration": { + "timeoutInSeconds": 60 + } + }, + { + "additionalInfo": { + "description": "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type.", + "layoutX": 204, + "layoutY": 240 + }, + "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode", + "name": "Device Profile Node", + "debugMode": false, + "configuration": { + "persistAlarmRulesState": false, + "fetchAlarmRulesStateOnStart": false + } + } + ], + "connections": [ + { + "fromIndex": 6, + "toIndex": 2, + "type": "Success" + }, + { + "fromIndex": 2, + "toIndex": 4, + "type": "Other" + }, + { + "fromIndex": 2, + "toIndex": 1, + "type": "Post attributes" + }, + { + "fromIndex": 2, + "toIndex": 0, + "type": "Post telemetry" + }, + { + "fromIndex": 2, + "toIndex": 3, + "type": "RPC Request from Device" + }, + { + "fromIndex": 2, + "toIndex": 5, + "type": "RPC Request to Device" + } + ], + "ruleChainConnections": null + } +} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 2c6f2fa939..69f4e57c28 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -51,6 +51,7 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.query.BooleanFilterPredicate; import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValueSourceType; @@ -72,6 +73,7 @@ import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; @@ -127,6 +129,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Autowired private DeviceCredentialsService deviceCredentialsService; + @Autowired + private RuleChainService ruleChainService; + @Bean protected BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); @@ -266,6 +271,8 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { thermostatDeviceProfile.setTransportType(DeviceTransportType.DEFAULT); thermostatDeviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); thermostatDeviceProfile.setDescription("Thermostat device profile"); + thermostatDeviceProfile.setDefaultRuleChainId(ruleChainService.findTenantRuleChains( + demoTenant.getId(), new PageLink(1, 0, "Thermostat")).getData().get(0).getId()); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index 550e5f28bd..bd49089f40 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -212,10 +212,17 @@ public class InstallScripts { public void loadDemoRuleChains(TenantId tenantId) throws Exception { Path ruleChainsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, RULE_CHAINS_DIR); try { + JsonNode thermostatRuleChainJson = objectMapper.readTree(ruleChainsDir.resolve("thermostat_rule_chain.json").toFile()); + RuleChain thermostatRuleChain = objectMapper.treeToValue(thermostatRuleChainJson.get("ruleChain"), RuleChain.class); + RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(thermostatRuleChainJson.get("metadata"), RuleChainMetaData.class); + thermostatRuleChain.setTenantId(tenantId); + thermostatRuleChain = ruleChainService.saveRuleChain(thermostatRuleChain); + ruleChainMetaData.setRuleChainId(thermostatRuleChain.getId()); + ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), ruleChainMetaData); + JsonNode rootChainJson = objectMapper.readTree(ruleChainsDir.resolve("root_rule_chain.json").toFile()); RuleChain rootChain = objectMapper.treeToValue(rootChainJson.get("ruleChain"), RuleChain.class); RuleChainMetaData rootChainMetaData = objectMapper.treeToValue(rootChainJson.get("metadata"), RuleChainMetaData.class); - rootChain.setTenantId(tenantId); rootChain = ruleChainService.saveRuleChain(rootChain); rootChainMetaData.setRuleChainId(rootChain.getId()); From e19bd433ade3d00679d7a73035d73ca714370fca Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 25 Nov 2020 12:18:41 +0200 Subject: [PATCH 433/463] improvements asset validation for RE statistics --- .../thingsboard/server/dao/asset/BaseAssetService.java | 8 ++++++-- .../org/thingsboard/server/dao/sql/asset/JpaAssetDao.java | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java index 29eaad31fb..e8375d8065 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java @@ -78,6 +78,8 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ public static final String INCORRECT_PAGE_LINK = "Incorrect page link "; public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId "; public static final String INCORRECT_ASSET_ID = "Incorrect assetId "; + public static final String TB_SERVICE_QUEUE = "TbServiceQueue"; + @Autowired private AssetDao assetDao; @@ -329,8 +331,10 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ protected void validateCreate(TenantId tenantId, Asset asset) { DefaultTenantProfileConfiguration profileConfiguration = (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); - long maxAssets = profileConfiguration.getMaxAssets(); - validateNumberOfEntitiesPerTenant(tenantId, assetDao, maxAssets, EntityType.ASSET); + if (!TB_SERVICE_QUEUE.equals(asset.getType())) { + long maxAssets = profileConfiguration.getMaxAssets(); + validateNumberOfEntitiesPerTenant(tenantId, assetDao, maxAssets, EntityType.ASSET); + } } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java index fd73aa2481..b56a3eb3a9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java @@ -39,12 +39,13 @@ import java.util.Objects; import java.util.Optional; import java.util.UUID; +import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE; + /** * Created by Valerii Sosliuk on 5/19/2017. */ @Component public class JpaAssetDao extends JpaAbstractSearchTextDao implements AssetDao { - public static final String TB_SERVICE_QUEUE = "TbServiceQueue"; @Autowired private AssetRepository assetRepository; From c32a2ac06476aae36c15a8b36fcbae9078d8c14d Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 25 Nov 2020 14:26:26 +0200 Subject: [PATCH 434/463] load demo rule chains improvements --- .../demo/rule_chains/root_rule_chain.json | 135 ------------------ .../rule_chains/thermostat_rule_chain.json | 135 ------------------ .../server/controller/AssetController.java | 7 + .../service/install/InstallScripts.java | 18 +-- 4 files changed, 9 insertions(+), 286 deletions(-) delete mode 100644 application/src/main/data/json/demo/rule_chains/root_rule_chain.json delete mode 100644 application/src/main/data/json/demo/rule_chains/thermostat_rule_chain.json diff --git a/application/src/main/data/json/demo/rule_chains/root_rule_chain.json b/application/src/main/data/json/demo/rule_chains/root_rule_chain.json deleted file mode 100644 index a37b1fc865..0000000000 --- a/application/src/main/data/json/demo/rule_chains/root_rule_chain.json +++ /dev/null @@ -1,135 +0,0 @@ -{ - "ruleChain": { - "additionalInfo": null, - "name": "Root Rule Chain", - "firstRuleNodeId": null, - "root": true, - "debugMode": false, - "configuration": null - }, - "metadata": { - "firstNodeIndex": 6, - "nodes": [ - { - "additionalInfo": { - "layoutX": 824, - "layoutY": 156 - }, - "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", - "name": "Save Timeseries", - "debugMode": false, - "configuration": { - "defaultTTL": 0 - } - }, - { - "additionalInfo": { - "layoutX": 825, - "layoutY": 52 - }, - "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", - "name": "Save Client Attributes", - "debugMode": false, - "configuration": { - "scope": "CLIENT_SCOPE", - "notifyDevice": "false" - } - }, - { - "additionalInfo": { - "layoutX": 347, - "layoutY": 149 - }, - "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", - "name": "Message Type Switch", - "debugMode": false, - "configuration": { - "version": 0 - } - }, - { - "additionalInfo": { - "layoutX": 825, - "layoutY": 266 - }, - "type": "org.thingsboard.rule.engine.action.TbLogNode", - "name": "Log RPC from Device", - "debugMode": false, - "configuration": { - "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" - } - }, - { - "additionalInfo": { - "layoutX": 825, - "layoutY": 379 - }, - "type": "org.thingsboard.rule.engine.action.TbLogNode", - "name": "Log Other", - "debugMode": false, - "configuration": { - "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" - } - }, - { - "additionalInfo": { - "layoutX": 825, - "layoutY": 468 - }, - "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode", - "name": "RPC Call Request", - "debugMode": false, - "configuration": { - "timeoutInSeconds": 60 - } - }, - { - "additionalInfo": { - "description": "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type.", - "layoutX": 204, - "layoutY": 240 - }, - "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode", - "name": "Device Profile Node", - "debugMode": false, - "configuration": { - "persistAlarmRulesState": false, - "fetchAlarmRulesStateOnStart": false - } - } - ], - "connections": [ - { - "fromIndex": 6, - "toIndex": 2, - "type": "Success" - }, - { - "fromIndex": 2, - "toIndex": 4, - "type": "Other" - }, - { - "fromIndex": 2, - "toIndex": 1, - "type": "Post attributes" - }, - { - "fromIndex": 2, - "toIndex": 0, - "type": "Post telemetry" - }, - { - "fromIndex": 2, - "toIndex": 3, - "type": "RPC Request from Device" - }, - { - "fromIndex": 2, - "toIndex": 5, - "type": "RPC Request to Device" - } - ], - "ruleChainConnections": null - } -} \ No newline at end of file diff --git a/application/src/main/data/json/demo/rule_chains/thermostat_rule_chain.json b/application/src/main/data/json/demo/rule_chains/thermostat_rule_chain.json deleted file mode 100644 index 6e22e90ca5..0000000000 --- a/application/src/main/data/json/demo/rule_chains/thermostat_rule_chain.json +++ /dev/null @@ -1,135 +0,0 @@ -{ - "ruleChain": { - "additionalInfo": null, - "name": "Thermostat", - "firstRuleNodeId": null, - "root": false, - "debugMode": false, - "configuration": null - }, - "metadata": { - "firstNodeIndex": 6, - "nodes": [ - { - "additionalInfo": { - "layoutX": 824, - "layoutY": 156 - }, - "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", - "name": "Save Timeseries", - "debugMode": false, - "configuration": { - "defaultTTL": 0 - } - }, - { - "additionalInfo": { - "layoutX": 825, - "layoutY": 52 - }, - "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", - "name": "Save Client Attributes", - "debugMode": false, - "configuration": { - "scope": "CLIENT_SCOPE", - "notifyDevice": "false" - } - }, - { - "additionalInfo": { - "layoutX": 347, - "layoutY": 149 - }, - "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", - "name": "Message Type Switch", - "debugMode": false, - "configuration": { - "version": 0 - } - }, - { - "additionalInfo": { - "layoutX": 825, - "layoutY": 266 - }, - "type": "org.thingsboard.rule.engine.action.TbLogNode", - "name": "Log RPC from Device", - "debugMode": false, - "configuration": { - "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" - } - }, - { - "additionalInfo": { - "layoutX": 825, - "layoutY": 379 - }, - "type": "org.thingsboard.rule.engine.action.TbLogNode", - "name": "Log Other", - "debugMode": false, - "configuration": { - "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" - } - }, - { - "additionalInfo": { - "layoutX": 825, - "layoutY": 468 - }, - "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode", - "name": "RPC Call Request", - "debugMode": false, - "configuration": { - "timeoutInSeconds": 60 - } - }, - { - "additionalInfo": { - "description": "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type.", - "layoutX": 204, - "layoutY": 240 - }, - "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode", - "name": "Device Profile Node", - "debugMode": false, - "configuration": { - "persistAlarmRulesState": false, - "fetchAlarmRulesStateOnStart": false - } - } - ], - "connections": [ - { - "fromIndex": 6, - "toIndex": 2, - "type": "Success" - }, - { - "fromIndex": 2, - "toIndex": 4, - "type": "Other" - }, - { - "fromIndex": 2, - "toIndex": 1, - "type": "Post attributes" - }, - { - "fromIndex": 2, - "toIndex": 0, - "type": "Post telemetry" - }, - { - "fromIndex": 2, - "toIndex": 3, - "type": "RPC Request from Device" - }, - { - "fromIndex": 2, - "toIndex": 5, - "type": "RPC Request to Device" - } - ], - "ruleChainConnections": null - } -} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/controller/AssetController.java b/application/src/main/java/org/thingsboard/server/controller/AssetController.java index 2c28feebf2..dd0314e00e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AssetController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AssetController.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.asset.AssetSearchQuery; import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; @@ -51,6 +52,8 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE; + @RestController @TbCoreComponent @RequestMapping("/api") @@ -89,6 +92,10 @@ public class AssetController extends BaseController { @ResponseBody public Asset saveAsset(@RequestBody Asset asset) throws ThingsboardException { try { + if (TB_SERVICE_QUEUE.equals(asset.getType())) { + throw new ThingsboardException("Unable to save asset with type " + TB_SERVICE_QUEUE, ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + asset.setTenantId(getCurrentUser().getTenantId()); checkEntity(asset.getId(), asset, Resource.ASSET); diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index bd49089f40..5e6797eeb7 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -210,23 +210,9 @@ public class InstallScripts { public void loadDemoRuleChains(TenantId tenantId) throws Exception { - Path ruleChainsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, RULE_CHAINS_DIR); try { - JsonNode thermostatRuleChainJson = objectMapper.readTree(ruleChainsDir.resolve("thermostat_rule_chain.json").toFile()); - RuleChain thermostatRuleChain = objectMapper.treeToValue(thermostatRuleChainJson.get("ruleChain"), RuleChain.class); - RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(thermostatRuleChainJson.get("metadata"), RuleChainMetaData.class); - thermostatRuleChain.setTenantId(tenantId); - thermostatRuleChain = ruleChainService.saveRuleChain(thermostatRuleChain); - ruleChainMetaData.setRuleChainId(thermostatRuleChain.getId()); - ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), ruleChainMetaData); - - JsonNode rootChainJson = objectMapper.readTree(ruleChainsDir.resolve("root_rule_chain.json").toFile()); - RuleChain rootChain = objectMapper.treeToValue(rootChainJson.get("ruleChain"), RuleChain.class); - RuleChainMetaData rootChainMetaData = objectMapper.treeToValue(rootChainJson.get("metadata"), RuleChainMetaData.class); - rootChain.setTenantId(tenantId); - rootChain = ruleChainService.saveRuleChain(rootChain); - rootChainMetaData.setRuleChainId(rootChain.getId()); - ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), rootChainMetaData); + createDefaultRuleChains(tenantId); + createDefaultRuleChain(tenantId, "Thermostat"); } catch (Exception e) { log.error("Unable to load dashboard from json", e); throw new RuntimeException("Unable to load dashboard from json", e); From c4b36b0c6f81913b2bb7fc4bfe789295813a2ed5 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 25 Nov 2020 18:12:54 +0200 Subject: [PATCH 435/463] Fix create database in docker images. No more Californium.properties --- .../server/transport/coap/CoapTransportService.java | 3 ++- msa/tb/docker-cassandra/start-db.sh | 6 +++++- msa/tb/docker-postgres/start-db.sh | 8 ++++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java index afb83f513f..a6813e488d 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.CoapResource; import org.eclipse.californium.core.CoapServer; import org.eclipse.californium.core.network.CoapEndpoint; +import org.eclipse.californium.core.network.config.NetworkConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; @@ -46,7 +47,7 @@ public class CoapTransportService { public void init() throws UnknownHostException { log.info("Starting CoAP transport..."); log.info("Starting CoAP transport server"); - this.server = new CoapServer(); + this.server = new CoapServer(NetworkConfig.createStandardWithoutFile()); createResources(); InetAddress addr = InetAddress.getByName(coapTransportContext.getHost()); InetSocketAddress sockAddr = new InetSocketAddress(addr, coapTransportContext.getPort()); diff --git a/msa/tb/docker-cassandra/start-db.sh b/msa/tb/docker-cassandra/start-db.sh index fa80f6db57..8491a979d2 100644 --- a/msa/tb/docker-cassandra/start-db.sh +++ b/msa/tb/docker-cassandra/start-db.sh @@ -27,7 +27,11 @@ fi exec setsid nohup postgres >> ${PGLOG}/postgres.log 2>&1 & if [ ! -f ${firstlaunch} ]; then - psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard" + sleep 2 + while ! psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard" + do + sleep 1 + done fi cassandra_data_dir=${CASSANDRA_DATA} diff --git a/msa/tb/docker-postgres/start-db.sh b/msa/tb/docker-postgres/start-db.sh index 6119daf2f9..618dae77da 100644 --- a/msa/tb/docker-postgres/start-db.sh +++ b/msa/tb/docker-postgres/start-db.sh @@ -27,5 +27,9 @@ fi exec setsid nohup postgres >> ${PGLOG}/postgres.log 2>&1 & if [ ! -f ${firstlaunch} ]; then - psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard" -fi + sleep 2 + while ! psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard" + do + sleep 1 + done +fi \ No newline at end of file From 8515b6d6e011b548bdfdb478e2b1a95740334d12 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 25 Nov 2020 18:19:33 +0200 Subject: [PATCH 436/463] alarm rules improvements --- .../service/install/DefaultSystemDataLoaderService.java | 6 +++--- .../common/data/device/profile/DeviceProfileAlarm.java | 5 ++--- .../rule/engine/profile/TbDeviceProfileNodeTest.java | 9 ++------- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 69f4e57c28..c51420c8e0 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -82,7 +82,7 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService; import java.util.Arrays; import java.util.Collections; -import java.util.LinkedHashMap; +import java.util.TreeMap; @Service @Profile("install") @@ -311,7 +311,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { temperatureCondition.setCondition(Arrays.asList(temperatureAlarmFlagAttributeFilter, temperatureTimeseriesFilter)); temperatureRule.setAlarmDetails("Current temperature = ${temperature}"); temperatureRule.setCondition(temperatureCondition); - highTemperature.setCreateRules(new LinkedHashMap<>(Collections.singletonMap(AlarmSeverity.MAJOR, temperatureRule))); + highTemperature.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.MAJOR, temperatureRule))); AlarmRule clearTemperatureRule = new AlarmRule(); AlarmCondition clearTemperatureCondition = new AlarmCondition(); @@ -362,7 +362,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { humidityRule.setCondition(humidityCondition); humidityRule.setAlarmDetails("Current humidity = ${humidity}"); - lowHumidity.setCreateRules(new LinkedHashMap<>(Collections.singletonMap(AlarmSeverity.MINOR, humidityRule))); + lowHumidity.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.MINOR, humidityRule))); AlarmRule clearHumidityRule = new AlarmRule(); AlarmCondition clearHumidityCondition = new AlarmCondition(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java index 17035f337b..3b6acce6f3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java @@ -18,9 +18,8 @@ package org.thingsboard.server.common.data.device.profile; import lombok.Data; import org.thingsboard.server.common.data.alarm.AlarmSeverity; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; +import java.util.TreeMap; @Data public class DeviceProfileAlarm { @@ -28,7 +27,7 @@ public class DeviceProfileAlarm { private String id; private String alarmType; - private LinkedHashMap createRules; + private TreeMap createRules; private AlarmRule clearRule; // Hidden in advanced settings diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java index ae6bd7ebbd..39c16247fa 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java @@ -23,17 +23,13 @@ import org.junit.runner.RunWith; import org.mockito.AdditionalAnswers; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; -import org.mockito.stubbing.Answer; -import org.springframework.util.StringUtils; import org.thingsboard.rule.engine.api.RuleEngineAlarmService; import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.device.profile.AlarmCondition; import org.thingsboard.server.common.data.device.profile.AlarmRule; @@ -52,11 +48,10 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import java.util.Collections; -import java.util.LinkedHashMap; +import java.util.TreeMap; import java.util.UUID; import static org.mockito.Mockito.verify; @@ -140,7 +135,7 @@ public class TbDeviceProfileNodeTest { DeviceProfileAlarm dpa = new DeviceProfileAlarm(); dpa.setId("highTemperatureAlarmID"); dpa.setAlarmType("highTemperatureAlarm"); - dpa.setCreateRules(new LinkedHashMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); KeyFilter lowTempFilter = new KeyFilter(); lowTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); From 474401bb67ce28a34bf6ab01f01e765659efd333 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 26 Nov 2020 11:15:54 +0200 Subject: [PATCH 437/463] Fix base url for dashboard public link --- ui-ngx/src/app/core/http/dashboard.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/core/http/dashboard.service.ts b/ui-ngx/src/app/core/http/dashboard.service.ts index 42ba3834c9..61435747b3 100644 --- a/ui-ngx/src/app/core/http/dashboard.service.ts +++ b/ui-ngx/src/app/core/http/dashboard.service.ts @@ -130,7 +130,7 @@ export class DashboardService { const publicCustomerId = publicCustomers[0].customerId.id; let url = this.window.location.protocol + '//' + this.window.location.hostname; const port = this.window.location.port; - if (port !== '80' && port !== '443') { + if (port && port.length > 0 && port !== '80' && port !== '443') { url += ':' + port; } url += `/dashboard/${dashboard.id.id}?publicId=${publicCustomerId}`; From ae35892ba20a99e4ede72c06f33e4e670b932093 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 26 Nov 2020 11:54:11 +0200 Subject: [PATCH 438/463] Copy MQTT Credentials --- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 5ee61fa277..06a20e939f 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -843,7 +843,7 @@ "details": "Details", "copyId": "Copy device Id", "copyAccessToken": "Copy access token", - "copy-mqtt-authentication": "Copy MQTT authentication", + "copy-mqtt-authentication": "Copy MQTT credentials", "idCopiedMessage": "Device Id has been copied to clipboard", "accessTokenCopiedMessage": "Device access token has been copied to clipboard", "mqtt-authentication-copied-message": "Device MQTT authentication has been copied to clipboard", From 52122d8047fb61cf6d23f1ff4a079582c2231f21 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 26 Nov 2020 12:09:35 +0200 Subject: [PATCH 439/463] Add send sms rule node help link --- ui-ngx/src/app/shared/models/constants.ts | 1 + ui-ngx/src/app/shared/models/rule-node.models.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index 049ba03270..679fc8d77b 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -108,6 +108,7 @@ export const HelpLinks = { ruleNodeRabbitMq: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/external-nodes/#rabbitmq-node', ruleNodeRestApiCall: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/external-nodes/#rest-api-call-node', ruleNodeSendEmail: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/external-nodes/#send-email-node', + ruleNodeSendSms: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/external-nodes/#send-sms-node', tenants: helpBaseUrl + '/docs/user-guide/ui/tenants', tenantProfiles: helpBaseUrl + '/docs/user-guide/ui/tenant-profiles', customers: helpBaseUrl + '/docs/user-guide/ui/customers', diff --git a/ui-ngx/src/app/shared/models/rule-node.models.ts b/ui-ngx/src/app/shared/models/rule-node.models.ts index b9494fb1c4..0312ad3198 100644 --- a/ui-ngx/src/app/shared/models/rule-node.models.ts +++ b/ui-ngx/src/app/shared/models/rule-node.models.ts @@ -416,7 +416,8 @@ const ruleNodeClazzHelpLinkMap = { 'org.thingsboard.rule.engine.mqtt.azure.TbAzureIotHubNode': 'ruleNodeAzureIotHub', 'org.thingsboard.rule.engine.rabbitmq.TbRabbitMqNode': 'ruleNodeRabbitMq', 'org.thingsboard.rule.engine.rest.TbRestApiCallNode': 'ruleNodeRestApiCall', - 'org.thingsboard.rule.engine.mail.TbSendEmailNode': 'ruleNodeSendEmail' + 'org.thingsboard.rule.engine.mail.TbSendEmailNode': 'ruleNodeSendEmail', + 'org.thingsboard.rule.engine.sms.TbSendSmsNode': 'ruleNodeSendSms' }; export function getRuleNodeHelpLink(component: RuleNodeComponentDescriptor): string { From 8ac0a6850e53c8e292e424b2c247653090030207 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Fri, 27 Nov 2020 11:29:38 +0200 Subject: [PATCH 440/463] Spaces removal on provisioning request --- .../server/service/device/DeviceProvisionServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index cfb51b4627..2a2f1bd03a 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -112,7 +112,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { public ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) { String provisionRequestKey = provisionRequest.getCredentials().getProvisionDeviceKey(); String provisionRequestSecret = provisionRequest.getCredentials().getProvisionDeviceSecret(); - if (provisionRequest.getDeviceName() != null) { + if (!StringUtils.isEmpty(provisionRequest.getDeviceName())) { provisionRequest.setDeviceName(provisionRequest.getDeviceName().trim()); if (StringUtils.isEmpty(provisionRequest.getDeviceName())) { log.warn("Provision request contains empty device name!"); From 8f8814d1678df96236c5a2b3f7b44d73dbbd3c67 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 26 Nov 2020 15:58:43 +0200 Subject: [PATCH 441/463] moved sms configs to common package, rest client improvements --- .../server/controller/AdminController.java | 2 +- .../server/service/sms/DefaultSmsSenderFactory.java | 6 +++--- .../server/service/sms/DefaultSmsService.java | 4 ++-- .../server/service/sms/aws/AwsSmsSender.java | 2 +- .../server/service/sms/twilio/TwilioSmsSender.java | 2 +- .../sms/config/AwsSnsSmsProviderConfiguration.java | 2 +- .../data}/sms/config/SmsProviderConfiguration.java | 2 +- .../common/data}/sms/config/SmsProviderType.java | 2 +- .../common/data}/sms/config/TestSmsRequest.java | 2 +- .../sms/config/TwilioSmsProviderConfiguration.java | 2 +- .../java/org/thingsboard/rest/client/RestClient.java | 11 ++++++++++- .../org/thingsboard/rule/engine/api/SmsService.java | 2 +- .../rule/engine/api/sms/SmsSenderFactory.java | 2 +- .../rule/engine/sms/TbSendSmsNodeConfiguration.java | 2 +- 14 files changed, 26 insertions(+), 17 deletions(-) rename {rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api => common/data/src/main/java/org/thingsboard/server/common/data}/sms/config/AwsSnsSmsProviderConfiguration.java (94%) rename {rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api => common/data/src/main/java/org/thingsboard/server/common/data}/sms/config/SmsProviderConfiguration.java (95%) rename {rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api => common/data/src/main/java/org/thingsboard/server/common/data}/sms/config/SmsProviderType.java (92%) rename {rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api => common/data/src/main/java/org/thingsboard/server/common/data}/sms/config/TestSmsRequest.java (93%) rename {rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api => common/data/src/main/java/org/thingsboard/server/common/data}/sms/config/TwilioSmsProviderConfiguration.java (94%) diff --git a/application/src/main/java/org/thingsboard/server/controller/AdminController.java b/application/src/main/java/org/thingsboard/server/controller/AdminController.java index c980bff796..52283029fe 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AdminController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AdminController.java @@ -26,7 +26,7 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.SmsService; -import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; +import org.thingsboard.server.common.data.sms.config.TestSmsRequest; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.UpdateMessage; import org.thingsboard.server.common.data.exception.ThingsboardException; diff --git a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java index 3452bcf1ac..d319276c40 100644 --- a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java +++ b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java @@ -18,9 +18,9 @@ package org.thingsboard.server.service.sms; import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.sms.SmsSender; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; -import org.thingsboard.rule.engine.api.sms.config.AwsSnsSmsProviderConfiguration; -import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; -import org.thingsboard.rule.engine.api.sms.config.TwilioSmsProviderConfiguration; +import org.thingsboard.server.common.data.sms.config.AwsSnsSmsProviderConfiguration; +import org.thingsboard.server.common.data.sms.config.SmsProviderConfiguration; +import org.thingsboard.server.common.data.sms.config.TwilioSmsProviderConfiguration; import org.thingsboard.server.service.sms.aws.AwsSmsSender; import org.thingsboard.server.service.sms.twilio.TwilioSmsSender; diff --git a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java index 7f61476cd0..2a8b2ef913 100644 --- a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java +++ b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java @@ -22,8 +22,8 @@ import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.sms.SmsSender; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; -import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; -import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; +import org.thingsboard.server.common.data.sms.config.SmsProviderConfiguration; +import org.thingsboard.server.common.data.sms.config.TestSmsRequest; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; diff --git a/application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java b/application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java index 98a8556a5d..c6cea69540 100644 --- a/application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java +++ b/application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java @@ -23,7 +23,7 @@ import com.amazonaws.services.sns.AmazonSNSClient; import com.amazonaws.services.sns.model.PublishRequest; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.thingsboard.rule.engine.api.sms.config.AwsSnsSmsProviderConfiguration; +import org.thingsboard.server.common.data.sms.config.AwsSnsSmsProviderConfiguration; import org.thingsboard.rule.engine.api.sms.exception.SmsException; import org.thingsboard.rule.engine.api.sms.exception.SmsSendException; import org.thingsboard.server.service.sms.AbstractSmsSender; diff --git a/application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java b/application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java index 072b2b2ff7..7af65cfc48 100644 --- a/application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java +++ b/application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java @@ -19,7 +19,7 @@ import com.twilio.http.TwilioRestClient; import com.twilio.rest.api.v2010.account.Message; import com.twilio.type.PhoneNumber; import org.apache.commons.lang3.StringUtils; -import org.thingsboard.rule.engine.api.sms.config.TwilioSmsProviderConfiguration; +import org.thingsboard.server.common.data.sms.config.TwilioSmsProviderConfiguration; import org.thingsboard.rule.engine.api.sms.exception.SmsException; import org.thingsboard.rule.engine.api.sms.exception.SmsSendException; import org.thingsboard.server.service.sms.AbstractSmsSender; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/AwsSnsSmsProviderConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/AwsSnsSmsProviderConfiguration.java similarity index 94% rename from rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/AwsSnsSmsProviderConfiguration.java rename to common/data/src/main/java/org/thingsboard/server/common/data/sms/config/AwsSnsSmsProviderConfiguration.java index 74cd5486a1..2acad17a0f 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/AwsSnsSmsProviderConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/AwsSnsSmsProviderConfiguration.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.api.sms.config; +package org.thingsboard.server.common.data.sms.config; import lombok.Data; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderConfiguration.java similarity index 95% rename from rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderConfiguration.java rename to common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderConfiguration.java index 8feec6ff40..8b1984e9b4 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderConfiguration.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.api.sms.config; +package org.thingsboard.server.common.data.sms.config; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderType.java b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderType.java similarity index 92% rename from rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderType.java rename to common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderType.java index e698dd4783..f60d917cf1 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/SmsProviderType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderType.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.api.sms.config; +package org.thingsboard.server.common.data.sms.config; public enum SmsProviderType { AWS_SNS, diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TestSmsRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TestSmsRequest.java similarity index 93% rename from rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TestSmsRequest.java rename to common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TestSmsRequest.java index 3d00f9e580..23c44be48c 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TestSmsRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TestSmsRequest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.api.sms.config; +package org.thingsboard.server.common.data.sms.config; import lombok.Data; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TwilioSmsProviderConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TwilioSmsProviderConfiguration.java similarity index 94% rename from rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TwilioSmsProviderConfiguration.java rename to common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TwilioSmsProviderConfiguration.java index 2e0585e466..6f9b226711 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/config/TwilioSmsProviderConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TwilioSmsProviderConfiguration.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.api.sms.config; +package org.thingsboard.server.common.data.sms.config; import lombok.Data; diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index 0a92d4b1a8..e79bb2b713 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -109,6 +109,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; +import org.thingsboard.server.common.data.sms.config.TestSmsRequest; import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetsBundle; @@ -218,7 +219,11 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } public void sendTestMail(AdminSettings adminSettings) { - restTemplate.postForEntity(baseURL + "/api/admin/settings/testMail", adminSettings, AdminSettings.class); + restTemplate.postForLocation(baseURL + "/api/admin/settings/testMail", adminSettings); + } + + public void sendTestSms(TestSmsRequest testSmsRequest) { + restTemplate.postForLocation(baseURL + "/api/admin/settings/testSms", testSmsRequest); } public Optional getSecuritySettings() { @@ -1714,6 +1719,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return restTemplate.postForEntity(baseURL + "/api/oauth2/config", oauth2Params, OAuth2ClientsParams.class).getBody(); } + public String getLoginProcessingUrl() { + return restTemplate.getForEntity(baseURL + "/api/oauth2/loginProcessingUrl", String.class).getBody(); + } + public void handleOneWayDeviceRPCRequest(DeviceId deviceId, JsonNode requestBody) { restTemplate.postForLocation(baseURL + "/api/plugins/rpc/oneway/{deviceId}", requestBody, deviceId.getId()); } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java index fb34383a9d..01b82931a6 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java @@ -15,7 +15,7 @@ */ package org.thingsboard.rule.engine.api; -import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; +import org.thingsboard.server.common.data.sms.config.TestSmsRequest; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSenderFactory.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSenderFactory.java index 39daa48983..5930601c22 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSenderFactory.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSenderFactory.java @@ -15,7 +15,7 @@ */ package org.thingsboard.rule.engine.api.sms; -import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; +import org.thingsboard.server.common.data.sms.config.SmsProviderConfiguration; public interface SmsSenderFactory { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNodeConfiguration.java index 14720aa342..7bbe1d9217 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNodeConfiguration.java @@ -17,7 +17,7 @@ package org.thingsboard.rule.engine.sms; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; -import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; +import org.thingsboard.server.common.data.sms.config.SmsProviderConfiguration; @Data public class TbSendSmsNodeConfiguration implements NodeConfiguration { From 0fa0a6f1d462a5879719a25b5170070bed2223ba Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 27 Nov 2020 15:34:26 +0200 Subject: [PATCH 442/463] fixed hourly update api usage state --- .../server/service/apiusage/DefaultTbApiUsageStateService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 330ecc23f5..1a7a296d8f 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -166,7 +166,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { long newValue = tenantState.add(recordKey, kvProto.getValue()); updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.getApiCountKey(), newValue))); long newHourlyValue = tenantState.addToHourly(recordKey, kvProto.getValue()); - updatedEntries.add(new BasicTsKvEntry(hourTs, new LongDataEntry(recordKey.getApiCountKey() + HOURLY, newHourlyValue))); + updatedEntries.add(new BasicTsKvEntry(newHourTs, new LongDataEntry(recordKey.getApiCountKey() + HOURLY, newHourlyValue))); apiFeatures.add(recordKey.getApiFeature()); } result = tenantState.checkStateUpdatedDueToThreshold(apiFeatures); From d9a9690f6ed5d3f8f6b32f9d82d35f494f0f6e6d Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 27 Nov 2020 17:35:19 +0200 Subject: [PATCH 443/463] Ability to embed dashboard page. Implement Api Usage page. --- ui-ngx/src/app/core/services/menu.models.ts | 1 + ui-ngx/src/app/core/services/menu.service.ts | 13 + ui-ngx/src/app/core/services/utils.service.ts | 35 +- .../widget/action/custom-action.models.ts | 2 +- .../home/menu/menu-link.component.html | 2 +- .../api-usage/api-usage-routing.module.ts | 41 + .../pages/api-usage/api-usage.component.html | 18 + .../pages/api-usage/api-usage.component.scss | 20 + .../pages/api-usage/api-usage.component.ts | 41 + .../home/pages/api-usage/api-usage.module.ts | 38 + .../home/pages/api-usage/api_usage_json.raw | 4343 +++++++++++++++++ .../dashboard/dashboard-page.component.html | 2 +- .../dashboard/dashboard-page.component.ts | 31 +- .../home/pages/dashboard/dashboard.module.ts | 3 + .../states/state-controller.component.ts | 2 +- .../modules/home/pages/home-pages.module.ts | 2 + ui-ngx/src/app/shared/models/constants.ts | 1 + .../assets/locale/locale.constant-en_US.json | 3 + 18 files changed, 4584 insertions(+), 14 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/pages/api-usage/api-usage-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/api-usage/api-usage.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/api-usage/api_usage_json.raw diff --git a/ui-ngx/src/app/core/services/menu.models.ts b/ui-ngx/src/app/core/services/menu.models.ts index 028a0f5380..ad29c17258 100644 --- a/ui-ngx/src/app/core/services/menu.models.ts +++ b/ui-ngx/src/app/core/services/menu.models.ts @@ -23,6 +23,7 @@ export interface MenuSection extends HasUUID{ type: MenuSectionType; path: string; icon: string; + notExact?: boolean; isMdiIcon?: boolean; height?: string; pages?: Array; diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index e30d993d92..6184050142 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -288,6 +288,14 @@ export class MenuService { type: 'link', path: '/auditLogs', icon: 'track_changes' + }, + { + id: guid(), + name: 'api-usage.api-usage', + type: 'link', + path: '/usage', + icon: 'insert_chart', + notExact: true } ); return sections; @@ -374,6 +382,11 @@ export class MenuService { name: 'audit-log.audit-logs', icon: 'track_changes', path: '/auditLogs' + }, + { + name: 'api-usage.api-usage', + icon: 'insert_chart', + path: '/usage' } ] } diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index 3613c0f484..463c455b55 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -26,12 +26,12 @@ import { deleteNullProperties, guid, isDefined, - isDefinedAndNotNull, + isDefinedAndNotNull, isString, isUndefined } from '@core/utils'; import { WindowMessage } from '@shared/models/window-message.model'; import { TranslateService } from '@ngx-translate/core'; -import { customTranslationsPrefix } from '@app/shared/models/constants'; +import { customTranslationsPrefix, i18nPrefix } from '@app/shared/models/constants'; import { DataKey, Datasource, DatasourceType, KeyInfo } from '@shared/models/widget.models'; import { EntityType } from '@shared/models/entity-type.models'; import { DataKeyType } from '@app/shared/models/telemetry/telemetry.models'; @@ -42,6 +42,8 @@ import jsonSchemaDefaults from 'json-schema-defaults'; import materialIconsCodepoints from '!raw-loader!material-design-icons/iconfont/codepoints'; import { Observable, of, ReplaySubject } from 'rxjs'; +const i18nRegExp = new RegExp(`{${i18nPrefix}:[^{}]+}`, 'g'); + const predefinedFunctions: { [func: string]: string } = { Sin: 'return Math.round(1000*Math.sin(time/5000));', Cos: 'return Math.round(1000*Math.cos(time/5000));', @@ -107,8 +109,8 @@ export class UtilsService { materialIcons: Array = []; constructor(@Inject(WINDOW) private window: Window, - private zone: NgZone, - private translate: TranslateService) { + private zone: NgZone, + private translate: TranslateService) { let frame: Element = null; try { frame = window.frameElement; @@ -221,8 +223,31 @@ export class UtilsService { } public customTranslation(translationValue: string, defaultValue: string): string { + if (translationValue && isString(translationValue)) { + if (translationValue.includes(`{${i18nPrefix}`)) { + const matches = translationValue.match(i18nRegExp); + let result = translationValue; + for (const match of matches) { + const translationId = match.substring(6, match.length - 1); + result = result.replace(match, this.doTranslate(translationId, match)); + } + return result; + } else { + return this.doTranslate(translationValue, defaultValue, customTranslationsPrefix); + } + } else { + return translationValue; + } + } + + private doTranslate(translationValue: string, defaultValue: string, prefix?: string): string { let result: string; - const translationId = customTranslationsPrefix + translationValue; + let translationId; + if (prefix) { + translationId = prefix + translationValue; + } else { + translationId = translationValue; + } const translation = this.translate.instant(translationId); if (translation !== translationId) { result = translation + ''; diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-action.models.ts b/ui-ngx/src/app/modules/home/components/widget/action/custom-action.models.ts index 6539780e02..e15fc7f0eb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/custom-action.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-action.models.ts @@ -71,7 +71,7 @@ const customPrettyActionCompletions: TbEditorCompletions = { } }, ...customActionCompletions -} +}; export const CustomActionEditorCompleter = new TbEditorCompleter(customActionCompletions); export const CustomPrettyActionEditorCompleter = new TbEditorCompleter(customPrettyActionCompletions); diff --git a/ui-ngx/src/app/modules/home/menu/menu-link.component.html b/ui-ngx/src/app/modules/home/menu/menu-link.component.html index 034d92aa70..3ece859be1 100644 --- a/ui-ngx/src/app/modules/home/menu/menu-link.component.html +++ b/ui-ngx/src/app/modules/home/menu/menu-link.component.html @@ -15,7 +15,7 @@ limitations under the License. --> - + {{section.icon}} {{section.name | translate}} diff --git a/ui-ngx/src/app/modules/home/pages/api-usage/api-usage-routing.module.ts b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage-routing.module.ts new file mode 100644 index 0000000000..b6c6906c06 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage-routing.module.ts @@ -0,0 +1,41 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { Authority } from '@shared/models/authority.enum'; +import { ApiUsageComponent } from '@home/pages/api-usage/api-usage.component'; + +const routes: Routes = [ + { + path: 'usage', + component: ApiUsageComponent, + data: { + auth: [Authority.TENANT_ADMIN], + title: 'api-usage.api-usage', + breadcrumb: { + label: 'api-usage.api-usage', + icon: 'insert_chart' + } + } + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class ApiUsageRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.html b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.html new file mode 100644 index 0000000000..d7b188fa23 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.html @@ -0,0 +1,18 @@ + + diff --git a/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.scss b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.scss new file mode 100644 index 0000000000..342433682c --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.scss @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +:host { + width: 100%; + height: 100%; +} diff --git a/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.ts b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.ts new file mode 100644 index 0000000000..026d81fe27 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.ts @@ -0,0 +1,41 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { PageComponent } from '@shared/components/page.component'; +import apiUsageDashboardJson from '!raw-loader!./api_usage_json.raw'; +import { Dashboard } from '@shared/models/dashboard.models'; + +@Component({ + selector: 'tb-api-usage', + templateUrl: './api-usage.component.html', + styleUrls: ['./api-usage.component.scss'] +}) +export class ApiUsageComponent extends PageComponent implements OnInit { + + apiUsageDashboard: Dashboard; + + constructor(protected store: Store) { + super(store); + } + + ngOnInit() { + this.apiUsageDashboard = JSON.parse(apiUsageDashboardJson); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.module.ts b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.module.ts new file mode 100644 index 0000000000..ba6ec77d44 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.module.ts @@ -0,0 +1,38 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@app/shared/shared.module'; +import { HomeComponentsModule } from '@modules/home/components/home-components.module'; +import { ApiUsageComponent } from '@home/pages/api-usage/api-usage.component'; +import { DashboardModule } from '@home/pages/dashboard/dashboard.module'; +import { ApiUsageRoutingModule } from '@home/pages/api-usage/api-usage-routing.module'; + +@NgModule({ + declarations: + [ + ApiUsageComponent + ], + imports: [ + CommonModule, + SharedModule, + HomeComponentsModule, + DashboardModule, + ApiUsageRoutingModule + ] +}) +export class ApiUsageModule { } diff --git a/ui-ngx/src/app/modules/home/pages/api-usage/api_usage_json.raw b/ui-ngx/src/app/modules/home/pages/api-usage/api_usage_json.raw new file mode 100644 index 0000000000..97afee9d16 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/api-usage/api_usage_json.raw @@ -0,0 +1,4343 @@ +{ + "title": "Api Usage", + "configuration": { + "description": "", + "widgets": { + "fd6df872-2ddf-0921-3929-2e7f55062fad": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "html_value_card", + "type": "latest", + "title": "New widget", + "sizeX": 7.5, + "sizeY": 3, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "jsExecutionApiState", + "type": "timeseries", + "label": "apiState", + "color": "#2196f3", + "settings": {}, + "_hash": 0.8830669138660703, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "jsExecutionLimit", + "type": "timeseries", + "label": "limit", + "color": "#4caf50", + "settings": {}, + "_hash": 0.5463603803546802, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "jsExecutionCount", + "type": "timeseries", + "label": "count", + "color": "#f44336", + "settings": {}, + "_hash": 0.5564241862015964, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "jsExecutionApiState", + "type": "timeseries", + "label": "apiStateClass", + "color": "#ffc107", + "settings": {}, + "_hash": 0.8737107059960671, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value ? value.toLowerCase() : '';" + }, + { + "name": "jsExecutionApiState", + "type": "timeseries", + "label": "cardId", + "color": "#607d8b", + "settings": {}, + "_hash": 0.051659774305067296, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return (Math.random()*100000).toFixed(0);" + }, + { + "name": "jsExecutionApiState", + "type": "timeseries", + "label": "title", + "color": "#9c27b0", + "settings": {}, + "_hash": 0.7673280949238444, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"JavaScript functions\";" + } + ] + } + ], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "#666666", + "padding": "0", + "settings": { + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 24px;\n font-size: 20px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n", + "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
" + }, + "title": "JavaScript functions", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": { + "cursor": "default" + }, + "titleStyle": { + "fontSize": "20px", + "fontWeight": 400 + }, + "useDashboardTimewindow": true, + "showLegend": false, + "actions": { + "elementClick": [ + { + "name": "javascript_functions_details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "javascript_functions", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "d4961bea-84de-e1af-e50f-666b98d34cd5" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "" + }, + "row": 0, + "col": 0, + "id": "fd6df872-2ddf-0921-3929-2e7f55062fad" + }, + "7e235874-461b-e7c2-2fdd-d8762a020773": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "html_value_card", + "type": "latest", + "title": "New widget", + "sizeX": 7.5, + "sizeY": 3, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "dbApiState", + "type": "timeseries", + "label": "apiState", + "color": "#2196f3", + "settings": {}, + "_hash": 0.8830669138660703, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "storageDataPointsLimit", + "type": "timeseries", + "label": "limit", + "color": "#4caf50", + "settings": {}, + "_hash": 0.5463603803546802, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "storageDataPointsCount", + "type": "timeseries", + "label": "count", + "color": "#f44336", + "settings": {}, + "_hash": 0.5564241862015964, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "dbApiState", + "type": "timeseries", + "label": "apiStateClass", + "color": "#ffc107", + "settings": {}, + "_hash": 0.8737107059960671, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value ? value.toLowerCase() : '';" + }, + { + "name": "dbApiState", + "type": "timeseries", + "label": "cardId", + "color": "#607d8b", + "settings": {}, + "_hash": 0.051659774305067296, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return (Math.random()*100000).toFixed(0);" + }, + { + "name": "dbApiState", + "type": "timeseries", + "label": "title", + "color": "#9c27b0", + "settings": {}, + "_hash": 0.6301889725474652, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"Telemetry persistence\";" + } + ] + } + ], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "#666666", + "padding": "0", + "settings": { + "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 24px;\n font-size: 20px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n" + }, + "title": "Telemetry persistence", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": { + "cursor": "default" + }, + "titleStyle": { + "fontSize": "20px", + "fontWeight": 400 + }, + "useDashboardTimewindow": true, + "showLegend": false, + "actions": { + "elementClick": [ + { + "name": "telemetry_persistence_details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "telemetry_persistence", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "6248831c-5b3f-8879-8548-afcf43f10610" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "" + }, + "row": 0, + "col": 0, + "id": "7e235874-461b-e7c2-2fdd-d8762a020773" + }, + "08545554-a0e8-05c7-66df-6000cfeff8a4": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "html_value_card", + "type": "latest", + "title": "New widget", + "sizeX": 7.5, + "sizeY": 3, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "ruleEngineApiState", + "type": "timeseries", + "label": "apiState", + "color": "#2196f3", + "settings": {}, + "_hash": 0.8830669138660703, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "ruleEngineExecutionLimit", + "type": "timeseries", + "label": "limit", + "color": "#4caf50", + "settings": {}, + "_hash": 0.5463603803546802, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "ruleEngineExecutionCount", + "type": "timeseries", + "label": "count", + "color": "#f44336", + "settings": {}, + "_hash": 0.5564241862015964, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "ruleEngineApiState", + "type": "timeseries", + "label": "apiStateClass", + "color": "#ffc107", + "settings": {}, + "_hash": 0.8737107059960671, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value ? value.toLowerCase() : '';" + }, + { + "name": "ruleEngineApiState", + "type": "timeseries", + "label": "cardId", + "color": "#607d8b", + "settings": {}, + "_hash": 0.051659774305067296, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return (Math.random()*100000).toFixed(0);" + }, + { + "name": "ruleEngineApiState", + "type": "timeseries", + "label": "title", + "color": "#9c27b0", + "settings": {}, + "_hash": 0.3551317421302518, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"Rule Engine execution\";" + } + ] + } + ], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "#666666", + "padding": "0", + "settings": { + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 24px;\n font-size: 20px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n", + "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n \n
\n
" + }, + "title": "Rule Engine execution", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": { + "cursor": "default" + }, + "titleStyle": { + "fontSize": "20px", + "fontWeight": 400 + }, + "useDashboardTimewindow": true, + "showLegend": false, + "actions": { + "elementClick": [ + { + "name": "rule_engine_execution_details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "rule_engine_execution", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "3c30248f-0cd8-fb97-a917-bc1e09984a79" + }, + { + "name": "rule_engine_statistics_details", + "icon": "show_chart", + "type": "openDashboardState", + "targetDashboardStateId": "rule_engine_statistics", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "04e4565a-9e24-23df-f376-f2ec70a8165f" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "" + }, + "row": 0, + "col": 0, + "id": "08545554-a0e8-05c7-66df-6000cfeff8a4" + }, + "a245c67e-53ec-d299-fa89-69fe2062ccb2": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "html_value_card", + "type": "latest", + "title": "New widget", + "sizeX": 7.5, + "sizeY": 3, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "transportApiState", + "type": "timeseries", + "label": "apiState", + "color": "#2196f3", + "settings": {}, + "_hash": 0.8830669138660703, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "transportMsgLimit", + "type": "timeseries", + "label": "limit", + "color": "#4caf50", + "settings": {}, + "_hash": 0.5463603803546802, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "transportMsgCount", + "type": "timeseries", + "label": "count", + "color": "#f44336", + "settings": {}, + "_hash": 0.5564241862015964, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "transportApiState", + "type": "timeseries", + "label": "apiStateClass", + "color": "#ffc107", + "settings": {}, + "_hash": 0.8737107059960671, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value ? value.toLowerCase() : '';" + }, + { + "name": "transportApiState", + "type": "timeseries", + "label": "cardId", + "color": "#607d8b", + "settings": {}, + "_hash": 0.051659774305067296, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return (Math.random()*100000).toFixed(0);" + }, + { + "name": "transportDataPointsLimit", + "type": "timeseries", + "label": "pointsLimit", + "color": "#9c27b0", + "settings": {}, + "_hash": 0.22082255831864894, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "transportDataPointsCount", + "type": "timeseries", + "label": "pointsCount", + "color": "#8bc34a", + "settings": {}, + "_hash": 0.6340356364819146, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "transportApiState", + "type": "timeseries", + "label": "title", + "color": "#3f51b5", + "settings": {}, + "_hash": 0.6894070537030252, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"Transport\";" + } + ] + } + ], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "#666666", + "padding": "0", + "settings": { + "cardHtml": "
\n \n \n
\n
\n
\n
\n ${title}\n
\n
${apiState}
\n
\n
\n
\n
\n
Messages
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
Data points
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bars-row {\n flex: 1;\n display: flex;\n flex-direction: row;\n}\n\n.card .bar-column {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 16px;\n font-size: 14px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n" + }, + "title": "Transport", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": { + "cursor": "default" + }, + "titleStyle": { + "fontSize": "20px", + "fontWeight": 400 + }, + "useDashboardTimewindow": true, + "showLegend": false, + "actions": { + "elementClick": [ + { + "name": "transport_details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "transport", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "46b7cefe-e1f2-67c1-4055-3a214520f869" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "" + }, + "row": 0, + "col": 0, + "id": "a245c67e-53ec-d299-fa89-69fe2062ccb2" + }, + "a151ae60-0326-6116-d818-9070dda8e9c7": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "html_value_card", + "type": "latest", + "title": "New widget", + "sizeX": 7.5, + "sizeY": 3, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "emailApiState", + "type": "timeseries", + "label": "apiState", + "color": "#2196f3", + "settings": {}, + "_hash": 0.8830669138660703, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "emailLimit", + "type": "timeseries", + "label": "limit", + "color": "#4caf50", + "settings": {}, + "_hash": 0.5463603803546802, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "emailCount", + "type": "timeseries", + "label": "count", + "color": "#f44336", + "settings": {}, + "_hash": 0.5564241862015964, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "emailApiState", + "type": "timeseries", + "label": "apiStateClass", + "color": "#ffc107", + "settings": {}, + "_hash": 0.8737107059960671, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value ? value.toLowerCase() : '';" + }, + { + "name": "emailApiState", + "type": "timeseries", + "label": "cardId", + "color": "#607d8b", + "settings": {}, + "_hash": 0.051659774305067296, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return (Math.random()*100000).toFixed(0);" + }, + { + "name": "emailApiState", + "type": "timeseries", + "label": "title", + "color": "#9c27b0", + "settings": {}, + "_hash": 0.43439375716502227, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"Email messages\";" + } + ] + } + ], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "#666666", + "padding": "0", + "settings": { + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 24px;\n font-size: 20px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n", + "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
" + }, + "title": "Email messages", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": { + "cursor": "default" + }, + "titleStyle": { + "fontSize": "20px", + "fontWeight": 400 + }, + "useDashboardTimewindow": true, + "showLegend": false, + "actions": { + "elementClick": [ + { + "name": "email_messages_details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "email_messages", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "946ba769-84ac-1507-6baa-94701de8967b" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "" + }, + "row": 0, + "col": 0, + "id": "a151ae60-0326-6116-d818-9070dda8e9c7" + }, + "2503a391-5692-0614-85e6-f179c5ee0dc9": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "html_value_card", + "type": "latest", + "title": "New widget", + "sizeX": 7.5, + "sizeY": 3, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "smsApiState", + "type": "timeseries", + "label": "apiState", + "color": "#2196f3", + "settings": {}, + "_hash": 0.8830669138660703, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "smsLimit", + "type": "timeseries", + "label": "limit", + "color": "#4caf50", + "settings": {}, + "_hash": 0.5463603803546802, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "smsCount", + "type": "timeseries", + "label": "count", + "color": "#f44336", + "settings": {}, + "_hash": 0.5564241862015964, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "smsApiState", + "type": "timeseries", + "label": "apiStateClass", + "color": "#ffc107", + "settings": {}, + "_hash": 0.8737107059960671, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value ? value.toLowerCase() : '';" + }, + { + "name": "smsApiState", + "type": "timeseries", + "label": "cardId", + "color": "#607d8b", + "settings": {}, + "_hash": 0.051659774305067296, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return (Math.random()*100000).toFixed(0);" + }, + { + "name": "smsApiState", + "type": "timeseries", + "label": "title", + "color": "#9c27b0", + "settings": {}, + "_hash": 0.286155312985084, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"SMS messages\";" + } + ] + } + ], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "#666666", + "padding": "0", + "settings": { + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 24px;\n font-size: 20px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n", + "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
" + }, + "title": "SMS messages", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": { + "cursor": "default" + }, + "titleStyle": { + "fontSize": "20px", + "fontWeight": 400 + }, + "useDashboardTimewindow": true, + "showLegend": false, + "actions": { + "elementClick": [ + { + "name": "sms_messages_details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "sms_messages", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "73a35536-96a3-47dd-95bb-b1d9119d0ea2" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "" + }, + "row": 0, + "col": 0, + "id": "2503a391-5692-0614-85e6-f179c5ee0dc9" + }, + "68e16e98-0420-f72c-4848-41dedffd3904": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "ruleEngineExecutionCountHourly", + "type": "timeseries", + "label": "Rule Engine executions", + "color": "#ab00ff", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 86400000, + "interval": 3600000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "min": 0, + "tickDecimals": 0 + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 1800000, + "barAlignment": "center", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "Rule Engine hourly activity", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": { + "headerButton": [ + { + "name": "View statistics", + "icon": "show_chart", + "type": "openDashboardState", + "targetDashboardStateId": "rule_engine_statistics", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "f9f08190-9ed9-d802-5b7a-c57ff84b5648" + }, + { + "name": "View details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "rule_engine_execution", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "1aec196b-44ba-ddf4-c4dc-c3f60c1eb6fc" + } + ] + }, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "68e16e98-0420-f72c-4848-41dedffd3904" + }, + "2aa6b499-6e27-b315-6833-89c4d58485ce": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "transportMsgCountHourly", + "type": "timeseries", + "label": "Transport messages", + "color": "#2196f3", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "transportDataPointsCountHourly", + "type": "timeseries", + "label": "Transport data points", + "color": "#4caf50", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.46849996721308895, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 86400000, + "interval": 3600000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "min": 0, + "tickDecimals": 0 + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": true, + "tooltipIndividual": false, + "defaultBarWidth": 1800000, + "barAlignment": "center", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + }, + "tooltipCumulative": false + }, + "title": "Transport hourly activity", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": { + "headerButton": [ + { + "name": "View details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "transport", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "6ef12f6a-0266-25cf-6ca5-5dcb772252c6" + } + ] + }, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "2aa6b499-6e27-b315-6833-89c4d58485ce" + }, + "d890cea3-fba0-6474-9a21-fa780230dc62": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "jsExecutionCountHourly", + "type": "timeseries", + "label": "JavaScript executions", + "color": "#ff9900", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 86400000, + "interval": 3600000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "min": 0, + "tickDecimals": 0 + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 1800000, + "barAlignment": "center", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "JavaScript functions hourly activity", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": { + "headerButton": [ + { + "name": "View details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "javascript_functions", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "4687d3f6-8800-a3b6-26e5-0d33f3b828a9" + } + ] + }, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "d890cea3-fba0-6474-9a21-fa780230dc62" + }, + "84b6cfa5-1449-e0f2-3560-f810d2dd7ead": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "storageDataPointsCountHourly", + "type": "timeseries", + "label": "Storage data points", + "color": "#1039ee", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 86400000, + "interval": 3600000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "min": 0, + "tickDecimals": 0 + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 1800000, + "barAlignment": "center", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "Telemetry persistence hourly activity", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": { + "headerButton": [ + { + "name": "View details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "telemetry_persistence", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "16707efb-e572-bd02-c219-55fc1b0f672a" + } + ] + }, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "84b6cfa5-1449-e0f2-3560-f810d2dd7ead" + }, + "d296b566-a000-7402-ae9d-c815381c5435": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "emailCountHourly", + "type": "timeseries", + "label": "Email messages", + "color": "#d35a00", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 86400000, + "interval": 3600000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "min": 0, + "tickDecimals": 0 + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 1800000, + "barAlignment": "center", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "Email messages hourly activity", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": { + "headerButton": [ + { + "name": "View details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "email_messages", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "371882f9-ea23-3abc-fca8-9449c5dfdd6b" + } + ] + }, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "d296b566-a000-7402-ae9d-c815381c5435" + }, + "00a02464-9509-911b-3b5e-21fb37629822": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "smsCountHourly", + "type": "timeseries", + "label": "SMS messages", + "color": "#f36021", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 86400000, + "interval": 3600000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "min": 0, + "tickDecimals": 0 + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 1800000, + "barAlignment": "center", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "SMS messages hourly activity", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": { + "headerButton": [ + { + "name": "View details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "sms_messages", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "49aefac0-ec5e-d6f3-f39c-8744759f4b19" + } + ] + }, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "00a02464-9509-911b-3b5e-21fb37629822" + }, + "74199074-7873-6c6a-2a51-3fc614769f03": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "ruleEngineExecutionCountHourly", + "type": "timeseries", + "label": "Rule Engine executions", + "color": "#ab00ff", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 2592000000, + "interval": 86400000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "min": 0, + "tickDecimals": 0 + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 1800000, + "barAlignment": "center", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "Rule Engine daily activity", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "74199074-7873-6c6a-2a51-3fc614769f03" + }, + "00006bc3-8a8d-b55e-ed39-e318f1bcd090": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "ruleEngineExecutionCount", + "type": "timeseries", + "label": "Rule Engine executions", + "color": "#ab00ff", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 31536000000, + "interval": 86400000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "min": 0, + "tickDecimals": 0 + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 900000000, + "barAlignment": "center", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "Rule Engine monthly activity", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "00006bc3-8a8d-b55e-ed39-e318f1bcd090" + }, + "5f5ca59c-e507-5301-5910-7ad8cd34df40": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "jsExecutionCountHourly", + "type": "timeseries", + "label": "JavaScript executions", + "color": "#ff9900", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 2592000000, + "interval": 86400000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "min": 0, + "tickDecimals": 0, + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 1800000, + "barAlignment": "center", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "JavaScript functions daily activity", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "5f5ca59c-e507-5301-5910-7ad8cd34df40" + }, + "ada32ee9-44ed-48d1-c368-fd0c94b7607f": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "jsExecutionCount", + "type": "timeseries", + "label": "JavaScript executions", + "color": "#ff9900", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 31536000000, + "interval": 86400000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "min": 0, + "tickDecimals": 0, + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 900000000, + "barAlignment": "center", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "JavaScript functions monthly activity", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "ada32ee9-44ed-48d1-c368-fd0c94b7607f" + }, + "98bae68b-0f35-72f2-a428-9b06889f1554": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "transportMsgCountHourly", + "type": "timeseries", + "label": "Transport messages", + "color": "#2196f3", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "transportDataPointsCountHourly", + "type": "timeseries", + "label": "Transport data points", + "color": "#4caf50", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.46849996721308895, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 2592000000, + "interval": 86400000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "min": 0, + "tickDecimals": 0 + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": true, + "tooltipIndividual": false, + "defaultBarWidth": 1800000, + "barAlignment": "center", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + }, + "tooltipCumulative": false + }, + "title": "Transport daily activity", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "98bae68b-0f35-72f2-a428-9b06889f1554" + }, + "e61e5477-5a09-cc25-966b-f613d81da833": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "transportMsgCount", + "type": "timeseries", + "label": "Transport messages", + "color": "#2196f3", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "transportDataPointsCount", + "type": "timeseries", + "label": "Transport data points", + "color": "#4caf50", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.46849996721308895, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 31536000000, + "interval": 2592000000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "min": 0, + "tickDecimals": 0 + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": true, + "tooltipIndividual": false, + "defaultBarWidth": 900000000, + "barAlignment": "center", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + }, + "tooltipCumulative": false + }, + "title": "Transport monthly activity", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "e61e5477-5a09-cc25-966b-f613d81da833" + }, + "85fe0738-5326-f069-ab3f-30594bde5fed": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "storageDataPointsCountHourly", + "type": "timeseries", + "label": "Storage data points", + "color": "#1039ee", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 2592000000, + "interval": 86400000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "min": 0, + "tickDecimals": 0, + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 1800000, + "barAlignment": "center", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "Telemetry persistence daily activity", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "85fe0738-5326-f069-ab3f-30594bde5fed" + }, + "eaeb381a-437e-f6e9-60c9-6bc8826fdd44": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "storageDataPointsCount", + "type": "timeseries", + "label": "Storage data points", + "color": "#1039ee", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 31536000000, + "interval": 86400000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "min": 0, + "tickDecimals": 0, + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 900000000, + "barAlignment": "center", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "Telemetry persistence monthly activity", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "eaeb381a-437e-f6e9-60c9-6bc8826fdd44" + }, + "4b798823-b97d-9d6a-59dc-fcafd897fc23": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "emailCountHourly", + "type": "timeseries", + "label": "Email messages", + "color": "#d35a00", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 2592000000, + "interval": 86400000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "min": 0, + "tickDecimals": 0, + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 1800000, + "barAlignment": "center", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "Email messages daily activity", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "4b798823-b97d-9d6a-59dc-fcafd897fc23" + }, + "6a981580-7490-19dd-f937-b64cbf67a982": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "emailCount", + "type": "timeseries", + "label": "Email messages", + "color": "#d35a00", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 31536000000, + "interval": 86400000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "min": 0, + "tickDecimals": 0, + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 900000000, + "barAlignment": "center", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "Email messages monthly activity", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "6a981580-7490-19dd-f937-b64cbf67a982" + }, + "7302df65-1b0c-579e-bbdb-145126ae3392": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "smsCountHourly", + "type": "timeseries", + "label": "SMS messages", + "color": "#f36021", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 2592000000, + "interval": 86400000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "min": 0, + "tickDecimals": 0, + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 1800000, + "barAlignment": "center", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "SMS messages daily activity", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "7302df65-1b0c-579e-bbdb-145126ae3392" + }, + "fdb385e7-14fe-fc9f-ebdc-b400f26fc66b": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "smsCount", + "type": "timeseries", + "label": "SMS messages", + "color": "#f36021", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 31536000000, + "interval": 86400000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "min": 0, + "tickDecimals": 0, + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 900000000, + "barAlignment": "center", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "SMS messages monthly activity", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "fdb385e7-14fe-fc9f-ebdc-b400f26fc66b" + }, + "2408ad30-163e-8221-08e1-a82b638be564": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "basic_timeseries", + "type": "timeseries", + "title": "New widget", + "sizeX": 12, + "sizeY": 7, + "config": { + "datasources": [ + { + "type": "entity", + "dataKeys": [ + { + "name": "successfulMsgs", + "type": "timeseries", + "label": "${entityName} Successful", + "color": "#4caf50", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": true, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.15490750967648736 + }, + { + "name": "failedMsgs", + "type": "timeseries", + "label": "${entityName} Permanent Failures", + "color": "#ef5350", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": true, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.4186621166514697 + }, + { + "name": "tmpFailed", + "type": "timeseries", + "label": "${entityName} Processing Failures", + "color": "#ffc107", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": true, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.49891007198715376 + } + ], + "entityAliasId": "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f" + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 3600000, + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 10000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "min": 0, + "tickDecimals": 0, + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "Queue Stats", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "mobileHeight": null, + "showTitleIcon": false, + "titleIcon": null, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "widgetStyle": {}, + "useDashboardTimewindow": false, + "displayTimewindow": true, + "showLegend": true, + "actions": {}, + "legendConfig": { + "direction": "column", + "position": "bottom", + "showMin": true, + "showMax": true, + "showAvg": false, + "showTotal": true + } + }, + "id": "2408ad30-163e-8221-08e1-a82b638be564" + }, + "e43dcfe1-b970-6a11-ce0e-5769f3eb5e88": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "basic_timeseries", + "type": "timeseries", + "title": "New widget", + "sizeX": 12, + "sizeY": 7, + "config": { + "datasources": [ + { + "type": "entity", + "dataKeys": [ + { + "name": "timeoutMsgs", + "type": "timeseries", + "label": "${entityName} Permanent Timeouts", + "color": "#4caf50", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": true, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.565222981550328 + }, + { + "name": "tmpTimeout", + "type": "timeseries", + "label": "${entityName} Processing Timeouts", + "color": "#9c27b0", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": true, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.2679547062508352 + } + ], + "entityAliasId": "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f" + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 3600000, + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 10000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "min": 0, + "tickDecimals": 0, + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "Processing Failures and Timeouts", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "mobileHeight": null, + "showTitleIcon": false, + "titleIcon": null, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "widgetStyle": {}, + "useDashboardTimewindow": false, + "displayTimewindow": true, + "showLegend": true, + "actions": {}, + "legendConfig": { + "direction": "column", + "position": "bottom", + "showMin": true, + "showMax": true, + "showAvg": false, + "showTotal": true + } + }, + "id": "e43dcfe1-b970-6a11-ce0e-5769f3eb5e88" + }, + "a669cf86-e715-efa4-dd9a-b839abf499e9": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "timeseries_table", + "type": "timeseries", + "title": "New widget", + "sizeX": 24, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "dataKeys": [ + { + "name": "ruleEngineException", + "type": "timeseries", + "label": "Rule Chain", + "color": "#2196f3", + "settings": { + "useCellStyleFunction": false, + "useCellContentFunction": true, + "cellContentFunction": "return JSON.parse(value).ruleChainName;" + }, + "_hash": 0.9954481282345906 + }, + { + "name": "ruleEngineException", + "type": "timeseries", + "label": "Rule Node", + "color": "#4caf50", + "settings": { + "useCellStyleFunction": false, + "useCellContentFunction": true, + "cellContentFunction": "return JSON.parse(value).ruleNodeName;" + }, + "_hash": 0.18580357036589978 + }, + { + "name": "ruleEngineException", + "type": "timeseries", + "label": "Latest Error", + "color": "#f44336", + "settings": { + "useCellStyleFunction": false, + "useCellContentFunction": true, + "cellContentFunction": "return JSON.parse(value).message;" + }, + "_hash": 0.7255162989552142 + } + ], + "entityAliasId": "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f" + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 2592000000, + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "rgb(255, 255, 255)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "showTimestamp": true, + "displayPagination": true, + "defaultPageSize": 10 + }, + "title": "Exceptions", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "useDashboardTimewindow": false, + "showLegend": false, + "widgetStyle": {}, + "actions": {}, + "showTitleIcon": false, + "titleIcon": null, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "displayTimewindow": true + }, + "id": "a669cf86-e715-efa4-dd9a-b839abf499e9" + } + }, + "states": { + "default": { + "name": "Api Usage", + "root": true, + "layouts": { + "main": { + "widgets": { + "fd6df872-2ddf-0921-3929-2e7f55062fad": { + "sizeX": 4, + "sizeY": 2, + "row": 0, + "col": 8, + "mobileHeight": 3 + }, + "7e235874-461b-e7c2-2fdd-d8762a020773": { + "sizeX": 4, + "sizeY": 2, + "row": 0, + "col": 12, + "mobileHeight": 3 + }, + "08545554-a0e8-05c7-66df-6000cfeff8a4": { + "sizeX": 4, + "sizeY": 2, + "row": 0, + "col": 4, + "mobileHeight": 3 + }, + "a245c67e-53ec-d299-fa89-69fe2062ccb2": { + "sizeX": 4, + "sizeY": 2, + "row": 0, + "col": 0, + "mobileHeight": 3 + }, + "a151ae60-0326-6116-d818-9070dda8e9c7": { + "sizeX": 4, + "sizeY": 2, + "row": 0, + "col": 16, + "mobileHeight": 3 + }, + "2503a391-5692-0614-85e6-f179c5ee0dc9": { + "sizeX": 4, + "sizeY": 2, + "row": 0, + "col": 20, + "mobileHeight": 3 + }, + "68e16e98-0420-f72c-4848-41dedffd3904": { + "sizeX": 8, + "sizeY": 4, + "row": 2, + "col": 8 + }, + "2aa6b499-6e27-b315-6833-89c4d58485ce": { + "sizeX": 8, + "sizeY": 4, + "row": 2, + "col": 0 + }, + "d890cea3-fba0-6474-9a21-fa780230dc62": { + "sizeX": 8, + "sizeY": 4, + "row": 2, + "col": 16 + }, + "84b6cfa5-1449-e0f2-3560-f810d2dd7ead": { + "sizeX": 8, + "sizeY": 4, + "row": 6, + "col": 0 + }, + "d296b566-a000-7402-ae9d-c815381c5435": { + "sizeX": 8, + "sizeY": 4, + "row": 6, + "col": 8 + }, + "00a02464-9509-911b-3b5e-21fb37629822": { + "sizeX": 8, + "sizeY": 4, + "row": 6, + "col": 16 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 5, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "transport": { + "name": "Transport", + "root": false, + "layouts": { + "main": { + "widgets": { + "98bae68b-0f35-72f2-a428-9b06889f1554": { + "sizeX": 24, + "sizeY": 6, + "row": 0, + "col": 0 + }, + "e61e5477-5a09-cc25-966b-f613d81da833": { + "sizeX": 24, + "sizeY": 6, + "row": 6, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "rule_engine_execution": { + "name": "Rule Engine execution", + "root": false, + "layouts": { + "main": { + "widgets": { + "74199074-7873-6c6a-2a51-3fc614769f03": { + "sizeX": 24, + "sizeY": 6, + "row": 0, + "col": 0 + }, + "00006bc3-8a8d-b55e-ed39-e318f1bcd090": { + "sizeX": 24, + "sizeY": 6, + "row": 6, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "javascript_functions": { + "name": "JavaScript functions", + "root": false, + "layouts": { + "main": { + "widgets": { + "5f5ca59c-e507-5301-5910-7ad8cd34df40": { + "sizeX": 24, + "sizeY": 6, + "row": 0, + "col": 0 + }, + "ada32ee9-44ed-48d1-c368-fd0c94b7607f": { + "sizeX": 24, + "sizeY": 6, + "row": 6, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "telemetry_persistence": { + "name": "Telemetry persistence", + "root": false, + "layouts": { + "main": { + "widgets": { + "85fe0738-5326-f069-ab3f-30594bde5fed": { + "sizeX": 24, + "sizeY": 6, + "row": 0, + "col": 0 + }, + "eaeb381a-437e-f6e9-60c9-6bc8826fdd44": { + "sizeX": 24, + "sizeY": 6, + "row": 6, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "email_messages": { + "name": "Email messages", + "root": false, + "layouts": { + "main": { + "widgets": { + "4b798823-b97d-9d6a-59dc-fcafd897fc23": { + "sizeX": 24, + "sizeY": 6, + "row": 0, + "col": 0 + }, + "6a981580-7490-19dd-f937-b64cbf67a982": { + "sizeX": 24, + "sizeY": 6, + "row": 6, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "sms_messages": { + "name": "SMS messages", + "root": false, + "layouts": { + "main": { + "widgets": { + "7302df65-1b0c-579e-bbdb-145126ae3392": { + "sizeX": 24, + "sizeY": 6, + "row": 0, + "col": 0 + }, + "fdb385e7-14fe-fc9f-ebdc-b400f26fc66b": { + "sizeX": 24, + "sizeY": 6, + "row": 6, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "rule_engine_statistics": { + "name": "Rule Engine Statistics", + "root": false, + "layouts": { + "main": { + "widgets": { + "2408ad30-163e-8221-08e1-a82b638be564": { + "sizeX": 12, + "sizeY": 7, + "mobileHeight": null, + "row": 0, + "col": 0 + }, + "e43dcfe1-b970-6a11-ce0e-5769f3eb5e88": { + "sizeX": 12, + "sizeY": 7, + "mobileHeight": null, + "row": 0, + "col": 12 + }, + "a669cf86-e715-efa4-dd9a-b839abf499e9": { + "sizeX": 24, + "sizeY": 5, + "row": 7, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + } + }, + "entityAliases": { + "40193437-33ac-3172-eefd-0b08eb849062": { + "id": "40193437-33ac-3172-eefd-0b08eb849062", + "alias": "Api usage state", + "filter": { + "type": "apiUsageState", + "resolveMultiple": false + } + }, + "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f": { + "id": "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f", + "alias": "TbServiceQueues", + "filter": { + "type": "assetType", + "resolveMultiple": true, + "assetType": "TbServiceQueue", + "assetNameFilter": "" + } + } + }, + "filters": {}, + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 86400000, + "interval": 3600000 + }, + "aggregation": { + "type": "NONE", + "limit": 50000 + } + }, + "settings": { + "stateControllerId": "entity", + "showTitle": false, + "showDashboardsSelect": false, + "showEntitiesSelect": false, + "showDashboardTimewindow": false, + "showDashboardExport": false, + "toolbarAlwaysOpen": true, + "titleColor": "rgba(0,0,0,0.870588)", + "showFilters": false + } + }, + "name": "Api Usage" +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html index 9f46f6b219..e6063b3556 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html @@ -196,7 +196,7 @@ matTooltipPosition="above"> done - \n \n" + "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" }, "title": "JavaScript functions", "dropShadow": true, @@ -240,7 +253,20 @@ "decimals": null, "funcBody": null, "usePostProcessing": true, - "postFuncBody": "return \"Telemetry persistence\";" + "postFuncBody": "return \"Telemetry\";" + }, + { + "name": "dbApiState", + "type": "timeseries", + "label": "unit", + "color": "#8bc34a", + "settings": {}, + "_hash": 0.0027742924142306613, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"Data points storage days\";" } ] } @@ -255,8 +281,8 @@ "color": "#666666", "padding": "0", "settings": { - "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 24px;\n font-size: 20px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n" + "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" }, "title": "Telemetry persistence", "dropShadow": true, @@ -385,7 +411,20 @@ "decimals": null, "funcBody": null, "usePostProcessing": true, - "postFuncBody": "return \"Rule Engine execution\";" + "postFuncBody": "return \"Rule Engine\";" + }, + { + "name": "ruleEngineApiState", + "type": "timeseries", + "label": "unit", + "color": "#8bc34a", + "settings": {}, + "_hash": 0.5100381746798048, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"Executions\";" } ] } @@ -400,8 +439,8 @@ "color": "#666666", "padding": "0", "settings": { - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 24px;\n font-size: 20px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n", - "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n \n
\n
" + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n", + "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n \n
\n
" }, "title": "Rule Engine execution", "dropShadow": true, @@ -581,8 +620,8 @@ "color": "#666666", "padding": "0", "settings": { - "cardHtml": "
\n \n \n
\n
\n
\n
\n ${title}\n
\n
${apiState}
\n
\n
\n
\n
\n
Messages
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
Data points
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bars-row {\n flex: 1;\n display: flex;\n flex-direction: row;\n}\n\n.card .bar-column {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 16px;\n font-size: 14px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n" + "cardHtml": "
\n \n \n
\n
\n
\n
\n ${title}\n
\n
${apiState}
\n
\n
\n
\n
\n
Messages
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
Data points
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bars-row {\n flex: 1;\n display: flex;\n flex-direction: row;\n}\n\n.card .bar-column {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 6px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n" }, "title": "Transport", "dropShadow": true, @@ -711,7 +750,20 @@ "decimals": null, "funcBody": null, "usePostProcessing": true, - "postFuncBody": "return \"Email messages\";" + "postFuncBody": "return \"Email\";" + }, + { + "name": "emailApiState", + "type": "timeseries", + "label": "unit", + "color": "#8bc34a", + "settings": {}, + "_hash": 0.9964061963495883, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"Messages\";" } ] } @@ -726,8 +778,8 @@ "color": "#666666", "padding": "0", "settings": { - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 24px;\n font-size: 20px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n", - "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
" + "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" }, "title": "Email messages", "dropShadow": true, @@ -856,7 +908,20 @@ "decimals": null, "funcBody": null, "usePostProcessing": true, - "postFuncBody": "return \"SMS messages\";" + "postFuncBody": "return \"SMS\";" + }, + { + "name": "smsApiState", + "type": "timeseries", + "label": "unit", + "color": "#8bc34a", + "settings": {}, + "_hash": 0.39363366156190605, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"Messages\";" } ] } @@ -871,8 +936,8 @@ "color": "#666666", "padding": "0", "settings": { - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 24px;\n font-size: 20px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n", - "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
" + "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" }, "title": "SMS messages", "dropShadow": true, @@ -4352,4 +4417,4 @@ } }, "name": "Api Usage" -} +} \ No newline at end of file From 2cf6de81c0227aea231e06fbb89b3f90b1278c39 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 1 Dec 2020 10:16:58 +0200 Subject: [PATCH 450/463] UI: Added trackBy in table widget --- .../widget/lib/alarms-table-widget.component.html | 6 +++--- .../widget/lib/alarms-table-widget.component.ts | 8 ++++++-- .../widget/lib/entities-table-widget.component.html | 6 +++--- .../widget/lib/entities-table-widget.component.ts | 8 ++++++-- .../lib/timeseries-table-widget.component.html | 8 ++++---- .../widget/lib/timeseries-table-widget.component.ts | 12 ++++++++++-- 6 files changed, 32 insertions(+), 16 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html index d269fbbe92..54ddd6fc80 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html @@ -61,7 +61,7 @@
-
@@ -96,7 +96,7 @@ width: (actionCellDescriptors.length * 40) + 'px' }">
-
-
{{ column.title }} @@ -57,7 +57,7 @@ width: (actionCellDescriptors.length * 40) + 'px' }">
-
Timestamp @@ -67,7 +67,7 @@ width: (actionCellDescriptors.length * 40) + 'px' }">
- \n
\n", - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" + "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" }, "title": "JavaScript functions", "dropShadow": true, @@ -253,7 +253,7 @@ "decimals": null, "funcBody": null, "usePostProcessing": true, - "postFuncBody": "return \"Telemetry\";" + "postFuncBody": "return \"{i18n:api-usage.telemetry}\";" }, { "name": "dbApiState", @@ -266,7 +266,7 @@ "decimals": null, "funcBody": null, "usePostProcessing": true, - "postFuncBody": "return \"Data points storage days\";" + "postFuncBody": "return \"{i18n:api-usage.data-points-storage-days}\";" } ] } @@ -281,8 +281,8 @@ "color": "#666666", "padding": "0", "settings": { - "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" + "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" }, "title": "Telemetry persistence", "dropShadow": true, @@ -411,7 +411,7 @@ "decimals": null, "funcBody": null, "usePostProcessing": true, - "postFuncBody": "return \"Rule Engine\";" + "postFuncBody": "return \"{i18n:api-usage.rule-engine}\";" }, { "name": "ruleEngineApiState", @@ -424,7 +424,7 @@ "decimals": null, "funcBody": null, "usePostProcessing": true, - "postFuncBody": "return \"Executions\";" + "postFuncBody": "return \"{i18n:api-usage.executions}\";" } ] } @@ -439,8 +439,8 @@ "color": "#666666", "padding": "0", "settings": { - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n", - "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n \n
\n
" + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n", + "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n \n
\n
" }, "title": "Rule Engine execution", "dropShadow": true, @@ -605,7 +605,7 @@ "decimals": null, "funcBody": null, "usePostProcessing": true, - "postFuncBody": "return \"Transport\";" + "postFuncBody": "return \"{i18n:api-usage.transport}\";" } ] } @@ -620,8 +620,8 @@ "color": "#666666", "padding": "0", "settings": { - "cardHtml": "
\n \n \n
\n
\n
\n
\n ${title}\n
\n
${apiState}
\n
\n
\n
\n
\n
Messages
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
Data points
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bars-row {\n flex: 1;\n display: flex;\n flex-direction: row;\n}\n\n.card .bar-column {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 6px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n" + "cardHtml": "
\n \n \n
\n
\n
\n
\n ${title}\n
\n
${apiState}
\n
\n
\n
\n
\n
{i18n:api-usage.messages}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
{i18n:api-usage.data-points}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bars-row {\n flex: 1;\n display: flex;\n flex-direction: row;\n}\n\n.card .bar-column {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 6px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n" }, "title": "Transport", "dropShadow": true, @@ -750,7 +750,7 @@ "decimals": null, "funcBody": null, "usePostProcessing": true, - "postFuncBody": "return \"Email\";" + "postFuncBody": "return \"{i18n:api-usage.email}\";" }, { "name": "emailApiState", @@ -763,7 +763,7 @@ "decimals": null, "funcBody": null, "usePostProcessing": true, - "postFuncBody": "return \"Messages\";" + "postFuncBody": "return \"{i18n:api-usage.messages}\";" } ] } @@ -778,8 +778,8 @@ "color": "#666666", "padding": "0", "settings": { - "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" + "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" }, "title": "Email messages", "dropShadow": true, @@ -908,7 +908,7 @@ "decimals": null, "funcBody": null, "usePostProcessing": true, - "postFuncBody": "return \"SMS\";" + "postFuncBody": "return \"{i18n:api-usage.sms}\";" }, { "name": "smsApiState", @@ -921,7 +921,7 @@ "decimals": null, "funcBody": null, "usePostProcessing": true, - "postFuncBody": "return \"Messages\";" + "postFuncBody": "return \"{i18n:api-usage.messages}\";" } ] } @@ -936,8 +936,8 @@ "color": "#666666", "padding": "0", "settings": { - "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", - "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" + "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" }, "title": "SMS messages", "dropShadow": true, @@ -993,7 +993,7 @@ { "name": "ruleEngineExecutionCountHourly", "type": "timeseries", - "label": "Rule Engine executions", + "label": "{i18n:api-usage.rule-engine-executions}", "color": "#ab00ff", "settings": { "excludeFromStacking": false, @@ -1078,7 +1078,7 @@ "showLabels": true } }, - "title": "Rule Engine hourly activity", + "title": "{i18n:api-usage.rule-engine-hourly-activity}", "dropShadow": true, "enableFullscreen": true, "titleStyle": { @@ -1091,7 +1091,7 @@ "actions": { "headerButton": [ { - "name": "View statistics", + "name": "{i18n:api-usage.view-statistics}", "icon": "show_chart", "type": "openDashboardState", "targetDashboardStateId": "rule_engine_statistics", @@ -1101,7 +1101,7 @@ "id": "f9f08190-9ed9-d802-5b7a-c57ff84b5648" }, { - "name": "View details", + "name": "{i18n:api-usage.view-details}", "icon": "insert_chart", "type": "openDashboardState", "targetDashboardStateId": "rule_engine_execution", @@ -1150,7 +1150,7 @@ { "name": "transportMsgCountHourly", "type": "timeseries", - "label": "Transport messages", + "label": "{i18n:api-usage.transport-messages}", "color": "#2196f3", "settings": { "excludeFromStacking": false, @@ -1185,7 +1185,7 @@ { "name": "transportDataPointsCountHourly", "type": "timeseries", - "label": "Transport data points", + "label": "{i18n:api-usage.transport-data-points}", "color": "#4caf50", "settings": { "excludeFromStacking": false, @@ -1271,7 +1271,7 @@ }, "tooltipCumulative": false }, - "title": "Transport hourly activity", + "title": "{i18n:api-usage.transport-hourly-activity}", "dropShadow": true, "enableFullscreen": true, "titleStyle": { @@ -1284,7 +1284,7 @@ "actions": { "headerButton": [ { - "name": "View details", + "name": "{i18n:api-usage.view-details}", "icon": "insert_chart", "type": "openDashboardState", "targetDashboardStateId": "transport", @@ -1333,7 +1333,7 @@ { "name": "jsExecutionCountHourly", "type": "timeseries", - "label": "JavaScript executions", + "label": "{i18n:api-usage.javascript-executions}", "color": "#ff9900", "settings": { "excludeFromStacking": false, @@ -1418,7 +1418,7 @@ "showLabels": true } }, - "title": "JavaScript functions hourly activity", + "title": "{i18n:api-usage.javascript-functions-hourly-activity}", "dropShadow": true, "enableFullscreen": true, "titleStyle": { @@ -1431,7 +1431,7 @@ "actions": { "headerButton": [ { - "name": "View details", + "name": "{i18n:api-usage.view-details}", "icon": "insert_chart", "type": "openDashboardState", "targetDashboardStateId": "javascript_functions", @@ -1480,7 +1480,7 @@ { "name": "storageDataPointsCountHourly", "type": "timeseries", - "label": "Data points storage days", + "label": "{i18n:api-usage.data-points-storage-days}", "color": "#1039ee", "settings": { "excludeFromStacking": false, @@ -1565,7 +1565,7 @@ "showLabels": true } }, - "title": "Telemetry persistence hourly activity", + "title": "{i18n:api-usage.telemetry-persistence-hourly-activity}", "dropShadow": true, "enableFullscreen": true, "titleStyle": { @@ -1578,7 +1578,7 @@ "actions": { "headerButton": [ { - "name": "View details", + "name": "{i18n:api-usage.view-details}", "icon": "insert_chart", "type": "openDashboardState", "targetDashboardStateId": "telemetry_persistence", @@ -1627,7 +1627,7 @@ { "name": "emailCountHourly", "type": "timeseries", - "label": "Email messages", + "label": "{i18n:api-usage.email-messages}", "color": "#d35a00", "settings": { "excludeFromStacking": false, @@ -1712,7 +1712,7 @@ "showLabels": true } }, - "title": "Email messages hourly activity", + "title": "{i18n:api-usage.email-messages-hourly-activity}", "dropShadow": true, "enableFullscreen": true, "titleStyle": { @@ -1725,7 +1725,7 @@ "actions": { "headerButton": [ { - "name": "View details", + "name": "{i18n:api-usage.view-details}", "icon": "insert_chart", "type": "openDashboardState", "targetDashboardStateId": "email_messages", @@ -1774,7 +1774,7 @@ { "name": "smsCountHourly", "type": "timeseries", - "label": "SMS messages", + "label": "{i18n:api-usage.sms-messages}", "color": "#f36021", "settings": { "excludeFromStacking": false, @@ -1859,7 +1859,7 @@ "showLabels": true } }, - "title": "SMS messages hourly activity", + "title": "{i18n:api-usage.sms-messages-hourly-activity}", "dropShadow": true, "enableFullscreen": true, "titleStyle": { @@ -1872,7 +1872,7 @@ "actions": { "headerButton": [ { - "name": "View details", + "name": "{i18n:api-usage.view-details}", "icon": "insert_chart", "type": "openDashboardState", "targetDashboardStateId": "sms_messages", @@ -1921,7 +1921,7 @@ { "name": "ruleEngineExecutionCountHourly", "type": "timeseries", - "label": "Rule Engine executions", + "label": "{i18n:api-usage.rule-engine-executions}", "color": "#ab00ff", "settings": { "excludeFromStacking": false, @@ -2007,7 +2007,7 @@ "showLabels": true } }, - "title": "Rule Engine daily activity", + "title": "{i18n:api-usage.rule-engine-daily-activity}", "dropShadow": true, "enableFullscreen": true, "titleStyle": { @@ -2056,7 +2056,7 @@ { "name": "ruleEngineExecutionCount", "type": "timeseries", - "label": "Rule Engine executions", + "label": "{i18n:api-usage.rule-engine-executions}", "color": "#ab00ff", "settings": { "excludeFromStacking": false, @@ -2142,7 +2142,7 @@ "showLabels": true } }, - "title": "Rule Engine monthly activity", + "title": "{i18n:api-usage.rule-engine-monthly-activity}", "dropShadow": true, "enableFullscreen": true, "titleStyle": { @@ -2191,7 +2191,7 @@ { "name": "jsExecutionCountHourly", "type": "timeseries", - "label": "JavaScript executions", + "label": "{i18n:api-usage.javascript-executions}", "color": "#ff9900", "settings": { "excludeFromStacking": false, @@ -2277,7 +2277,7 @@ "showLabels": true } }, - "title": "JavaScript functions daily activity", + "title": "{i18n:api-usage.javascript-functions-daily-activity}", "dropShadow": true, "enableFullscreen": true, "titleStyle": { @@ -2326,7 +2326,7 @@ { "name": "jsExecutionCount", "type": "timeseries", - "label": "JavaScript executions", + "label": "{i18n:api-usage.javascript-executions}", "color": "#ff9900", "settings": { "excludeFromStacking": false, @@ -2412,7 +2412,7 @@ "showLabels": true } }, - "title": "JavaScript functions monthly activity", + "title": "{i18n:api-usage.javascript-functions-monthly-activity}", "dropShadow": true, "enableFullscreen": true, "titleStyle": { @@ -2461,7 +2461,7 @@ { "name": "transportMsgCountHourly", "type": "timeseries", - "label": "Transport messages", + "label": "{i18n:api-usage.transport-messages}", "color": "#2196f3", "settings": { "excludeFromStacking": false, @@ -2496,7 +2496,7 @@ { "name": "transportDataPointsCountHourly", "type": "timeseries", - "label": "Transport data points", + "label": "{i18n:api-usage.transport-data-points}", "color": "#4caf50", "settings": { "excludeFromStacking": false, @@ -2583,7 +2583,7 @@ }, "tooltipCumulative": false }, - "title": "Transport daily activity", + "title": "{i18n:api-usage.transport-daily-activity}", "dropShadow": true, "enableFullscreen": true, "titleStyle": { @@ -2632,7 +2632,7 @@ { "name": "transportMsgCount", "type": "timeseries", - "label": "Transport messages", + "label": "{i18n:api-usage.transport-messages}", "color": "#2196f3", "settings": { "excludeFromStacking": false, @@ -2667,7 +2667,7 @@ { "name": "transportDataPointsCount", "type": "timeseries", - "label": "Transport data points", + "label": "{i18n:api-usage.transport-data-points}", "color": "#4caf50", "settings": { "excludeFromStacking": false, @@ -2754,7 +2754,7 @@ }, "tooltipCumulative": false }, - "title": "Transport monthly activity", + "title": "{i18n:api-usage.transport-monthly-activity}", "dropShadow": true, "enableFullscreen": true, "titleStyle": { @@ -2803,7 +2803,7 @@ { "name": "storageDataPointsCountHourly", "type": "timeseries", - "label": "Data points storage days", + "label": "{i18n:api-usage.data-points-storage-days}", "color": "#1039ee", "settings": { "excludeFromStacking": false, @@ -2889,7 +2889,7 @@ "showLabels": true } }, - "title": "Telemetry persistence daily activity", + "title": "{i18n:api-usage.telemetry-persistence-daily-activity}", "dropShadow": true, "enableFullscreen": true, "titleStyle": { @@ -2938,7 +2938,7 @@ { "name": "storageDataPointsCount", "type": "timeseries", - "label": "Data points storage days", + "label": "{i18n:api-usage.data-points-storage-days}", "color": "#1039ee", "settings": { "excludeFromStacking": false, @@ -3024,7 +3024,7 @@ "showLabels": true } }, - "title": "Telemetry persistence monthly activity", + "title": "{i18n:api-usage.telemetry-persistence-monthly-activity}", "dropShadow": true, "enableFullscreen": true, "titleStyle": { @@ -3073,7 +3073,7 @@ { "name": "emailCountHourly", "type": "timeseries", - "label": "Email messages", + "label": "{i18n:api-usage.email-messages}", "color": "#d35a00", "settings": { "excludeFromStacking": false, @@ -3159,7 +3159,7 @@ "showLabels": true } }, - "title": "Email messages daily activity", + "title": "{i18n:api-usage.email-messages-daily-activity}", "dropShadow": true, "enableFullscreen": true, "titleStyle": { @@ -3208,7 +3208,7 @@ { "name": "emailCount", "type": "timeseries", - "label": "Email messages", + "label": "{i18n:api-usage.email-messages}", "color": "#d35a00", "settings": { "excludeFromStacking": false, @@ -3294,7 +3294,7 @@ "showLabels": true } }, - "title": "Email messages monthly activity", + "title": "{i18n:api-usage.sms-messages-monthly-activity}", "dropShadow": true, "enableFullscreen": true, "titleStyle": { @@ -3343,7 +3343,7 @@ { "name": "smsCountHourly", "type": "timeseries", - "label": "SMS messages", + "label": "{i18n:api-usage.sms-messages}", "color": "#f36021", "settings": { "excludeFromStacking": false, @@ -3429,7 +3429,7 @@ "showLabels": true } }, - "title": "SMS messages daily activity", + "title": "{i18n:api-usage.sms-messages-daily-activity}", "dropShadow": true, "enableFullscreen": true, "titleStyle": { @@ -3478,7 +3478,7 @@ { "name": "smsCount", "type": "timeseries", - "label": "SMS messages", + "label": "{i18n:api-usage.sms-messages}", "color": "#f36021", "settings": { "excludeFromStacking": false, @@ -3564,7 +3564,7 @@ "showLabels": true } }, - "title": "SMS messages monthly activity", + "title": "{i18n:api-usage.sms-messages-monthly-activity}", "dropShadow": true, "enableFullscreen": true, "titleStyle": { @@ -3610,7 +3610,7 @@ { "name": "successfulMsgs", "type": "timeseries", - "label": "${entityName} Successful", + "label": "{i18n:api-usage.successful}", "color": "#4caf50", "settings": { "excludeFromStacking": false, @@ -3640,7 +3640,7 @@ { "name": "failedMsgs", "type": "timeseries", - "label": "${entityName} Permanent Failures", + "label": "{i18n:api-usage.permanent-failures}", "color": "#ef5350", "settings": { "excludeFromStacking": false, @@ -3670,7 +3670,7 @@ { "name": "tmpFailed", "type": "timeseries", - "label": "${entityName} Processing Failures", + "label": "{i18n:api-usage.processing-failures}", "color": "#ffc107", "settings": { "excludeFromStacking": false, @@ -3794,7 +3794,7 @@ { "name": "timeoutMsgs", "type": "timeseries", - "label": "${entityName} Permanent Timeouts", + "label": "{i18n:api-usage.permanent-timeouts}", "color": "#4caf50", "settings": { "excludeFromStacking": false, @@ -3824,7 +3824,7 @@ { "name": "tmpTimeout", "type": "timeseries", - "label": "${entityName} Processing Timeouts", + "label": "{i18n:api-usage.processing-timeouts}", "color": "#9c27b0", "settings": { "excludeFromStacking": false, @@ -4031,7 +4031,7 @@ }, "states": { "default": { - "name": "Api Usage", + "name": "{i18n:api-usage.api-usage}", "root": true, "layouts": { "main": { @@ -4130,7 +4130,7 @@ } }, "transport": { - "name": "Transport", + "name": "{i18n:api-usage.transport}", "root": false, "layouts": { "main": { @@ -4163,7 +4163,7 @@ } }, "rule_engine_execution": { - "name": "Rule Engine execution", + "name": "{i18n:api-usage.rule-engine-executions}", "root": false, "layouts": { "main": { @@ -4196,7 +4196,7 @@ } }, "javascript_functions": { - "name": "JavaScript functions", + "name": "{i18n:api-usage.javascript-functions}", "root": false, "layouts": { "main": { @@ -4229,7 +4229,7 @@ } }, "telemetry_persistence": { - "name": "Telemetry persistence", + "name": "{i18n:api-usage.telemetry-persistence}", "root": false, "layouts": { "main": { @@ -4262,7 +4262,7 @@ } }, "email_messages": { - "name": "Email messages", + "name": "{i18n:api-usage.email-messages}", "root": false, "layouts": { "main": { @@ -4295,7 +4295,7 @@ } }, "sms_messages": { - "name": "SMS messages", + "name": "{i18n:api-usage.sms-messages}", "root": false, "layouts": { "main": { @@ -4328,7 +4328,7 @@ } }, "rule_engine_statistics": { - "name": "Rule Engine Statistics", + "name": "{i18n:api-usage.rule-engine-statistics}", "root": false, "layouts": { "main": { @@ -4417,4 +4417,4 @@ } }, "name": "Api Usage" -} \ No newline at end of file +} diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 2c8cc0641a..86ac20bf35 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -433,7 +433,57 @@ "no-telemetry-text": "No telemetry found" }, "api-usage": { - "api-usage": "Api Usage" + "api-usage": "Api Usage", + "data-points": "Data points", + "data-points-storage-days": "Data points storage days", + "email": "Email", + "email-messages": "Email messages", + "email-messages-daily-activity": "Email messages daily activity", + "email-messages-hourly-activity": "Email messages hourly activity", + "email-messages-monthly-activity": "Email messages monthly activity", + "exceptions": "Exceptions", + "executions": "Executions", + "javascript": "JavaScript", + "javascript-executions": "JavaScript executions", + "javascript-functions": "JavaScript functions", + "javascript-functions-daily-activity": "JavaScript functions daily activity", + "javascript-functions-hourly-activity": "JavaScript functions hourly activity", + "javascript-functions-monthly-activity": "JavaScript functions monthly activity", + "latest-error": "Latest Error", + "messages": "Messages", + "permanent-failures": "${entityName} Permanent Failures", + "permanent-timeouts": "${entityName} Permanent Timeouts", + "processing-failures": "${entityName} Processing Failures", + "processing-failures-and-timeouts": "Processing Failures and Timeouts", + "processing-timeouts": "${entityName} Processing Timeouts", + "queue-stats": "Queue Stats", + "rule-chain": "Rule Chain", + "rule-engine": "Rule Engine", + "rule-engine-daily-activity": "Rule Engine daily activity", + "rule-engine-executions": "Rule Engine executions", + "rule-engine-hourly-activity": "Rule Engine hourly activity", + "rule-engine-monthly-activity": "Rule Engine monthly activity", + "rule-engine-statistics": "Rule Engine Statistics", + "rule-node": "Rule Node", + "sms": "SMS", + "sms-messages": "SMS messages", + "sms-messages-daily-activity": "SMS messages daily activity", + "sms-messages-hourly-activity": "SMS messages hourly activity", + "sms-messages-monthly-activity": "SMS messages monthly activity", + "successful": "${entityName} Successful", + "telemetry": "Telemetry", + "telemetry-persistence": "Telemetry persistence", + "telemetry-persistence-daily-activity": "Telemetry persistence daily activity", + "telemetry-persistence-hourly-activity": "Telemetry persistence hourly activity", + "telemetry-persistence-monthly-activity": "Telemetry persistence monthly activity", + "transport": "Transport", + "transport-daily-activity": "Transport daily activity", + "transport-data-points": "Transport data points", + "transport-hourly-activity": "Transport hourly activity", + "transport-messages": "Transport messages", + "transport-monthly-activity": "Transport monthly activity", + "view-details": "View details", + "view-statistics": "View statistics" }, "audit-log": { "audit": "Audit", From c9598a23ea3e8170f872b2c0c13a6964059076e5 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 3 Dec 2020 14:09:48 +0200 Subject: [PATCH 457/463] UI: Introduce initial page loading spinner --- ui-ngx/src/app/app.component.html | 2 +- ui-ngx/src/app/app.component.ts | 10 +++- ui-ngx/src/index.html | 76 ++++++++++++++++++++++++++++++- 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/app.component.html b/ui-ngx/src/app/app.component.html index 6521e99533..7330d0c1c1 100644 --- a/ui-ngx/src/app/app.component.html +++ b/ui-ngx/src/app/app.component.html @@ -17,4 +17,4 @@ --> - + diff --git a/ui-ngx/src/app/app.component.ts b/ui-ngx/src/app/app.component.ts index f7d290c5ca..ac3512694e 100644 --- a/ui-ngx/src/app/app.component.ts +++ b/ui-ngx/src/app/app.component.ts @@ -62,7 +62,7 @@ export class AppComponent implements OnInit { this.matIconRegistry.addSvgIconLiteral( 'alpha-e-circle-outline', this.domSanitizer.bypassSecurityTrustHtml( - '' ) @@ -124,5 +124,11 @@ export class AppComponent implements OnInit { ngOnInit() { } -} + onActivateComponent($event: any) { + const loadingElement = $('div#tb-loading-spinner'); + if (loadingElement.length) { + loadingElement.remove(); + } + } +} diff --git a/ui-ngx/src/index.html b/ui-ngx/src/index.html index af49c0c938..f569c054e4 100644 --- a/ui-ngx/src/index.html +++ b/ui-ngx/src/index.html @@ -16,7 +16,7 @@ --> - + ThingsBoard @@ -24,8 +24,82 @@ + +
+
+
+
+
From 30bca76585eed149420e57397099f758e309261d Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 3 Dec 2020 14:47:28 +0200 Subject: [PATCH 458/463] UI: set default background color --- ui-ngx/src/app/modules/login/pages/login/login.component.scss | 2 +- ui-ngx/src/index.html | 2 +- ui-ngx/src/styles.scss | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.scss b/ui-ngx/src/app/modules/login/pages/login/login.component.scss index b8fa052e75..28150044c3 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.scss +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.scss @@ -21,7 +21,7 @@ .tb-login-content { margin-top: 36px; margin-bottom: 76px; - background-color: rgb(250, 250, 250); + background-color: #eee; .tb-login-form { @media #{$mat-gt-xs} { width: 550px !important; diff --git a/ui-ngx/src/index.html b/ui-ngx/src/index.html index f569c054e4..4f2672a518 100644 --- a/ui-ngx/src/index.html +++ b/ui-ngx/src/index.html @@ -29,7 +29,7 @@ body, html { height: 100%; overflow: hidden; - background-color: rgb(250,250,250); + background-color: #eee; } .tb-loading-spinner { diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index 190fbb1ba7..600121b66d 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -41,7 +41,7 @@ body, html { body { margin: 0; padding: 0; - background-color: rgb(250,250,250); + background-color: #eee; overflow: hidden; } From 6f2f9c54dcd7c32f0511807a66c3ec8497aa42dd Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 1 Jan 2021 02:03:02 +0200 Subject: [PATCH 459/463] fixed zones in SchedulerUtils and ApiUsageStateService improvements --- .../DefaultTbApiUsageStateService.java | 31 +++++++++++++------ .../common/msg/tools/SchedulerUtils.java | 13 +++----- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 50a273223d..12e8e6017d 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -61,6 +61,7 @@ import org.thingsboard.server.service.telemetry.InternalTelemetryService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -73,6 +74,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; @Slf4j @Service @@ -347,16 +349,11 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { try { long now = System.currentTimeMillis(); myTenantStates.values().forEach(state -> { - if ((state.getNextCycleTs() > now) && (state.getNextCycleTs() - now < TimeUnit.HOURS.toMillis(1))) { + if ((state.getNextCycleTs() < now) && (now - state.getNextCycleTs() < TimeUnit.HOURS.toMillis(1))) { TenantId tenantId = state.getTenantId(); state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth()); - ToUsageStatsServiceMsg.Builder msg = ToUsageStatsServiceMsg.newBuilder(); - msg.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); - msg.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); - for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { - msg.addValues(UsageStatsKVProto.newBuilder().setKey(key.name()).setValue(0).build()); - } - process(new TbProtoQueueMsg<>(UUID.randomUUID(), msg.build()), TbCallback.EMPTY); + saveNewCounts(state, Arrays.asList(ApiUsageRecordKey.values())); + updateTenantState(state, tenantProfileCache.get(tenantId)); } }); } finally { @@ -364,6 +361,14 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } + private void saveNewCounts(TenantApiUsageState state, List keys) { + List counts = keys.stream() + .map(key -> new BasicTsKvEntry(state.getCurrentCycleTs(), new LongDataEntry(key.getApiCountKey(), 0L))) + .collect(Collectors.toList()); + + tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), counts, VOID_CALLBACK); + } + private TenantApiUsageState getOrFetchState(TenantId tenantId) { TenantApiUsageState tenantState = myTenantStates.get(tenantId); if (tenantState == null) { @@ -377,6 +382,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } TenantProfile tenantProfile = tenantProfileCache.get(tenantId); tenantState = new TenantApiUsageState(tenantProfile, dbStateEntity); + List newCounts = new ArrayList<>(); try { List dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getId()).get(); for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { @@ -385,7 +391,13 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { for (TsKvEntry tsKvEntry : dbValues) { if (tsKvEntry.getKey().equals(key.getApiCountKey())) { cycleEntryFound = true; - tenantState.put(key, tsKvEntry.getTs() == tenantState.getCurrentCycleTs() ? tsKvEntry.getLongValue().get() : 0L); + + boolean oldCount = tsKvEntry.getTs() == tenantState.getCurrentCycleTs(); + tenantState.put(key, oldCount ? tsKvEntry.getLongValue().get() : 0L); + + if (!oldCount) { + newCounts.add(key); + } } else if (tsKvEntry.getKey().equals(key.getApiCountKey() + HOURLY)) { hourlyEntryFound = true; tenantState.putHourly(key, tsKvEntry.getTs() == tenantState.getCurrentHourTs() ? tsKvEntry.getLongValue().get() : 0L); @@ -397,6 +409,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } log.debug("[{}] Initialized state: {}", tenantId, dbStateEntity); myTenantStates.put(tenantId, tenantState); + saveNewCounts(tenantState, newCounts); } catch (InterruptedException | ExecutionException e) { log.warn("[{}] Failed to fetch api usage state from db.", tenantId, e); } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/tools/SchedulerUtils.java b/common/message/src/main/java/org/thingsboard/server/common/msg/tools/SchedulerUtils.java index 421066b30b..57cc578f2c 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/tools/SchedulerUtils.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/tools/SchedulerUtils.java @@ -17,22 +17,19 @@ package org.thingsboard.server.common.msg.tools; import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.LocalTime; import java.time.ZoneId; -import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalAdjuster; import java.time.temporal.TemporalAdjusters; -import java.time.temporal.TemporalUnit; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import static java.time.ZoneOffset.UTC; import static java.time.temporal.ChronoField.DAY_OF_MONTH; import static java.time.temporal.ChronoUnit.MONTHS; public class SchedulerUtils { - private final static ZoneId UTC = ZoneId.of("UTC"); private static final ConcurrentMap tzMap = new ConcurrentHashMap<>(); public static ZoneId getZoneId(String tz) { @@ -44,7 +41,7 @@ public class SchedulerUtils { } public static long getStartOfCurrentHour(ZoneId zoneId) { - return LocalDateTime.now(ZoneOffset.UTC).atZone(zoneId).truncatedTo(ChronoUnit.HOURS).toInstant().toEpochMilli(); + return LocalDateTime.now(UTC).atZone(zoneId).truncatedTo(ChronoUnit.HOURS).toInstant().toEpochMilli(); } public static long getStartOfCurrentMonth() { @@ -52,7 +49,7 @@ public class SchedulerUtils { } public static long getStartOfCurrentMonth(ZoneId zoneId) { - return LocalDate.now().withDayOfMonth(1).atStartOfDay(zoneId).toInstant().toEpochMilli(); + return LocalDate.now(UTC).withDayOfMonth(1).atStartOfDay(zoneId).toInstant().toEpochMilli(); } public static long getStartOfNextMonth() { @@ -60,7 +57,7 @@ public class SchedulerUtils { } public static long getStartOfNextMonth(ZoneId zoneId) { - return LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli(); + return LocalDate.now(UTC).with(TemporalAdjusters.firstDayOfNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli(); } public static long getStartOfNextNextMonth() { @@ -68,7 +65,7 @@ public class SchedulerUtils { } public static long getStartOfNextNextMonth(ZoneId zoneId) { - return LocalDate.now().with(firstDayOfNextNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli(); + return LocalDate.now(UTC).with(firstDayOfNextNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli(); } public static TemporalAdjuster firstDayOfNextNextMonth() { From 2ca3ce47f40256b56d15434bc6fc99823fa778a8 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 3 Dec 2020 16:27:27 +0200 Subject: [PATCH 460/463] UI: Fixed show in Safari browser profile; Fixed show ui-rule-node --- .../filter-predicate-value.component.html | 16 ++-- .../filter/key-filter-list.component.html | 2 +- .../alarm/create-alarm-rules.component.html | 2 +- .../alarm/device-profile-alarm.component.html | 7 +- .../device-profile-alarms.component.html | 3 +- .../profile/device-profile.component.html | 2 +- .../rule-node-details.component.html | 86 +++++++++---------- 7 files changed, 57 insertions(+), 61 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html index 524285ea3f..01106137b9 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html @@ -17,28 +17,28 @@ -->
-
+
- + - + - - + {{ (filterPredicateValueFormGroup.get('defaultValue').value ? 'value.true' : 'value.false') | translate }} @@ -46,9 +46,9 @@
filter.default-value
-
+
- + @@ -62,7 +62,7 @@
filter.dynamic-source-type
- + diff --git a/ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.html b/ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.html index 26dbaaec4a..cf670d7b27 100644 --- a/ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.html @@ -41,7 +41,7 @@ filter.operation.and
-
+
{{ keyFilterControl.value.key.key }}
{{ entityKeyTypeTranslations.get(keyFilterControl.value.key.type) }}
-
+
-
+
-
+
diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html index fd4efddf5a..880e7586df 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html @@ -21,48 +21,46 @@ {{'rulechain.open-rulechain' | translate }}
-
- -
-
-
- - rulenode.name - - - {{ 'rulenode.name-required' | translate }} - - - - {{ 'rulenode.debug-mode' | translate }} - -
- - -
- - rulenode.description - - -
+ +
+
+
+ + rulenode.name + + + {{ 'rulenode.name-required' | translate }} + + + + {{ 'rulenode.debug-mode' | translate }} +
-
- - -
- - rulenode.description - - -
-
-
- -
+ + +
+ + rulenode.description + + +
+ +
+ + +
+ + rulenode.description + + +
+
+ + From 41006bf3fcaf88af20d6385607030dea6a57bd24 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 3 Dec 2020 18:20:45 +0200 Subject: [PATCH 461/463] UI: Fixed show in Safari browser dashboard filter --- .../filter-user-info-dialog.component.html | 6 +- .../filter/filters-edit-panel.component.html | 4 +- .../filter/user-filter-dialog.component.html | 58 +++++++++---------- 3 files changed, 32 insertions(+), 36 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.html b/ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.html index 587504da45..9be0838fb8 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.html @@ -27,10 +27,10 @@
- + {{ 'filter.editable' | translate }} -
+
filter.display-label @@ -39,7 +39,7 @@ {{ 'filter.autogenerated-label' | translate }}
- + filter.order-priority diff --git a/ui-ngx/src/app/modules/home/components/filter/filters-edit-panel.component.html b/ui-ngx/src/app/modules/home/components/filter/filters-edit-panel.component.html index 3de6b75bc4..775bfddaaa 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filters-edit-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filters-edit-panel.component.html @@ -16,8 +16,8 @@ -->
-
-
+
+
{{filter.value.filter}}