Commit 10e6b9a458d7d49596f3fc89ead24bbec6f3c74b

Authored by Andrii Shvaika
2 parents a87a3d68 c1737d0d

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

Showing 42 changed files with 292 additions and 78 deletions
... ... @@ -146,10 +146,6 @@
146 146 <artifactId>spring-boot-starter-websocket</artifactId>
147 147 </dependency>
148 148 <dependency>
149   - <groupId>org.springframework.cloud</groupId>
150   - <artifactId>spring-cloud-starter-oauth2</artifactId>
151   - </dependency>
152   - <dependency>
153 149 <groupId>org.springframework.security</groupId>
154 150 <artifactId>spring-security-oauth2-client</artifactId>
155 151 </dependency>
... ...
... ... @@ -24,6 +24,10 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
24 24
25 25 private long maxDevices;
26 26 private long maxAssets;
  27 + private long maxCustomers;
  28 + private long maxUsers;
  29 + private long maxDashboards;
  30 + private long maxRuleChains;
27 31
28 32 private String transportTenantMsgRateLimit;
29 33 private String transportTenantTelemetryMsgRateLimit;
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao;
  17 +
  18 +import org.thingsboard.server.common.data.id.TenantId;
  19 +
  20 +public interface TenantEntityDao {
  21 +
  22 + Long countByTenantId(TenantId tenantId);
  23 +}
... ...
... ... @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId;
23 23 import org.thingsboard.server.common.data.page.PageData;
24 24 import org.thingsboard.server.common.data.page.PageLink;
25 25 import org.thingsboard.server.dao.Dao;
  26 +import org.thingsboard.server.dao.TenantEntityDao;
