Browse Source

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

pull/3704/head
ShvaykaD 6 years ago
parent
commit
42152f4a92
  1. 4
      application/pom.xml
  2. 2
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  3. 3
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  4. 3
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  5. 5
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  6. 12
      application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
  7. 3
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  8. 1
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  9. 2
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  10. 2
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  11. 3
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java
  12. 4
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
  13. 13
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java
  14. 14
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java
  15. 10
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
  16. 7
      application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java
  17. 2
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java
  18. 27
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  19. 1
      application/src/main/resources/thingsboard.yml
  20. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TbTenantProfileCache.java
  21. 2
      common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleEvent.java
  22. 4
      common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java
  23. 4
      common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java
  24. 14
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  25. 24
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java
  26. 13
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  27. 23
      dao/src/main/java/org/thingsboard/server/dao/TenantEntityDao.java
  28. 3
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
  29. 11
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  30. 5
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java
  31. 13
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
  32. 4
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java
  33. 16
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  34. 3
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
  35. 13
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  36. 5
      dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java
  37. 15
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  38. 4
      dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java
  39. 15
      dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
  40. 1
      dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java
  41. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java
  42. 1
      dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java
  43. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java
  44. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardRepository.java
  45. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java
  46. 28
      dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java
  47. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
  48. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java
  49. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java
  50. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java
  51. 1
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java
  52. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java
  53. 1
      dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java
  54. 9
      dao/src/main/java/org/thingsboard/server/dao/tenant/DefaultTbTenantProfileCache.java
  55. 14
      dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java
  56. 8
      dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java
  57. 18
      dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
  58. 2
      docker/README.md
  59. 32
      pom.xml
  60. 5
      rule-engine/rule-engine-components/pom.xml
  61. 77
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java
  62. 1
      tools/pom.xml
  63. 3
      ui-ngx/src/app/core/http/entity.service.ts
  64. 6
      ui-ngx/src/app/core/services/dashboard-utils.service.ts
  65. 3
      ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html
  66. 13
      ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts
  67. 48
      ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html
  68. 4
      ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts
  69. 18
      ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts
  70. 2
      ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts
  71. 22
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts
  72. 23
      ui-ngx/src/app/shared/models/ace/service-completion.models.ts
  73. 9
      ui-ngx/src/app/shared/models/entity-type.models.ts
  74. 8
      ui-ngx/src/app/shared/models/tenant.model.ts
  75. 16
      ui-ngx/src/assets/locale/locale.constant-en_US.json

4
application/pom.xml

@ -145,10 +145,6 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>

2
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;

3
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;

3
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;

5
application/src/main/java/org/thingsboard/server/controller/TelemetryController.java

@ -392,6 +392,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<Void>() {

12
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;
@ -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,16 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
}
}
private void updateTenantState(TenantApiUsageState state, TenantProfile tenantProfile) {
private void updateTenantState(TenantApiUsageState state, TenantProfile profile) {
TenantProfileData oldProfileData = state.getTenantProfileData();
state.setTenantProfileId(tenantProfile.getId());
state.setTenantProfileData(tenantProfile.getProfileData());
state.setTenantProfileId(profile.getId());
state.setTenantProfileData(profile.getProfileData());
Map<ApiFeature, ApiUsageStateValue> 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,

3
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);

1
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java

@ -274,6 +274,7 @@ public class DefaultTbClusterService implements TbClusterService {
TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer();
Set<String> 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<TbProtoQueueMsg<ToCoreNotificationMsg>> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer();

2
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;

2
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;

3
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

4
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;
@ -153,6 +153,8 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
TbActorMsg actorMsg = actorMsgOpt.get();
if (actorMsg instanceof ComponentLifecycleMsg) {
ComponentLifecycleMsg componentLifecycleMsg = (ComponentLifecycleMsg) actorMsg;
log.info("[{}][{}][{}] Received Lifecycle event: {}", componentLifecycleMsg.getTenantId(), componentLifecycleMsg.getEntityId().getEntityType(),
componentLifecycleMsg.getEntityId(), componentLifecycleMsg.getEvent());
if (EntityType.TENANT_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
TenantProfileId tenantProfileId = new TenantProfileId(componentLifecycleMsg.getEntityId().getId());
tenantProfileCache.evict(tenantProfileId);

13
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);
}

14
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()));
}