26 27
27 28 import java.util.List;
28 29 import java.util.Optional;
... ... @@ -32,7 +33,7 @@ import java.util.UUID;
32 33 * The Interface AssetDao.
33 34 *
34 35 */
35   -public interface AssetDao extends Dao<Asset> {
  36 +public interface AssetDao extends Dao<Asset>, TenantEntityDao {
36 37
37 38 /**
38 39 * Find asset info by id.
... ... @@ -166,6 +167,4 @@ public interface AssetDao extends Dao<Asset> {
166 167 */
167 168 ListenableFuture<List<EntitySubtype>> findTenantAssetTypesAsync(UUID tenantId);
168 169
169   - Long countAssetsByTenantId(TenantId tenantId);
170   -
171 170 }
... ...
... ... @@ -330,12 +330,7 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
330 330 DefaultTenantProfileConfiguration profileConfiguration =
331 331 (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
332 332 long maxAssets = profileConfiguration.getMaxAssets();
333   - if (maxAssets > 0) {
334   - long currentAssetsCount = assetDao.countAssetsByTenantId(tenantId);
335   - if (maxAssets >= currentAssetsCount) {
336   - throw new DataValidationException("Can't create assets more then " + maxAssets);
337   - }
338   - }
  333 + validateNumberOfEntitiesPerTenant(tenantId, assetDao, maxAssets, EntityType.ASSET);
339 334 }
340 335
341 336 @Override
... ...
... ... @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.id.TenantId;
20 20 import org.thingsboard.server.common.data.page.PageData;
21 21 import org.thingsboard.server.common.data.page.PageLink;
22 22 import org.thingsboard.server.dao.Dao;
  23 +import org.thingsboard.server.dao.TenantEntityDao;
23 24
24 25 import java.util.Optional;
25 26 import java.util.UUID;
... ... @@ -27,7 +28,7 @@ import java.util.UUID;
27 28 /**
28 29 * The Interface CustomerDao.
29 30 */
30   -public interface CustomerDao extends Dao<Customer> {
  31 +public interface CustomerDao extends Dao<Customer>, TenantEntityDao {
31 32
32 33 /**
33 34 * Save or update customer object
... ... @@ -54,5 +55,5 @@ public interface CustomerDao extends Dao<Customer> {
54 55 * @return the optional customer object
55 56 */
56 57 Optional<Customer> findCustomersByTenantIdAndTitle(UUID tenantId, String title);
57   -
  58 +
58 59 }
... ...
... ... @@ -21,13 +21,16 @@ import com.google.common.util.concurrent.ListenableFuture;
21 21 import lombok.extern.slf4j.Slf4j;
22 22 import org.apache.commons.lang3.StringUtils;
23 23 import org.springframework.beans.factory.annotation.Autowired;
  24 +import org.springframework.context.annotation.Lazy;
24 25 import org.springframework.stereotype.Service;
25 26 import org.thingsboard.server.common.data.Customer;
  27 +import org.thingsboard.server.common.data.EntityType;
26 28 import org.thingsboard.server.common.data.Tenant;
27 29 import org.thingsboard.server.common.data.id.CustomerId;
28 30 import org.thingsboard.server.common.data.id.TenantId;
29 31 import org.thingsboard.server.common.data.page.PageData;
30 32 import org.thingsboard.server.common.data.page.PageLink;
  33 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
31 34 import org.thingsboard.server.dao.asset.AssetService;
32 35 import org.thingsboard.server.dao.dashboard.DashboardService;
33 36 import org.thingsboard.server.dao.device.DeviceService;
... ... @@ -38,6 +41,7 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException;
38 41 import org.thingsboard.server.dao.service.DataValidator;
39 42 import org.thingsboard.server.dao.service.PaginatedRemover;
40 43 import org.thingsboard.server.dao.service.Validator;
  44 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
41 45 import org.thingsboard.server.dao.tenant.TenantDao;
42 46 import org.thingsboard.server.dao.user.UserService;
43 47
... ... @@ -75,6 +79,10 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
75 79 @Autowired
76 80 private DashboardService dashboardService;
77 81
  82 + @Autowired
  83 + @Lazy
  84 + private TbTenantProfileCache tenantProfileCache;
  85 +
78 86 @Override
79 87 public Customer findCustomerById(TenantId tenantId, CustomerId customerId) {
80 88 log.trace("Executing findCustomerById [{}]", customerId);
... ... @@ -162,6 +170,11 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
162 170
163 171 @Override
164 172 protected void validateCreate(TenantId tenantId, Customer customer) {
  173 + DefaultTenantProfileConfiguration profileConfiguration =
  174 + (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
  175 + long maxCustomers = profileConfiguration.getMaxCustomers();
  176 +
  177 + validateNumberOfEntitiesPerTenant(tenantId, customerDao, maxCustomers, EntityType.CUSTOMER);
165 178 customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent(
166 179 c -> {
167 180 throw new DataValidationException("Customer with such title already exists!");
... ...
... ... @@ -18,11 +18,12 @@ package org.thingsboard.server.dao.dashboard;
18 18 import org.thingsboard.server.common.data.Dashboard;
19 19 import org.thingsboard.server.common.data.id.TenantId;
20 20 import org.thingsboard.server.dao.Dao;
  21 +import org.thingsboard.server.dao.TenantEntityDao;
21 22
22 23 /**
23 24 * The Interface DashboardDao.
24 25 */
25   -public interface DashboardDao extends Dao<Dashboard> {
  26 +public interface DashboardDao extends Dao<Dashboard>, TenantEntityDao {
26 27
27 28 /**
28 29 * Save or update dashboard object
... ... @@ -31,5 +32,4 @@ public interface DashboardDao extends Dao<Dashboard> {
31 32 * @return saved dashboard object
32 33 */
33 34 Dashboard save(TenantId tenantId, Dashboard dashboard);
34   -
35 35 }
... ...
... ... @@ -19,10 +19,12 @@ import com.google.common.util.concurrent.ListenableFuture;
19 19 import lombok.extern.slf4j.Slf4j;
20 20 import org.apache.commons.lang3.StringUtils;
21 21 import org.springframework.beans.factory.annotation.Autowired;
  22 +import org.springframework.context.annotation.Lazy;
22 23 import org.springframework.stereotype.Service;
23 24 import org.thingsboard.server.common.data.Customer;
24 25 import org.thingsboard.server.common.data.Dashboard;
25 26 import org.thingsboard.server.common.data.DashboardInfo;
  27 +import org.thingsboard.server.common.data.EntityType;
26 28 import org.thingsboard.server.common.data.Tenant;
27 29 import org.thingsboard.server.common.data.id.CustomerId;
28 30 import org.thingsboard.server.common.data.id.DashboardId;
... ... @@ -31,12 +33,14 @@ import org.thingsboard.server.common.data.page.PageData;
31 33 import org.thingsboard.server.common.data.page.PageLink;
32 34 import org.thingsboard.server.common.data.relation.EntityRelation;
33 35 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
  36 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
34 37 import org.thingsboard.server.dao.customer.CustomerDao;
35 38 import org.thingsboard.server.dao.entity.AbstractEntityService;
36 39 import org.thingsboard.server.dao.exception.DataValidationException;
37 40 import org.thingsboard.server.dao.service.DataValidator;
38 41 import org.thingsboard.server.dao.service.PaginatedRemover;
39 42 import org.thingsboard.server.dao.service.Validator;
  43 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
40 44 import org.thingsboard.server.dao.tenant.TenantDao;
41 45
42 46 import java.util.concurrent.ExecutionException;
... ... @@ -61,6 +65,10 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
61 65 @Autowired
62 66 private CustomerDao customerDao;
63 67
  68 + @Autowired
  69 + @Lazy
  70 + private TbTenantProfileCache tenantProfileCache;
  71 +
64 72 @Override
65 73 public Dashboard findDashboardById(TenantId tenantId, DashboardId dashboardId) {
66 74 log.trace("Executing findDashboardById [{}]", dashboardId);
... ... @@ -215,6 +223,14 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
215 223 private DataValidator<Dashboard> dashboardValidator =
216 224 new DataValidator<Dashboard>() {
217 225 @Override
  226 + protected void validateCreate(TenantId tenantId, Dashboard data) {
  227 + DefaultTenantProfileConfiguration profileConfiguration =
  228 + (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
  229 + long maxDashboards = profileConfiguration.getMaxDashboards();
  230 + validateNumberOfEntitiesPerTenant(tenantId, dashboardDao, maxDashboards, EntityType.DASHBOARD);
  231 + }
  232 +
  233 + @Override
218 234 protected void validateDataImpl(TenantId tenantId, Dashboard dashboard) {
219 235 if (StringUtils.isEmpty(dashboard.getTitle())) {
220 236 throw new DataValidationException("Dashboard title should be specified!");
... ...
... ... @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId;
23 23 import org.thingsboard.server.common.data.page.PageData;
24 24 import org.thingsboard.server.common.data.page.PageLink;
25 25 import org.thingsboard.server.dao.Dao;
  26 +import org.thingsboard.server.dao.TenantEntityDao;
26 27
27 28 import java.util.List;
28 29 import java.util.Optional;
... ... @@ -32,7 +33,7 @@ import java.util.UUID;
32 33 * The Interface DeviceDao.
33 34 *
34 35 */
35   -public interface DeviceDao extends Dao<Device> {
  36 +public interface DeviceDao extends Dao<Device>, TenantEntityDao {
36 37
37 38 /**
38 39 * Find device info by id.
... ... @@ -203,8 +204,6 @@ public interface DeviceDao extends Dao<Device> {
203 204 */
204 205 ListenableFuture<Device> findDeviceByTenantIdAndIdAsync(TenantId tenantId, UUID id);
205 206
206   - Long countDevicesByTenantId(TenantId tenantId);
207   -
208 207 Long countDevicesByDeviceProfileId(TenantId tenantId, UUID deviceProfileId);
209 208
210 209 /**
... ...
... ... @@ -530,12 +530,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
530 530 DefaultTenantProfileConfiguration profileConfiguration =
531 531 (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
532 532 long maxDevices = profileConfiguration.getMaxDevices();
533   - if (maxDevices > 0) {
534   - long currentDevicesCount = deviceDao.countDevicesByTenantId(tenantId);
535   - if (maxDevices >= currentDevicesCount) {
536   - throw new DataValidationException("Can't create devices more then " + maxDevices);
537   - }
538   - }
  533 + validateNumberOfEntitiesPerTenant(tenantId, deviceDao, maxDevices, EntityType.DEVICE);
539 534 }
540 535
541 536 @Override
... ...
... ... @@ -37,12 +37,11 @@ public abstract class AbstractEntityService {
37 37
38 38 protected Optional<ConstraintViolationException> extractConstraintViolationException(Exception t) {
39 39 if (t instanceof ConstraintViolationException) {
40   - return Optional.of ((ConstraintViolationException) t);
  40 + return Optional.of((ConstraintViolationException) t);
41 41 } else if (t.getCause() instanceof ConstraintViolationException) {
42   - return Optional.of ((ConstraintViolationException) (t.getCause()));
  42 + return Optional.of((ConstraintViolationException) (t.getCause()));
43 43 } else {
44 44 return Optional.empty();
45 45 }
46 46 }
47   -
48 47 }
... ...
... ... @@ -24,6 +24,7 @@ import org.apache.commons.collections.CollectionUtils;
24 24 import org.apache.commons.lang3.StringUtils;
25 25 import org.hibernate.exception.ConstraintViolationException;
26 26 import org.springframework.beans.factory.annotation.Autowired;
  27 +import org.springframework.context.annotation.Lazy;
27 28 import org.springframework.stereotype.Service;
28 29 import org.thingsboard.server.common.data.BaseData;
29 30 import org.thingsboard.server.common.data.EntityType;
... ... @@ -44,11 +45,13 @@ import org.thingsboard.server.common.data.rule.RuleChainData;
44 45 import org.thingsboard.server.common.data.rule.RuleChainImportResult;
45 46 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
46 47 import org.thingsboard.server.common.data.rule.RuleNode;
  48 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
47 49 import org.thingsboard.server.dao.entity.AbstractEntityService;
48 50 import org.thingsboard.server.dao.exception.DataValidationException;
49 51 import org.thingsboard.server.dao.service.DataValidator;
50 52 import org.thingsboard.server.dao.service.PaginatedRemover;
51 53 import org.thingsboard.server.dao.service.Validator;
  54 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
52 55 import org.thingsboard.server.dao.tenant.TenantDao;
53 56
54 57 import java.util.ArrayList;
... ... @@ -81,6 +84,10 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
81 84 @Autowired
82 85 private TenantDao tenantDao;
83 86
  87 + @Autowired
  88 + @Lazy
  89 + private TbTenantProfileCache tenantProfileCache;
  90 +
84 91 @Override
85 92 public RuleChain saveRuleChain(RuleChain ruleChain) {
86 93 ruleChainValidator.validate(ruleChain, RuleChain::getTenantId);
... ... @@ -581,6 +588,14 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
581 588 private DataValidator<RuleChain> ruleChainValidator =
582 589 new DataValidator<RuleChain>() {
583 590 @Override
  591 + protected void validateCreate(TenantId tenantId, RuleChain data) {
  592 + DefaultTenantProfileConfiguration profileConfiguration =
  593 + (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
  594 + long maxRuleChains = profileConfiguration.getMaxRuleChains();
  595 + validateNumberOfEntitiesPerTenant(tenantId, ruleChainDao, maxRuleChains, EntityType.RULE_CHAIN);
  596 + }
  597 +
  598 + @Override
584 599 protected void validateDataImpl(TenantId tenantId, RuleChain ruleChain) {
585 600 if (StringUtils.isEmpty(ruleChain.getName())) {
586 601 throw new DataValidationException("Rule chain name should be specified!.");
... ...
... ... @@ -19,13 +19,14 @@ import org.thingsboard.server.common.data.page.PageData;
19 19 import org.thingsboard.server.common.data.page.PageLink;
20 20 import org.thingsboard.server.common.data.rule.RuleChain;
21 21 import org.thingsboard.server.dao.Dao;
  22 +import org.thingsboard.server.dao.TenantEntityDao;
22 23
23 24 import java.util.UUID;
24 25
25 26 /**
26 27 * Created by igor on 3/12/18.
27 28 */
28   -public interface RuleChainDao extends Dao<RuleChain> {
  29 +public interface RuleChainDao extends Dao<RuleChain>, TenantEntityDao {
29 30
30 31 /**
31 32 * Find rule chains by tenantId and page link.
... ... @@ -35,5 +36,4 @@ public interface RuleChainDao extends Dao<RuleChain> {
35 36 * @return the list of rule chain objects
36 37 */
37 38 PageData<RuleChain> findRuleChainsByTenantId(UUID tenantId, PageLink pageLink);
38   -
39 39 }
... ...
... ... @@ -18,7 +18,9 @@ package org.thingsboard.server.dao.service;
18 18 import com.fasterxml.jackson.databind.JsonNode;
19 19 import lombok.extern.slf4j.Slf4j;
20 20 import org.thingsboard.server.common.data.BaseData;
  21 +import org.thingsboard.server.common.data.EntityType;
21 22 import org.thingsboard.server.common.data.id.TenantId;
  23 +import org.thingsboard.server.dao.TenantEntityDao;
22 24 import org.thingsboard.server.dao.exception.DataValidationException;
23 25
24 26 import java.util.HashSet;
... ... @@ -79,6 +81,19 @@ public abstract class DataValidator<D extends BaseData<?>> {
79 81 return emailMatcher.matches();
80 82 }
81 83
  84 + protected void validateNumberOfEntitiesPerTenant(TenantId tenantId,
  85 + TenantEntityDao tenantEntityDao,
  86 + long maxEntities,
  87 + EntityType entityType) {
  88 + if (maxEntities > 0) {
  89 + long currentEntitiesCount = tenantEntityDao.countByTenantId(tenantId);
  90 + if (currentEntitiesCount >= maxEntities) {
  91 + throw new DataValidationException(String.format("Can't create more then %d %ss!",
  92 + maxEntities, entityType.name().toLowerCase().replaceAll("_", " ")));
  93 + }
  94 + }
  95 + }
  96 +
82 97 protected static void validateJsonStructure(JsonNode expectedNode, JsonNode actualNode) {
83 98 Set<String> expectedFields = new HashSet<>();
84 99 Iterator<String> fieldsIterator = expectedNode.fieldNames();
... ...
... ... @@ -178,8 +178,7 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao<AssetEntity, Asset> im
178 178 }
179 179
180 180 @Override
181   - public Long countAssetsByTenantId(TenantId tenantId) {
  181 + public Long countByTenantId(TenantId tenantId) {
182 182 return assetRepository.countByTenantId(tenantId.getId());
183   -
184 183 }
185 184 }
... ...
... ... @@ -37,4 +37,5 @@ public interface CustomerRepository extends PagingAndSortingRepository<CustomerE
37 37
38 38 CustomerEntity findByTenantIdAndTitle(UUID tenantId, String title);
39 39
  40 + Long countByTenantId(UUID tenantId);
40 41 }
... ...
... ... @@ -19,6 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired;
19 19 import org.springframework.data.repository.CrudRepository;
20 20 import org.springframework.stereotype.Component;
21 21 import org.thingsboard.server.common.data.Customer;
  22 +import org.thingsboard.server.common.data.id.TenantId;
22 23 import org.thingsboard.server.common.data.page.PageData;
23 24 import org.thingsboard.server.common.data.page.PageLink;
24 25 import org.thingsboard.server.dao.DaoUtil;
... ... @@ -62,4 +63,9 @@ public class JpaCustomerDao extends JpaAbstractSearchTextDao<CustomerEntity, Cus
62 63 Customer customer = DaoUtil.getData(customerRepository.findByTenantIdAndTitle(tenantId, title));
63 64 return Optional.ofNullable(customer);
64 65 }
  66 +
  67 + @Override
  68 + public Long countByTenantId(TenantId tenantId) {
  69 + return customerRepository.countByTenantId(tenantId.getId());
  70 + }
65 71 }
... ...
... ... @@ -24,4 +24,6 @@ import java.util.UUID;
24 24 * Created by Valerii Sosliuk on 5/6/2017.
25 25 */
26 26 public interface DashboardRepository extends CrudRepository<DashboardEntity, UUID> {
  27 +
  28 + Long countByTenantId(UUID tenantId);
27 29 }
... ...
... ... @@ -19,6 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired;
19 19 import org.springframework.data.repository.CrudRepository;
20 20 import org.springframework.stereotype.Component;
21 21 import org.thingsboard.server.common.data.Dashboard;
  22 +import org.thingsboard.server.common.data.id.TenantId;
22 23 import org.thingsboard.server.dao.dashboard.DashboardDao;
23 24 import org.thingsboard.server.dao.model.sql.DashboardEntity;
24 25 import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
... ... @@ -43,4 +44,9 @@ public class JpaDashboardDao extends JpaAbstractSearchTextDao<DashboardEntity, D
43 44 protected CrudRepository<DashboardEntity, UUID> getCrudRepository() {
44 45 return dashboardRepository;
45 46 }
  47 +
  48 + @Override
  49 + public Long countByTenantId(TenantId tenantId) {
  50 + return dashboardRepository.countByTenantId(tenantId.getId());
  51 + }
46 52 }
... ...
... ... @@ -50,9 +50,9 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit
50 50 "AND d.deviceProfileId = :profileId " +
51 51 "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
52 52 Page<DeviceEntity> findByTenantIdAndProfileId(@Param("tenantId") UUID tenantId,
53   - @Param("profileId") UUID profileId,
54   - @Param("searchText") String searchText,
55   - Pageable pageable);
  53 + @Param("profileId") UUID profileId,
  54 + @Param("searchText") String searchText,
  55 + Pageable pageable);
56 56
57 57 @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " +
58 58 "FROM DeviceEntity d " +
... ... @@ -62,9 +62,9 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit
62 62 "AND d.customerId = :customerId " +
63 63 "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
64 64 Page<DeviceInfoEntity> findDeviceInfosByTenantIdAndCustomerId(@Param("tenantId") UUID tenantId,
65   - @Param("customerId") UUID customerId,
66   - @Param("searchText") String searchText,
67   - Pageable pageable);
  65 + @Param("customerId") UUID customerId,
  66 + @Param("searchText") String searchText,
  67 + Pageable pageable);
68 68
69 69 @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId")
70 70 Page<DeviceEntity> findByTenantId(@Param("tenantId") UUID tenantId,
... ... @@ -102,9 +102,9 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit
102 102 "AND d.type = :type " +
103 103 "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))")
104 104 Page<DeviceInfoEntity> findDeviceInfosByTenantIdAndType(@Param("tenantId") UUID tenantId,
105   - @Param("type") String type,
106   - @Param("textSearch") String textSearch,
107   - Pageable pageable);
  105 + @Param("type") String type,
  106 + @Param("textSearch") String textSearch,
  107 + Pageable pageable);
108 108
109 109 @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " +
110 110 "FROM DeviceEntity d " +
... ... @@ -137,10 +137,10 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit
137 137 "AND d.type = :type " +
138 138 "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))")
139 139 Page<DeviceInfoEntity> findDeviceInfosByTenantIdAndCustomerIdAndType(@Param("tenantId") UUID tenantId,
140   - @Param("customerId") UUID customerId,
141   - @Param("type") String type,
142   - @Param("textSearch") String textSearch,
143   - Pageable pageable);
  140 + @Param("customerId") UUID customerId,
  141 + @Param("type") String type,
  142 + @Param("textSearch") String textSearch,
  143 + Pageable pageable);
144 144
145 145 @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " +
146 146 "FROM DeviceEntity d " +
... ...
... ... @@ -220,7 +220,7 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device>
220 220 }
221 221
222 222 @Override
223   - public Long countDevicesByTenantId(TenantId tenantId) {
  223 + public Long countByTenantId(TenantId tenantId) {
224 224 return deviceRepository.countByTenantId(tenantId.getId());
225 225 }
226 226
... ...
... ... @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.beans.factory.annotation.Autowired;
20 20 import org.springframework.data.repository.CrudRepository;
21 21 import org.springframework.stereotype.Component;
  22 +import org.thingsboard.server.common.data.id.TenantId;
22 23 import org.thingsboard.server.common.data.page.PageData;
23 24 import org.thingsboard.server.common.data.page.PageLink;
24 25 import org.thingsboard.server.common.data.rule.RuleChain;
... ... @@ -56,4 +57,8 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao<RuleChainEntity, R
56 57 DaoUtil.toPageable(pageLink)));
57 58 }
58 59
  60 + @Override
  61 + public Long countByTenantId(TenantId tenantId) {
  62 + return ruleChainRepository.countByTenantId(tenantId.getId());
  63 + }
59 64 }
... ...
... ... @@ -32,4 +32,5 @@ public interface RuleChainRepository extends PagingAndSortingRepository<RuleChai
32 32 @Param("searchText") String searchText,
33 33 Pageable pageable);
34 34
  35 + Long countByTenantId(UUID tenantId);
35 36 }
... ...
... ... @@ -91,4 +91,9 @@ public class JpaUserDao extends JpaAbstractSearchTextDao<UserEntity, User> imple
91 91 DaoUtil.toPageable(pageLink)));
92 92
93 93 }
  94 +
  95 + @Override
  96 + public Long countByTenantId(TenantId tenantId) {
  97 + return userRepository.countByTenantId(tenantId.getId());
  98 + }
94 99 }
... ...
... ... @@ -47,4 +47,5 @@ public interface UserRepository extends PagingAndSortingRepository<UserEntity, U
47 47 @Param("searchText") String searchText,
48 48 Pageable pageable);
49 49
  50 + Long countByTenantId(UUID tenantId);
50 51 }
... ...
... ... @@ -20,10 +20,11 @@ import org.thingsboard.server.common.data.id.TenantId;
20 20 import org.thingsboard.server.common.data.page.PageData;
21 21 import org.thingsboard.server.common.data.page.PageLink;
22 22 import org.thingsboard.server.dao.Dao;
  23 +import org.thingsboard.server.dao.TenantEntityDao;
23 24
24 25 import java.util.UUID;
25 26
26   -public interface UserDao extends Dao<User> {
  27 +public interface UserDao extends Dao<User>, TenantEntityDao {
27 28
28 29 /**
29 30 * Save or update user object
... ... @@ -49,7 +50,7 @@ public interface UserDao extends Dao<User> {
49 50 * @return the list of user entities
50 51 */
51 52 PageData<User> findByTenantId(UUID tenantId, PageLink pageLink);
52   -
  53 +
53 54 /**
54 55 * Find tenant admin users by tenantId and page link.
55 56 *
... ... @@ -58,7 +59,7 @@ public interface UserDao extends Dao<User> {
58 59 * @return the list of user entities
59 60 */
60 61 PageData<User> findTenantAdmins(UUID tenantId, PageLink pageLink);
61   -
  62 +
62 63 /**
63 64 * Find customer users by tenantId, customerId and page link.
64 65 *
... ... @@ -68,5 +69,4 @@ public interface UserDao extends Dao<User> {
68 69 * @return the list of user entities
69 70 */
70 71 PageData<User> findCustomerUsers(UUID tenantId, UUID customerId, PageLink pageLink);
71   -
72 72 }
... ...
... ... @@ -24,8 +24,10 @@ import org.apache.commons.lang3.RandomStringUtils;
24 24 import org.apache.commons.lang3.StringUtils;
25 25 import org.springframework.beans.factory.annotation.Autowired;
26 26 import org.springframework.beans.factory.annotation.Value;
  27 +import org.springframework.context.annotation.Lazy;
27 28 import org.springframework.stereotype.Service;
28 29 import org.thingsboard.server.common.data.Customer;
  30 +import org.thingsboard.server.common.data.EntityType;
29 31 import org.thingsboard.server.common.data.Tenant;
30 32 import org.thingsboard.server.common.data.User;
31 33 import org.thingsboard.server.common.data.id.CustomerId;
... ... @@ -36,6 +38,7 @@ import org.thingsboard.server.common.data.page.PageData;
36 38 import org.thingsboard.server.common.data.page.PageLink;
37 39 import org.thingsboard.server.common.data.security.Authority;
38 40 import org.thingsboard.server.common.data.security.UserCredentials;
  41 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
39 42 import org.thingsboard.server.dao.customer.CustomerDao;
40 43 import org.thingsboard.server.dao.entity.AbstractEntityService;
41 44 import org.thingsboard.server.dao.exception.DataValidationException;
... ... @@ -43,6 +46,7 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException;
43 46 import org.thingsboard.server.dao.model.ModelConstants;
44 47 import org.thingsboard.server.dao.service.DataValidator;
45 48 import org.thingsboard.server.dao.service.PaginatedRemover;
  49 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
46 50 import org.thingsboard.server.dao.tenant.TenantDao;
47 51
48 52 import java.util.HashMap;
... ... @@ -84,6 +88,10 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
84 88 @Autowired
85 89 private CustomerDao customerDao;
86 90
  91 + @Autowired
  92 + @Lazy
  93 + private TbTenantProfileCache tenantProfileCache;
  94 +
87 95 @Override
88 96 public User findUserByEmail(TenantId tenantId, String email) {
89 97 log.trace("Executing findUserByEmail [{}]", email);
... ... @@ -365,6 +373,16 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
365 373 private DataValidator<User> userValidator =
366 374 new DataValidator<User>() {
367 375 @Override
  376 + protected void validateCreate(TenantId tenantId, User user) {
  377 + if (!user.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
  378 + DefaultTenantProfileConfiguration profileConfiguration =
  379 + (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
  380 + long maxUsers = profileConfiguration.getMaxUsers();
  381 + validateNumberOfEntitiesPerTenant(tenantId, userDao, maxUsers, EntityType.USER);
  382 + }
  383 + }
  384 +
  385 + @Override
368 386 protected void validateDataImpl(TenantId requestTenantId, User user) {
369 387 if (StringUtils.isEmpty(user.getEmail())) {
370 388 throw new DataValidationException("User email should be specified!");
... ...
... ... @@ -36,12 +36,11 @@
36 36 <pkg.implementationTitle>${project.name}</pkg.implementationTitle>
37 37 <pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder>
38 38 <pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
39   - <spring-boot.version>2.2.6.RELEASE</spring-boot.version>
40   - <spring-oauth2.version>2.1.2.RELEASE</spring-oauth2.version>
41   - <spring.version>5.2.6.RELEASE</spring.version>
42   - <spring-security.version>5.2.3.RELEASE</spring-security.version>
43   - <spring-data-redis.version>2.2.4.RELEASE</spring-data-redis.version>
44   - <jedis.version>3.1.0</jedis.version>
  39 + <spring-boot.version>2.3.5.RELEASE</spring-boot.version>
  40 + <spring.version>5.2.10.RELEASE</spring.version>
  41 + <spring-security.version>5.4.1</spring-security.version>
  42 + <spring-data-redis.version>2.4.1</spring-data-redis.version>
  43 + <jedis.version>3.3.0</jedis.version>
45 44 <jjwt.version>0.7.0</jjwt.version>
46 45 <json-path.version>2.2.0</json-path.version>
47 46 <junit.version>4.12</junit.version>
... ... @@ -52,15 +51,16 @@
52 51 <cassandra.version>4.6.0</cassandra.version>
53 52 <metrics.version>4.0.5</metrics.version>
54 53 <cassandra-unit.version>4.3.1.0</cassandra-unit.version>
  54 + <cassandra-all.version>3.11.9</cassandra-all.version>
55 55 <takari-cpsuite.version>1.2.7</takari-cpsuite.version>
56 56 <guava.version>28.2-jre</guava.version>
57 57 <caffeine.version>2.6.1</caffeine.version>
58 58 <commons-lang3.version>3.4</commons-lang3.version>
59 59 <commons-io.version>2.5</commons-io.version>
60 60 <commons-csv.version>1.4</commons-csv.version>
61   - <jackson.version>2.10.2</jackson.version>
62   - <jackson-annotations.version>2.10.2</jackson-annotations.version>
63   - <jackson-core.version>2.10.2</jackson-core.version>
  61 + <jackson.version>2.11.3</jackson.version>
  62 + <jackson-annotations.version>2.11.3</jackson-annotations.version>
  63 + <jackson-core.version>2.11.3</jackson-core.version>
64 64 <json-schema-validator.version>2.2.6</json-schema-validator.version>
65 65 <californium.version>1.0.2</californium.version>
66 66 <gson.version>2.6.2</gson.version>
... ... @@ -72,7 +72,7 @@
72 72 <grpc.version>1.22.1</grpc.version>
73 73 <lombok.version>1.16.18</lombok.version>
74 74 <paho.client.version>1.2.4</paho.client.version>
75   - <netty.version>4.1.49.Final</netty.version>
  75 + <netty.version>4.1.53.Final</netty.version>
76 76 <os-maven-plugin.version>1.5.0</os-maven-plugin.version>
77 77 <rabbitmq.version>4.8.0</rabbitmq.version>
78 78 <surfire.version>2.19.1</surfire.version>
... ... @@ -96,7 +96,7 @@
96 96 <bucket4j.version>4.1.1</bucket4j.version>
97 97 <fst.version>2.57</fst.version>
98 98 <antlr.version>2.7.7</antlr.version>
99   - <snakeyaml.version>1.25</snakeyaml.version>
  99 + <snakeyaml.version>1.27</snakeyaml.version>
100 100 <amazonaws.sqs.version>1.11.747</amazonaws.sqs.version>
101 101 <pubsub.client.version>1.105.0</pubsub.client.version>
102 102 <azure-servicebus.version>3.2.0</azure-servicebus.version>
... ... @@ -873,11 +873,6 @@
873 873 <version>${spring-boot.version}</version>
874 874 </dependency>
875 875 <dependency>
876   - <groupId>org.springframework.cloud</groupId>
877   - <artifactId>spring-cloud-starter-oauth2</artifactId>
878   - <version>${spring-oauth2.version}</version>
879   - </dependency>
880   - <dependency>
881 876 <groupId>org.springframework.security</groupId>
882 877 <artifactId>spring-security-oauth2-client</artifactId>
883 878 <version>${spring-security.version}</version>
... ... @@ -1201,6 +1196,11 @@
1201 1196 <scope>test</scope>
1202 1197 </dependency>
1203 1198 <dependency>
  1199 + <groupId>org.apache.cassandra</groupId>
  1200 + <artifactId>cassandra-all</artifactId>
  1201 + <version>${cassandra-all.version}</version>
  1202 + </dependency>
  1203 + <dependency>
1204 1204 <groupId>junit</groupId>
1205 1205 <artifactId>junit</artifactId>
1206 1206 <version>${junit.version}</version>
... ...
... ... @@ -121,6 +121,11 @@
121 121 <artifactId>jts-core</artifactId>
122 122 </dependency>
123 123 <dependency>
  124 + <groupId>com.sun.mail</groupId>
  125 + <artifactId>javax.mail</artifactId>
  126 + <scope>provided</scope>
  127 + </dependency>
  128 + <dependency>
124 129 <groupId>junit</groupId>
125 130 <artifactId>junit</artifactId>
126 131 <version>${junit.version}</version>
... ...
... ... @@ -54,7 +54,6 @@
54 54 <dependency>
55 55 <groupId>org.apache.cassandra</groupId>
56 56 <artifactId>cassandra-all</artifactId>
57   - <version>3.11.6</version>
58 57 </dependency>
59 58 <dependency>
60 59 <groupId>com.datastax.oss</groupId>
... ...
... ... @@ -41,6 +41,54 @@
41 41 </mat-error>
42 42 </mat-form-field>
43 43 <mat-form-field class="mat-block">
  44 + <mat-label translate>tenant-profile.maximum-customers</mat-label>
  45 + <input matInput required min="0" step="1"
  46 + formControlName="maxCustomers"
  47 + type="number">
  48 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxCustomers').hasError('required')">
  49 + {{ 'tenant-profile.maximum-customers-required' | translate}}
  50 + </mat-error>
  51 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxCustomers').hasError('min')">
  52 + {{ 'tenant-profile.maximum-customers-range' | translate}}
  53 + </mat-error>
  54 + </mat-form-field>
  55 + <mat-form-field class="mat-block">
  56 + <mat-label translate>tenant-profile.maximum-users</mat-label>
  57 + <input matInput required min="0" step="1"
  58 + formControlName="maxUsers"
  59 + type="number">
  60 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxUsers').hasError('required')">
  61 + {{ 'tenant-profile.maximum-users-required' | translate}}
  62 + </mat-error>
  63 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxUsers').hasError('min')">
  64 + {{ 'tenant-profile.maximum-users-range' | translate}}
  65 + </mat-error>
  66 + </mat-form-field>
  67 + <mat-form-field class="mat-block">
  68 + <mat-label translate>tenant-profile.maximum-dashboards</mat-label>
  69 + <input matInput required min="0" step="1"
  70 + formControlName="maxDashboards"
  71 + type="number">
  72 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxDashboards').hasError('required')">
  73 + {{ 'tenant-profile.maximum-dashboards-required' | translate}}
  74 + </mat-error>
  75 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxDashboards').hasError('min')">
  76 + {{ 'tenant-profile.maximum-dashboards-range' | translate}}
  77 + </mat-error>
  78 + </mat-form-field>
  79 + <mat-form-field class="mat-block">
  80 + <mat-label translate>tenant-profile.maximum-rule-chains</mat-label>
  81 + <input matInput required min="0" step="1"
  82 + formControlName="maxRuleChains"
  83 + type="number">
  84 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxRuleChains').hasError('required')">
  85 + {{ 'tenant-profile.maximum-rule-chains-required' | translate}}
  86 + </mat-error>
  87 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxRuleChains').hasError('min')">
  88 + {{ 'tenant-profile.maximum-rule-chains-range' | translate}}
  89 + </mat-error>
  90 + </mat-form-field>
  91 + <mat-form-field class="mat-block">
44 92 <mat-label translate>tenant-profile.max-transport-messages</mat-label>
45 93 <input matInput required min="0" step="1"
46 94 formControlName="maxTransportMessages"
... ...
... ... @@ -55,6 +55,10 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA
55 55 this.defaultTenantProfileConfigurationFormGroup = this.fb.group({
56 56 maxDevices: [null, [Validators.required, Validators.min(0)]],
57 57 maxAssets: [null, [Validators.required, Validators.min(0)]],
  58 + maxCustomers: [null, [Validators.required, Validators.min(0)]],
  59 + maxUsers: [null, [Validators.required, Validators.min(0)]],
  60 + maxDashboards: [null, [Validators.required, Validators.min(0)]],
  61 + maxRuleChains: [null, [Validators.required, Validators.min(0)]],
58 62 transportTenantMsgRateLimit: [null, []],
59 63 transportTenantTelemetryMsgRateLimit: [null, []],
60 64 transportTenantTelemetryDataPointsRateLimit: [null, []],
... ...
... ... @@ -487,7 +487,8 @@ export default abstract class LeafletMap {
487 487 }
488 488
489 489 const mapBounds = this.map.getBounds();
490   - if (bounds.isValid() && (!this.bounds || !this.bounds.isValid() || !this.bounds.equals(bounds) && !mapBounds.contains(bounds))) {
  490 + if (bounds.isValid() && (!this.bounds || !this.bounds.isValid() || !this.bounds.equals(bounds)
  491 + && this.options.fitMapBounds ? !mapBounds.contains(bounds) : false)) {
491 492 this.bounds = bounds;
492 493 this.fitBounds(bounds);
493 494 }
... ...
... ... @@ -20,6 +20,7 @@ import { EntityType } from '@shared/models/entity-type.models';
20 20 import tinycolor from 'tinycolor2';
21 21
22 22 export const DEFAULT_MAP_PAGE_SIZE = 16384;
  23 +export const DEFAULT_ZOOM_LEVEL = 8;
23 24
24 25 export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
25 26 export type MarkerImageFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
... ... @@ -229,7 +230,6 @@ export const defaultSettings: any = {
229 230 strokeWeight: 2,
230 231 strokeOpacity: 1.0,
231 232 initCallback: () => { },
232   - defaultZoomLevel: 8,
233 233 disableScrollZooming: false,
234 234 minZoomLevel: 16,
235 235 credentials: '',
... ...
... ... @@ -17,7 +17,7 @@
17 17
18 18 import L from 'leaflet';
19 19 import LeafletMap from '../leaflet-map';
20   -import { UnitedMapSettings } from '../map-models';
  20 +import { DEFAULT_ZOOM_LEVEL, UnitedMapSettings } from '../map-models';
21 21 import 'leaflet.gridlayer.googlemutant';
22 22 import { ResourcesService } from '@core/services/resources.service';
23 23 import { WidgetContext } from '@home/models/widget-component.models';
... ... @@ -39,7 +39,7 @@ export class GoogleMap extends LeafletMap {
39 39 const map = L.map($container, {
40 40 attributionControl: false,
41 41 editable: !!options.editablePolygon
42   - }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel);
  42 + }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL);
43 43 (L.gridLayer as any).googleMutant({
44 44 type: options?.gmDefaultMapType || 'roadmap'
45 45 }).addTo(map);
... ...
... ... @@ -16,7 +16,7 @@
16 16
17 17 import L from 'leaflet';
18 18 import LeafletMap from '../leaflet-map';
19   -import { UnitedMapSettings } from '../map-models';
  19 +import { DEFAULT_ZOOM_LEVEL, UnitedMapSettings } from '../map-models';
20 20 import { WidgetContext } from '@home/models/widget-component.models';
21 21
22 22 export class HEREMap extends LeafletMap {
... ... @@ -24,7 +24,7 @@ export class HEREMap extends LeafletMap {
24 24 super(ctx, $container, options);
25 25 const map = L.map($container, {
26 26 editable: !!options.editablePolygon
27   - }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel);
  27 + }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL);
28 28 const tileLayer = (L.tileLayer as any).provider(options.mapProviderHere || 'HERE.normalDay', options.credentials);
29 29 tileLayer.addTo(map);
30 30 super.initSettings(options);
... ...
... ... @@ -16,7 +16,7 @@
16 16
17 17 import L from 'leaflet';
18 18 import LeafletMap from '../leaflet-map';
19   -import { UnitedMapSettings } from '../map-models';
  19 +import { DEFAULT_ZOOM_LEVEL, UnitedMapSettings } from '../map-models';
20 20 import { WidgetContext } from '@home/models/widget-component.models';
21 21
22 22 export class OpenStreetMap extends LeafletMap {
... ... @@ -24,7 +24,7 @@ export class OpenStreetMap extends LeafletMap {
24 24 super(ctx, $container, options);
25 25 const map = L.map($container, {
26 26 editable: !!options.editablePolygon
27   - }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel);
  27 + }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL);
28 28 let tileLayer;
29 29 if (options.useCustomProvider) {
30 30 tileLayer = L.tileLayer(options.customProviderTileUrl);
... ...
... ... @@ -17,7 +17,7 @@
17 17
18 18 import L from 'leaflet';
19 19 import LeafletMap from '../leaflet-map';
20   -import { UnitedMapSettings } from '../map-models';
  20 +import { DEFAULT_ZOOM_LEVEL, UnitedMapSettings } from '../map-models';
21 21 import { WidgetContext } from '@home/models/widget-component.models';
22 22
23 23 export class TencentMap extends LeafletMap {
... ... @@ -26,7 +26,7 @@ export class TencentMap extends LeafletMap {
26 26 const txUrl = 'http://rt{s}.map.gtimg.com/realtimerender?z={z}&x={x}&y={y}&type=vector&style=0';
27 27 const map = L.map($container, {
28 28 editable: !!options.editablePolygon
29   - }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel);
  29 + }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL);
30 30 const txLayer = L.tileLayer(txUrl, {
31 31 subdomains: '0123',
32 32 tms: true,
... ...
... ... @@ -56,6 +56,12 @@ export const customerHref = '<a href="https://github.com/thingsboard/thingsboard
56 56
57 57 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>';
58 58
  59 +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>';
  60 +
  61 +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>';
  62 +
  63 +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>';
  64 +
59 65 export const userHref = '<a href="https://github.com/thingsboard/thingsboard/blob/13e6b10b7ab830e64d31b99614a9d95a1a25928a/ui-ngx/src/app/shared/models/user.model.ts#L23">User</a>';
60 66
61 67 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 = {
1080 1086 ],
1081 1087 return: observableReturnTypeVariable('any')
1082 1088 },
  1089 + getEntityTimeseries: {
  1090 + description: 'Get entity timeseries',
  1091 + meta: 'function',
  1092 + args: [
  1093 + {name: 'entityId', type: entityIdHref, description: 'Id of the entity'},
  1094 + {name: 'keys', type: `Array&lt;string&gt;`, description: 'Array of the keys'},
  1095 + {name: 'startTs', type: 'number', description: 'Start time in milliseconds'},
  1096 + {name: 'endTs', type: 'number', description: 'End time in milliseconds'},
  1097 + {name: 'limit', type: 'number', description: 'Limit of values to receive for each key'},
  1098 + {name: 'agg', type: aggregationTypeHref, description: 'Aggregation type'},
  1099 + {name: 'interval', type: 'number', description: 'Aggregation interval'},
  1100 + {name: 'orderBy', type: dataSortOrderHref, description: 'Data order by time'},
  1101 + {name: 'useStrictDataTypes', type: 'boolean', description: 'If "false" all values will be returned as strings'},
  1102 + requestConfigArg
  1103 + ],
  1104 + return: observableReturnTypeVariable(timeseriesDataHref)
  1105 + },
1083 1106 }
1084 1107 },
1085 1108 entityService: {
... ...
... ... @@ -26,6 +26,10 @@ export enum TenantProfileType {
26 26 export interface DefaultTenantProfileConfiguration {
27 27 maxDevices: number;
28 28 maxAssets: number;
  29 + maxCustomers: number;
  30 + maxUsers: number;
  31 + maxDashboards: number;
  32 + maxRuleChains: number;
29 33
30 34 transportTenantMsgRateLimit?: string;
31 35 transportTenantTelemetryMsgRateLimit?: string;
... ... @@ -56,6 +60,10 @@ export function createTenantProfileConfiguration(type: TenantProfileType): Tenan
56 60 const defaultConfiguration: DefaultTenantProfileConfiguration = {
57 61 maxDevices: 0,
58 62 maxAssets: 0,
  63 + maxCustomers: 0,
  64 + maxUsers: 0,
  65 + maxDashboards: 0,
  66 + maxRuleChains: 0,
59 67 maxTransportMessages: 0,
60 68 maxTransportDataPoints: 0,
61 69 maxREExecutions: 0,
... ...
... ... @@ -1946,6 +1946,18 @@
1946 1946 "maximum-assets": "Maximum number of assets (0 - unlimited)",
1947 1947 "maximum-assets-required": "Maximum number of assets is required.",
1948 1948 "maximum-assets-range": "Maximum number of assets can't be negative",
  1949 + "maximum-customers": "Maximum number of customers (0 - unlimited)",
  1950 + "maximum-customers-required": "Maximum number of customers is required.",
  1951 + "maximum-customers-range": "Maximum number of customers can't be negative",
  1952 + "maximum-users": "Maximum number of users (0 - unlimited)",
  1953 + "maximum-users-required": "Maximum number of users is required.",
  1954 + "maximum-users-range": "Maximum number of users can't be negative",
  1955 + "maximum-dashboards": "Maximum number of dashboards (0 - unlimited)",
  1956 + "maximum-dashboards-required": "Maximum number of dashboards is required.",
  1957 + "maximum-dashboards-range": "Maximum number of dashboards can't be negative",
  1958 + "maximum-rule-chains": "Maximum number of rule chains (0 - unlimited)",
  1959 + "maximum-rule-chains-required": "Maximum number of rule chains is required.",
  1960 + "maximum-rule-chains-range": "Maximum number of rule chains can't be negative",
1949 1961 "transport-tenant-msg-rate-limit": "Transport tenant messages rate limit.",
1950 1962 "transport-tenant-telemetry-msg-rate-limit": "Transport tenant telemetry messages rate limit.",
1951 1963 "transport-tenant-telemetry-data-points-rate-limit": "Transport tenant telemetry data points rate limit.",
... ...