10
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;

7
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;
}

2
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)));
}
}

27
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;
@ -164,22 +164,25 @@ public class DefaultTransportApiService implements TransportApiService {
}
private ListenableFuture<TransportApiResponseMsg> 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 (!StringUtils.isEmpty(mqtt.getUserName())) {
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 {

1
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:

2
application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java → 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;

2
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
}

4
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;

4
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);
}

14
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java

@ -122,7 +122,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();
}
@ -464,8 +470,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);

24
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 <T> void mergeLimits(T deviceId, EntityTransportRateLimits newRateLimits,
Function<T, EntityTransportRateLimits> getFunction,
BiConsumer<T, EntityTransportRateLimits> putFunction) {
EntityTransportRateLimits oldRateLimits = getFunction.apply(deviceId);
private <T extends EntityId> void mergeLimits(T entityId, EntityTransportRateLimits newRateLimits,
Function<T, EntityTransportRateLimits> getFunction,
BiConsumer<T, EntityTransportRateLimits> 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);
}
}
}

13
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));
}
}

23
dao/src/main/java/org/thingsboard/server/dao/TenantEntityDao.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;
import org.thingsboard.server.common.data.id.TenantId;
public interface TenantEntityDao {
Long countByTenantId(TenantId tenantId);
}

3
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<Asset> {
public interface AssetDao extends Dao<Asset>, TenantEntityDao {
/**
* Find asset info by id.

11
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,10 @@ 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();
validateNumberOfEntitiesPerTenant(tenantId, assetDao, maxAssets, EntityType.ASSET);
}
@Override

5
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<Customer> {
public interface CustomerDao extends Dao<Customer>, TenantEntityDao {
/**
* Save or update customer object
@ -54,5 +55,5 @@ public interface CustomerDao extends Dao<Customer> {
* @return the optional customer object
*/
Optional<Customer> findCustomersByTenantIdAndTitle(UUID tenantId, String title);
}

13
dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java

@ -21,13 +21,16 @@ 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.EntityType;
import org.thingsboard.server.common.data.Tenant;
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 +41,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 +79,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 +170,11 @@ 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();
validateNumberOfEntitiesPerTenant(tenantId, customerDao, maxCustomers, EntityType.CUSTOMER);
customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent(
c -> {
throw new DataValidationException("Customer with such title already exists!");

4
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<Dashboard> {
public interface DashboardDao extends Dao<Dashboard>, TenantEntityDao {
/**
* Save or update dashboard object
@ -31,5 +32,4 @@ public interface DashboardDao extends Dao<Dashboard> {
* @return saved dashboard object
*/
Dashboard save(TenantId tenantId, Dashboard dashboard);
}

16
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java

@ -19,10 +19,12 @@ 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;
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;
@ -31,12 +33,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 +65,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 +222,14 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
private DataValidator<Dashboard> dashboardValidator =
new DataValidator<Dashboard>() {
@Override
protected void validateCreate(TenantId tenantId, Dashboard data) {
DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
long maxDashboards = profileConfiguration.getMaxDashboards();
validateNumberOfEntitiesPerTenant(tenantId, dashboardDao, maxDashboards, EntityType.DASHBOARD);
}
@Override
protected void validateDataImpl(TenantId tenantId, Dashboard dashboard) {
if (StringUtils.isEmpty(dashboard.getTitle())) {

3
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<Device> {
public interface DeviceDao extends Dao<Device>, TenantEntityDao {
/**
* Find device info by id.

13
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,10 @@ 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();
validateNumberOfEntitiesPerTenant(tenantId, deviceDao, maxDevices, EntityType.DEVICE);
}
@Override
@ -532,7 +543,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
@Override
protected void validateDataImpl(TenantId tenantId, Device device) {
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) {

5
dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java

@ -37,12 +37,11 @@ public abstract class AbstractEntityService {
protected Optional<ConstraintViolationException> 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();
}
}
}

15
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,14 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
private DataValidator<RuleChain> ruleChainValidator =
new DataValidator<RuleChain>() {
@Override
protected void validateCreate(TenantId tenantId, RuleChain data) {
DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
long maxRuleChains = profileConfiguration.getMaxRuleChains();
validateNumberOfEntitiesPerTenant(tenantId, ruleChainDao, maxRuleChains, EntityType.RULE_CHAIN);
}
@Override
protected void validateDataImpl(TenantId tenantId, RuleChain ruleChain) {
if (StringUtils.isEmpty(ruleChain.getName())) {

4
dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java

@ -19,13 +19,14 @@ 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<RuleChain> {
public interface RuleChainDao extends Dao<RuleChain>, TenantEntityDao {
/**
* Find rule chains by tenantId and page link.
@ -35,5 +36,4 @@ public interface RuleChainDao extends Dao<RuleChain> {
* @return the list of rule chain objects
*/
PageData<RuleChain> findRuleChainsByTenantId(UUID tenantId, PageLink pageLink);
}

15
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<D extends BaseData<?>> {
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<String> expectedFields = new HashSet<>();
Iterator<String> fieldsIterator = expectedNode.fieldNames();

1
dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java

@ -122,4 +122,5 @@ public interface AssetRepository extends PagingAndSortingRepository<AssetEntity,
@Query("SELECT DISTINCT a.type FROM AssetEntity a WHERE a.tenantId = :tenantId")
List<String> findTenantAssetTypes(@Param("tenantId") UUID tenantId);
Long countByTenantId(UUID tenantId);
}

5
dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java

@ -176,4 +176,9 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao<AssetEntity, Asset> im
}
return list;
}
@Override
public Long countByTenantId(TenantId tenantId) {
return assetRepository.countByTenantId(tenantId.getId());
}
}

1
dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java

@ -37,4 +37,5 @@ public interface CustomerRepository extends PagingAndSortingRepository<CustomerE
CustomerEntity findByTenantIdAndTitle(UUID tenantId, String title);
Long countByTenantId(UUID tenantId);
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.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.Customer;
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.DaoUtil;
@ -62,4 +63,9 @@ public class JpaCustomerDao extends JpaAbstractSearchTextDao<CustomerEntity, Cus
Customer customer = DaoUtil.getData(customerRepository.findByTenantIdAndTitle(tenantId, title));
return Optional.ofNullable(customer);
}
@Override
public Long countByTenantId(TenantId tenantId) {
return customerRepository.countByTenantId(tenantId.getId());
}
}

2
dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardRepository.java

@ -24,4 +24,6 @@ import java.util.UUID;
* Created by Valerii Sosliuk on 5/6/2017.
*/
public interface DashboardRepository extends CrudRepository<DashboardEntity, UUID> {
Long countByTenantId(UUID tenantId);
}

6
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<DashboardEntity, D
protected CrudRepository<DashboardEntity, UUID> getCrudRepository() {
return dashboardRepository;
}
@Override
public Long countByTenantId(TenantId tenantId) {
return dashboardRepository.countByTenantId(tenantId.getId());
}
}

28
dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java

@ -50,9 +50,9 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit
"AND d.deviceProfileId = :profileId " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
Page<DeviceEntity> 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<DeviceEntit
"AND d.customerId = :customerId " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
Page<DeviceInfoEntity> 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<DeviceEntity> findByTenantId(@Param("tenantId") UUID tenantId,
@ -102,9 +102,9 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit
"AND d.type = :type " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))")
Page<DeviceInfoEntity> 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<DeviceEntit
"AND d.type = :type " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))")
Page<DeviceInfoEntity> 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 " +
@ -168,4 +168,6 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit
DeviceEntity findByTenantIdAndId(UUID tenantId, UUID id);
Long countByDeviceProfileId(UUID deviceProfileId);
Long countByTenantId(UUID tenantId);
}

5
dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java

@ -219,6 +219,11 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device>
return deviceRepository.countByDeviceProfileId(deviceProfileId);
}
@Override
public Long countByTenantId(TenantId tenantId) {
return deviceRepository.countByTenantId(tenantId.getId());
}
private List<EntitySubtype> convertTenantDeviceTypesToDto(UUID tenantId, List<String> types) {
List<EntitySubtype> list = Collections.emptyList();
if (types != null && !types.isEmpty()) {

5
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, "(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, SELECT_API_USAGE_STATE);
}
public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{

2
dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java

@ -80,6 +80,7 @@ public class EntityKeyMapping {
public static final List<String> labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, ADDITIONAL_INFO);
public static final List<String> contactBasedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, EMAIL, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO);
public static final Set<String> apiUsageStateEntityFields = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME));
public static final Set<String> commonEntityFieldsSet = new HashSet<>(commonEntityFields);
public static final Set<String> 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 +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, apiUsageStateEntityFields);
entityFieldColumnMap.put(CREATED_TIME, ModelConstants.CREATED_TIME_PROPERTY);
entityFieldColumnMap.put(ENTITY_TYPE, ModelConstants.ENTITY_TYPE_PROPERTY);

5
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<RuleChainEntity, R
DaoUtil.toPageable(pageLink)));
}
@Override
public Long countByTenantId(TenantId tenantId) {
return ruleChainRepository.countByTenantId(tenantId.getId());
}
}

1
dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java

@ -32,4 +32,5 @@ public interface RuleChainRepository extends PagingAndSortingRepository<RuleChai
@Param("searchText") String searchText,
Pageable pageable);
Long countByTenantId(UUID tenantId);
}

5
dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java

@ -91,4 +91,9 @@ public class JpaUserDao extends JpaAbstractSearchTextDao<UserEntity, User> imple
DaoUtil.toPageable(pageLink)));
}
@Override
public Long countByTenantId(TenantId tenantId) {
return userRepository.countByTenantId(tenantId.getId());
}
}

1
dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java

@ -47,4 +47,5 @@ public interface UserRepository extends PagingAndSortingRepository<UserEntity, U
@Param("searchText") String searchText,
Pageable pageable);
Long countByTenantId(UUID tenantId);
}

9
application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java → 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;

14
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;
@ -83,7 +85,19 @@ 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<TsKvEntry> apiUsageStates = new ArrayList<>();
apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(),
new StringDataEntry(ApiFeature.TRANSPORT.getApiStateKey(), ApiUsageStateValue.ENABLED.name())));
apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(),
new StringDataEntry(ApiFeature.DB.getApiStateKey(), ApiUsageStateValue.ENABLED.name())));
apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(),
new StringDataEntry(ApiFeature.RE.getApiStateKey(), ApiUsageStateValue.ENABLED.name())));
apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(),
new StringDataEntry(ApiFeature.JS.getApiStateKey(), ApiUsageStateValue.ENABLED.name())));
tsService.save(tenantId, saved.getId(), apiUsageStates, 0L);
List<TsKvEntry> profileThresholds = new ArrayList<>();
for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) {
profileThresholds.add(new BasicTsKvEntry(saved.getCreatedTime(), new LongDataEntry(key.getApiLimitKey(), configuration.getProfileThreshold(key))));
}

8
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<User> {
public interface UserDao extends Dao<User>, TenantEntityDao {
/**
* Save or update user object
@ -49,7 +50,7 @@ public interface UserDao extends Dao<User> {
* @return the list of user entities
*/
PageData<User> findByTenantId(UUID tenantId, PageLink pageLink);
/**
* Find tenant admin users by tenantId and page link.
*
@ -58,7 +59,7 @@ public interface UserDao extends Dao<User> {
* @return the list of user entities
*/
PageData<User> findTenantAdmins(UUID tenantId, PageLink pageLink);
/**
* Find customer users by tenantId, customerId and page link.
*
@ -68,5 +69,4 @@ public interface UserDao extends Dao<User> {
* @return the list of user entities
*/
PageData<User> findCustomerUsers(UUID tenantId, UUID customerId, PageLink pageLink);
}

18
dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java

@ -24,8 +24,10 @@ 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.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId;
@ -36,6 +38,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 +46,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 +88,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 +372,16 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
private DataValidator<User> userValidator =
new DataValidator<User>() {
@Override
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();
validateNumberOfEntitiesPerTenant(tenantId, userDao, maxUsers, EntityType.USER);
}
}
@Override
protected void validateDataImpl(TenantId requestTenantId, User user) {
if (StringUtils.isEmpty(user.getEmail())) {

2
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.

32
pom.xml

@ -36,12 +36,11 @@
<pkg.implementationTitle>${project.name}</pkg.implementationTitle>
<pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
<spring-boot.version>2.2.6.RELEASE</spring-boot.version>
<spring-oauth2.version>2.1.2.RELEASE</spring-oauth2.version>
<spring.version>5.2.6.RELEASE</spring.version>
<spring-security.version>5.2.3.RELEASE</spring-security.version>
<spring-data-redis.version>2.2.4.RELEASE</spring-data-redis.version>
<jedis.version>3.1.0</jedis.version>
<spring-boot.version>2.3.5.RELEASE</spring-boot.version>
<spring.version>5.2.10.RELEASE</spring.version>
<spring-security.version>5.4.1</spring-security.version>
<spring-data-redis.version>2.4.1</spring-data-redis.version>
<jedis.version>3.3.0</jedis.version>
<jjwt.version>0.7.0</jjwt.version>
<json-path.version>2.2.0</json-path.version>
<junit.version>4.12</junit.version>
@ -52,15 +51,16 @@
<cassandra.version>4.6.0</cassandra.version>
<metrics.version>4.0.5</metrics.version>
<cassandra-unit.version>4.3.1.0</cassandra-unit.version>
<cassandra-all.version>3.11.9</cassandra-all.version>
<takari-cpsuite.version>1.2.7</takari-cpsuite.version>
<guava.version>28.2-jre</guava.version>
<caffeine.version>2.6.1</caffeine.version>
<commons-lang3.version>3.4</commons-lang3.version>
<commons-io.version>2.5</commons-io.version>
<commons-csv.version>1.4</commons-csv.version>
<jackson.version>2.10.2</jackson.version>
<jackson-annotations.version>2.10.2</jackson-annotations.version>
<jackson-core.version>2.10.2</jackson-core.version>
<jackson.version>2.11.3</jackson.version>
<jackson-annotations.version>2.11.3</jackson-annotations.version>
<jackson-core.version>2.11.3</jackson-core.version>
<json-schema-validator.version>2.2.6</json-schema-validator.version>
<californium.version>1.0.2</californium.version>
<gson.version>2.6.2</gson.version>
@ -72,7 +72,7 @@
<grpc.version>1.22.1</grpc.version>
<lombok.version>1.16.18</lombok.version>
<paho.client.version>1.2.4</paho.client.version>
<netty.version>4.1.49.Final</netty.version>
<netty.version>4.1.53.Final</netty.version>
<os-maven-plugin.version>1.5.0</os-maven-plugin.version>
<rabbitmq.version>4.8.0</rabbitmq.version>
<surfire.version>2.19.1</surfire.version>
@ -96,7 +96,7 @@
<bucket4j.version>4.1.1</bucket4j.version>
<fst.version>2.57</fst.version>
<antlr.version>2.7.7</antlr.version>
<snakeyaml.version>1.25</snakeyaml.version>
<snakeyaml.version>1.27</snakeyaml.version>
<amazonaws.sqs.version>1.11.747</amazonaws.sqs.version>
<pubsub.client.version>1.105.0</pubsub.client.version>
<azure-servicebus.version>3.2.0</azure-servicebus.version>
@ -872,11 +872,6 @@
<artifactId>spring-boot-starter-security</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>${spring-oauth2.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
@ -1200,6 +1195,11 @@
<version>${cassandra-unit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.cassandra</groupId>
<artifactId>cassandra-all</artifactId>
<version>${cassandra-all.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>

5
rule-engine/rule-engine-components/pom.xml

@ -120,6 +120,11 @@
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>

77
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;
@ -53,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;
@ -64,6 +67,7 @@ class AlarmState {
initCurrentAlarm(ctx);
lastMsgMetaData = msg.getMetaData();
lastMsgQueueName = msg.getQueueName();
this.dataSnapshot = data;
return createOrClearAlarms(ctx, data, update, AlarmRuleState::eval);
}
@ -74,7 +78,7 @@ class AlarmState {
public <T> boolean createOrClearAlarms(TbContext ctx, T data, SnapshotUpdate update, BiFunction<AlarmRuleState, T, AlarmEvalResult> 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,18 +88,18 @@ 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();
stateUpdate = clearAlarmState(stateUpdate, state);
}
}
if (resultSeverity != null) {
TbAlarmResult result = calculateAlarmResult(ctx, resultSeverity);
if (resultState != null) {
TbAlarmResult result = calculateAlarmResult(ctx, resultState);
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());
@ -103,23 +107,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.
@ -187,7 +194,8 @@ class AlarmState {
}
}
private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmSeverity severity) {
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).
// Maybe we should fetch alarm every time?
@ -213,7 +221,7 @@ class AlarmState {
currentAlarm.setSeverity(severity);
currentAlarm.setStartTs(System.currentTimeMillis());
currentAlarm.setEndTs(currentAlarm.getStartTs());
currentAlarm.setDetails(JacksonUtil.OBJECT_MAPPER.createObjectNode());
currentAlarm.setDetails(createDetails(ruleState));
currentAlarm.setOriginator(originator);
currentAlarm.setTenantId(ctx.getTenantId());
currentAlarm.setPropagate(alarmDefinition.isPropagate());
@ -226,13 +234,50 @@ class AlarmState {
}
}
private JsonNode createDetails(AlarmRuleState ruleState) {
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 = entityKeyValue.getLngValue();
break;
case DOUBLE:
result = entityKeyValue.getDblValue();
break;
case BOOLEAN:
result = 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())) {
currentAlarm = null;
for (AlarmRuleState state : createRulesSortedBySeverityDesc) {
state.clear();
updated |= state.checkUpdate();
updated = clearAlarmState(updated, state);
}
}
return updated;

1
tools/pom.xml

@ -54,7 +54,6 @@
<dependency>
<groupId>org.apache.cassandra</groupId>
<artifactId>cassandra-all</artifactId>
<version>3.11.6</version>
</dependency>
<dependency>
<groupId>com.datastax.oss</groupId>

3
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;
}

6
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;
}

3
ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html

@ -63,6 +63,9 @@
</mat-error>
</mat-form-field>
</div>
<mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration').hasError('unique')">
{{ 'device-profile.mqtt-device-topic-filters-unique' | translate }}
</mat-error>
<div class="tb-hint" innerHTML="{{ 'device-profile.support-level-wildcards' | translate }}"></div>
<div class="tb-hint" innerHTML="{{ 'device-profile.single-level-wildcards-hint' | translate }}"></div>
<div class="tb-hint" innerHTML="{{ 'device-profile.multi-level-wildcards-hint' | translate }}"></div>

13
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;
}
}

48
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}}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>tenant-profile.maximum-customers</mat-label>
<input matInput required min="0" step="1"
formControlName="maxCustomers"
type="number">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxCustomers').hasError('required')">
{{ 'tenant-profile.maximum-customers-required' | translate}}
</mat-error>
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxCustomers').hasError('min')">
{{ 'tenant-profile.maximum-customers-range' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>tenant-profile.maximum-users</mat-label>
<input matInput required min="0" step="1"
formControlName="maxUsers"
type="number">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxUsers').hasError('required')">
{{ 'tenant-profile.maximum-users-required' | translate}}
</mat-error>
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxUsers').hasError('min')">
{{ 'tenant-profile.maximum-users-range' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>tenant-profile.maximum-dashboards</mat-label>
<input matInput required min="0" step="1"
formControlName="maxDashboards"
type="number">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxDashboards').hasError('required')">
{{ 'tenant-profile.maximum-dashboards-required' | translate}}
</mat-error>
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxDashboards').hasError('min')">
{{ 'tenant-profile.maximum-dashboards-range' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>tenant-profile.maximum-rule-chains</mat-label>
<input matInput required min="0" step="1"
formControlName="maxRuleChains"
type="number">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxRuleChains').hasError('required')">
{{ 'tenant-profile.maximum-rule-chains-required' | translate}}
</mat-error>
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxRuleChains').hasError('min')">
{{ 'tenant-profile.maximum-rule-chains-range' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>tenant-profile.max-transport-messages</mat-label>
<input matInput required min="0" step="1"

4
ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts

@ -55,6 +55,10 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA
this.defaultTenantProfileConfigurationFormGroup = this.fb.group({
maxDevices: [null, [Validators.required, Validators.min(0)]],
maxAssets: [null, [Validators.required, Validators.min(0)]],
maxCustomers: [null, [Validators.required, Validators.min(0)]],
maxUsers: [null, [Validators.required, Validators.min(0)]],
maxDashboards: [null, [Validators.required, Validators.min(0)]],
maxRuleChains: [null, [Validators.required, Validators.min(0)]],
transportTenantMsgRateLimit: [null, []],
transportTenantTelemetryMsgRateLimit: [null, []],
transportTenantTelemetryDataPointsRateLimit: [null, []],

18
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');
@ -410,10 +413,15 @@ 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<any>): Array<any> {

2
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);
}

22
ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts

@ -259,9 +259,27 @@ export class ImageMap extends LeafletMap {
convertToCustomFormat(position: L.LatLng, width = this.width, height = this.height): object {
const 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
};
}

23
ui-ngx/src/app/shared/models/ace/service-completion.models.ts

@ -56,6 +56,12 @@ export const customerHref = '<a href="https://github.com/thingsboard/thingsboard
export const attributeDataHref = '<a href="https://github.com/thingsboard/thingsboard/blob/13e6b10b7ab830e64d31b99614a9d95a1a25928a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts#L76">Attribute Data</a>';
export const timeseriesDataHref = '<a href="https://github.com/thingsboard/thingsboard/blob/627c0577b08452308f925cecb3860e35292c649e/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts#L91">Timeseries Data</a>';
export const aggregationTypeHref = '<a href="https://github.com/thingsboard/thingsboard/blob/a8ea887eacf7729e603ace13ce2d7d89dae82931/ui-ngx/src/app/shared/models/time/time.models.ts#L54">Aggregation Type</a>';
export const dataSortOrderHref = '<a href="https://github.com/thingsboard/thingsboard/blob/627c0577b08452308f925cecb3860e35292c649e/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts#L95">Data Sort Order</a>';
export const userHref = '<a href="https://github.com/thingsboard/thingsboard/blob/13e6b10b7ab830e64d31b99614a9d95a1a25928a/ui-ngx/src/app/shared/models/user.model.ts#L23">User</a>';
export const entityDataHref = '<a href="https://github.com/thingsboard/thingsboard/blob/master/ui-ngx/src/app/shared/models/query/query.models.ts#L567">Entity data</a>';
@ -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&lt;string&gt;`, 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: {

9
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<EntityType | AliasEntityType, Enti
selectedEntities: 'alarm.selected-alarms'
}
],
[
EntityType.API_USAGE_STATE,
{
type: 'entity.type-api-usage-state'
}
],
[
EntityType.WIDGETS_BUNDLE,
{

8
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,

16
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",
@ -1099,7 +1100,8 @@
"details": "Entity details",
"no-entities-prompt": "No entities found",
"no-data": "No data to display",
"columns-to-display": "Columns to Display"
"columns-to-display": "Columns to Display",
"type-api-usage-state": "Api Usage State"
},
"entity-field": {
"created-time": "Created time",
@ -1944,6 +1946,18 @@
"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",
"maximum-customers": "Maximum number of customers (0 - unlimited)",
"maximum-customers-required": "Maximum number of customers is required.",
"maximum-customers-range": "Maximum number of customers can't be negative",
"maximum-users": "Maximum number of users (0 - unlimited)",
"maximum-users-required": "Maximum number of users is required.",
"maximum-users-range": "Maximum number of users can't be negative",
"maximum-dashboards": "Maximum number of dashboards (0 - unlimited)",
"maximum-dashboards-required": "Maximum number of dashboards is required.",
"maximum-dashboards-range": "Maximum number of dashboards can't be negative",
"maximum-rule-chains": "Maximum number of rule chains (0 - unlimited)",
"maximum-rule-chains-required": "Maximum number of rule chains is required.",
"maximum-rule-chains-range": "Maximum number of rule chains 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.",

Loading…
Cancel
Save