Showing
49 changed files
with
360 additions
and
98 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> | ... | ... |
... | ... | @@ -35,14 +35,17 @@ import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; |
35 | 35 | import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; |
36 | 36 | import org.thingsboard.server.common.data.page.PageData; |
37 | 37 | import org.thingsboard.server.common.data.page.PageLink; |
38 | +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; | |
38 | 39 | import org.thingsboard.server.common.data.security.Authority; |
39 | 40 | import org.thingsboard.server.common.data.security.UserCredentials; |
40 | 41 | import org.thingsboard.server.dao.customer.CustomerService; |
41 | 42 | import org.thingsboard.server.dao.dashboard.DashboardService; |
42 | 43 | import org.thingsboard.server.dao.oauth2.OAuth2User; |
44 | +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; | |
43 | 45 | import org.thingsboard.server.dao.tenant.TenantService; |
44 | 46 | import org.thingsboard.server.dao.user.UserService; |
45 | 47 | import org.thingsboard.server.service.install.InstallScripts; |
48 | +import org.thingsboard.server.service.queue.TbClusterService; | |
46 | 49 | import org.thingsboard.server.service.security.model.SecurityUser; |
47 | 50 | import org.thingsboard.server.service.security.model.UserPrincipal; |
48 | 51 | |
... | ... | @@ -76,6 +79,12 @@ public abstract class AbstractOAuth2ClientMapper { |
76 | 79 | @Autowired |
77 | 80 | private InstallScripts installScripts; |
78 | 81 | |
82 | + @Autowired | |
83 | + protected TbTenantProfileCache tenantProfileCache; | |
84 | + | |
85 | + @Autowired | |
86 | + protected TbClusterService tbClusterService; | |
87 | + | |
79 | 88 | private final Lock userCreationLock = new ReentrantLock(); |
80 | 89 | |
81 | 90 | protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2ClientRegistrationInfo clientRegistration) { |
... | ... | @@ -162,6 +171,10 @@ public abstract class AbstractOAuth2ClientMapper { |
162 | 171 | tenant.setTitle(tenantName); |
163 | 172 | tenant = tenantService.saveTenant(tenant); |
164 | 173 | installScripts.createDefaultRuleChains(tenant.getId()); |
174 | + tenantProfileCache.evict(tenant.getId()); | |
175 | + tbClusterService.onTenantChange(tenant, null); | |
176 | + tbClusterService.onEntityStateChange(tenant.getId(), tenant.getId(), | |
177 | + ComponentLifecycleEvent.CREATED); | |
165 | 178 | } else { |
166 | 179 | tenant = tenants.get(0); |
167 | 180 | } | ... | ... |
... | ... | @@ -15,10 +15,15 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.service.security.auth.oauth2; |
17 | 17 | |
18 | +import org.springframework.beans.factory.annotation.Autowired; | |
18 | 19 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
19 | 20 | import org.springframework.security.core.AuthenticationException; |
20 | 21 | import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; |
21 | 22 | import org.springframework.stereotype.Component; |
23 | +import org.thingsboard.server.common.data.id.CustomerId; | |
24 | +import org.thingsboard.server.common.data.id.EntityId; | |
25 | +import org.thingsboard.server.common.data.id.TenantId; | |
26 | +import org.thingsboard.server.service.security.system.SystemSecurityService; | |
22 | 27 | import org.thingsboard.server.utils.MiscUtils; |
23 | 28 | |
24 | 29 | import javax.servlet.ServletException; |
... | ... | @@ -32,11 +37,18 @@ import java.nio.charset.StandardCharsets; |
32 | 37 | @ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true") |
33 | 38 | public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { |
34 | 39 | |
40 | + private final SystemSecurityService systemSecurityService; | |
41 | + | |
42 | + @Autowired | |
43 | + public Oauth2AuthenticationFailureHandler(final SystemSecurityService systemSecurityService) { | |
44 | + this.systemSecurityService = systemSecurityService; | |
45 | + } | |
46 | + | |
35 | 47 | @Override |
36 | 48 | public void onAuthenticationFailure(HttpServletRequest request, |
37 | 49 | HttpServletResponse response, AuthenticationException exception) |
38 | 50 | throws IOException, ServletException { |
39 | - String baseUrl = MiscUtils.constructBaseUrl(request); | |
51 | + String baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); | |
40 | 52 | getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" + |
41 | 53 | URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8.toString())); |
42 | 54 | } | ... | ... |
... | ... | @@ -22,12 +22,16 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; |
22 | 22 | import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; |
23 | 23 | import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; |
24 | 24 | import org.springframework.stereotype.Component; |
25 | +import org.thingsboard.server.common.data.id.CustomerId; | |
26 | +import org.thingsboard.server.common.data.id.EntityId; | |
27 | +import org.thingsboard.server.common.data.id.TenantId; | |
25 | 28 | import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; |
26 | 29 | import org.thingsboard.server.dao.oauth2.OAuth2Service; |
27 | 30 | import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; |
28 | 31 | import org.thingsboard.server.service.security.model.SecurityUser; |
29 | 32 | import org.thingsboard.server.service.security.model.token.JwtToken; |
30 | 33 | import org.thingsboard.server.service.security.model.token.JwtTokenFactory; |
34 | +import org.thingsboard.server.service.security.system.SystemSecurityService; | |
31 | 35 | import org.thingsboard.server.utils.MiscUtils; |
32 | 36 | |
33 | 37 | import javax.servlet.http.HttpServletRequest; |
... | ... | @@ -45,25 +49,27 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS |
45 | 49 | private final OAuth2ClientMapperProvider oauth2ClientMapperProvider; |
46 | 50 | private final OAuth2Service oAuth2Service; |
47 | 51 | private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService; |
52 | + private final SystemSecurityService systemSecurityService; | |
48 | 53 | |
49 | 54 | @Autowired |
50 | 55 | public Oauth2AuthenticationSuccessHandler(final JwtTokenFactory tokenFactory, |
51 | 56 | final RefreshTokenRepository refreshTokenRepository, |
52 | 57 | final OAuth2ClientMapperProvider oauth2ClientMapperProvider, |
53 | 58 | final OAuth2Service oAuth2Service, |
54 | - final OAuth2AuthorizedClientService oAuth2AuthorizedClientService) { | |
59 | + final OAuth2AuthorizedClientService oAuth2AuthorizedClientService, final SystemSecurityService systemSecurityService) { | |
55 | 60 | this.tokenFactory = tokenFactory; |
56 | 61 | this.refreshTokenRepository = refreshTokenRepository; |
57 | 62 | this.oauth2ClientMapperProvider = oauth2ClientMapperProvider; |
58 | 63 | this.oAuth2Service = oAuth2Service; |
59 | 64 | this.oAuth2AuthorizedClientService = oAuth2AuthorizedClientService; |
65 | + this.systemSecurityService = systemSecurityService; | |
60 | 66 | } |
61 | 67 | |
62 | 68 | @Override |
63 | 69 | public void onAuthenticationSuccess(HttpServletRequest request, |
64 | 70 | HttpServletResponse response, |
65 | 71 | Authentication authentication) throws IOException { |
66 | - String baseUrl = MiscUtils.constructBaseUrl(request); | |
72 | + String baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); | |
67 | 73 | try { |
68 | 74 | OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; |
69 | 75 | ... | ... |
... | ... | @@ -202,16 +202,19 @@ public class DefaultSystemSecurityService implements SystemSecurityService { |
202 | 202 | |
203 | 203 | @Override |
204 | 204 | public String getBaseUrl(TenantId tenantId, CustomerId customerId, HttpServletRequest httpServletRequest) { |
205 | - String baseUrl; | |
205 | + String baseUrl = null; | |
206 | 206 | AdminSettings generalSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "general"); |
207 | 207 | |
208 | 208 | JsonNode prohibitDifferentUrl = generalSettings.getJsonValue().get("prohibitDifferentUrl"); |
209 | 209 | |
210 | 210 | if (prohibitDifferentUrl != null && prohibitDifferentUrl.asBoolean()) { |
211 | 211 | baseUrl = generalSettings.getJsonValue().get("baseUrl").asText(); |
212 | - } else { | |
212 | + } | |
213 | + | |
214 | + if (StringUtils.isEmpty(baseUrl)) { | |
213 | 215 | baseUrl = MiscUtils.constructBaseUrl(httpServletRequest); |
214 | 216 | } |
217 | + | |
215 | 218 | return baseUrl; |
216 | 219 | } |
217 | 220 | ... | ... |
... | ... | @@ -108,7 +108,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer |
108 | 108 | * Since number of subscriptions is usually much less then number of devices that are pushing data. |
109 | 109 | */ |
110 | 110 | subscriptionsBySessionId.values().forEach(map -> map.values() |
111 | - .forEach(sub -> pushSubscriptionToManagerService(sub, false))); | |
111 | + .forEach(sub -> pushSubscriptionToManagerService(sub, true))); | |
112 | 112 | } |
113 | 113 | } |
114 | 114 | ... | ... |
... | ... | @@ -165,7 +165,7 @@ public class DefaultTransportApiService implements TransportApiService { |
165 | 165 | |
166 | 166 | private ListenableFuture<TransportApiResponseMsg> validateCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) { |
167 | 167 | DeviceCredentials credentials = null; |
168 | - if (mqtt.getUserName() != null) { | |
168 | + if (!StringUtils.isEmpty(mqtt.getUserName())) { | |
169 | 169 | credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName()); |
170 | 170 | if (credentials != null) { |
171 | 171 | if (credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) { | ... | ... |
... | ... | @@ -21,5 +21,5 @@ import java.io.Serializable; |
21 | 21 | * @author Andrew Shvayka |
22 | 22 | */ |
23 | 23 | public enum ComponentLifecycleEvent implements Serializable { |
24 | - CREATED, STARTED, ACTIVATED, SUSPENDED, UPDATED, STOPPED, DELETED, ADDED_TO_ALLOW_LIST, ADDED_TO_DENY_LIST | |
24 | + CREATED, STARTED, ACTIVATED, SUSPENDED, UPDATED, STOPPED, DELETED | |
25 | 25 | } |
\ No newline at end of file | ... | ... |
... | ... | @@ -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; | ... | ... |
... | ... | @@ -390,7 +390,8 @@ public class DefaultTransportService implements TransportService { |
390 | 390 | metaData.putValue("deviceName", sessionInfo.getDeviceName()); |
391 | 391 | metaData.putValue("deviceType", sessionInfo.getDeviceType()); |
392 | 392 | metaData.putValue("notifyDevice", "false"); |
393 | - sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, SessionMsgType.POST_ATTRIBUTES_REQUEST, new TransportTbQueueCallback(callback)); | |
393 | + sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, SessionMsgType.POST_ATTRIBUTES_REQUEST, | |
394 | + new TransportTbQueueCallback(new ApiStatsProxyCallback<>(tenantId, msg.getKvList().size(), callback))); | |
394 | 395 | } |
395 | 396 | } |
396 | 397 | |
... | ... | @@ -399,7 +400,7 @@ public class DefaultTransportService implements TransportService { |
399 | 400 | if (checkLimits(sessionInfo, msg, callback)) { |
400 | 401 | reportActivityInternal(sessionInfo); |
401 | 402 | sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) |
402 | - .setGetAttributes(msg).build(), callback); | |
403 | + .setGetAttributes(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback)); | |
403 | 404 | } |
404 | 405 | } |
405 | 406 | |
... | ... | @@ -409,7 +410,7 @@ public class DefaultTransportService implements TransportService { |
409 | 410 | SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); |
410 | 411 | sessionMetaData.setSubscribedToAttributes(!msg.getUnsubscribe()); |
411 | 412 | sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) |
412 | - .setSubscribeToAttributes(msg).build(), callback); | |
413 | + .setSubscribeToAttributes(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback)); | |
413 | 414 | } |
414 | 415 | } |
415 | 416 | |
... | ... | @@ -419,7 +420,7 @@ public class DefaultTransportService implements TransportService { |
419 | 420 | SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); |
420 | 421 | sessionMetaData.setSubscribedToRPC(!msg.getUnsubscribe()); |
421 | 422 | sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) |
422 | - .setSubscribeToRPC(msg).build(), callback); | |
423 | + .setSubscribeToRPC(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback)); | |
423 | 424 | } |
424 | 425 | } |
425 | 426 | |
... | ... | @@ -428,7 +429,7 @@ public class DefaultTransportService implements TransportService { |
428 | 429 | if (checkLimits(sessionInfo, msg, callback)) { |
429 | 430 | reportActivityInternal(sessionInfo); |
430 | 431 | sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) |
431 | - .setToDeviceRPCCallResponse(msg).build(), callback); | |
432 | + .setToDeviceRPCCallResponse(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback)); | |
432 | 433 | } |
433 | 434 | } |
434 | 435 | |
... | ... | @@ -805,7 +806,7 @@ public class DefaultTransportService implements TransportService { |
805 | 806 | |
806 | 807 | @Override |
807 | 808 | public void onFailure(Throwable t) { |
808 | - callback.onError(t); | |
809 | + DefaultTransportService.this.transportCallbackExecutor.submit(() -> callback.onError(t)); | |
809 | 810 | } |
810 | 811 | } |
811 | 812 | ... | ... |
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 | } | ... | ... |
... | ... | @@ -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 | } | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -202,6 +202,9 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { |
202 | 202 | " THEN (select additional_info from entity_view where id = entity_id)" + |
203 | 203 | " END as additional_info"; |
204 | 204 | |
205 | + 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, " + | |
206 | + "(select title from tenant where id = aus.tenant_id) as name from api_usage_state as aus)"; | |
207 | + | |
205 | 208 | static { |
206 | 209 | entityTableMap.put(EntityType.ASSET, "asset"); |
207 | 210 | entityTableMap.put(EntityType.DEVICE, "device"); |
... | ... | @@ -210,7 +213,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { |
210 | 213 | entityTableMap.put(EntityType.CUSTOMER, "customer"); |
211 | 214 | entityTableMap.put(EntityType.USER, "tb_user"); |
212 | 215 | entityTableMap.put(EntityType.TENANT, "tenant"); |
213 | - entityTableMap.put(EntityType.API_USAGE_STATE, "api_usage_state"); | |
216 | + entityTableMap.put(EntityType.API_USAGE_STATE, SELECT_API_USAGE_STATE); | |
214 | 217 | } |
215 | 218 | |
216 | 219 | public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{ | ... | ... |
... | ... | @@ -80,7 +80,7 @@ public class EntityKeyMapping { |
80 | 80 | public static final List<String> labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, ADDITIONAL_INFO); |
81 | 81 | public static final List<String> contactBasedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, EMAIL, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO); |
82 | 82 | |
83 | - public static final Set<String> apiUsageStateEntityFields = Collections.singleton(CREATED_TIME); | |
83 | + public static final Set<String> apiUsageStateEntityFields = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME)); | |
84 | 84 | public static final Set<String> commonEntityFieldsSet = new HashSet<>(commonEntityFields); |
85 | 85 | 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)); |
86 | 86 | ... | ... |
... | ... | @@ -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 | } | ... | ... |
... | ... | @@ -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 | } | ... | ... |
... | ... | @@ -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> |
... | ... | @@ -875,11 +875,6 @@ |
875 | 875 | <version>${spring-boot.version}</version> |
876 | 876 | </dependency> |
877 | 877 | <dependency> |
878 | - <groupId>org.springframework.cloud</groupId> | |
879 | - <artifactId>spring-cloud-starter-oauth2</artifactId> | |
880 | - <version>${spring-oauth2.version}</version> | |
881 | - </dependency> | |
882 | - <dependency> | |
883 | 878 | <groupId>org.springframework.security</groupId> |
884 | 879 | <artifactId>spring-security-oauth2-client</artifactId> |
885 | 880 | <version>${spring-security.version}</version> |
... | ... | @@ -1203,6 +1198,11 @@ |
1203 | 1198 | <scope>test</scope> |
1204 | 1199 | </dependency> |
1205 | 1200 | <dependency> |
1201 | + <groupId>org.apache.cassandra</groupId> | |
1202 | + <artifactId>cassandra-all</artifactId> | |
1203 | + <version>${cassandra-all.version}</version> | |
1204 | + </dependency> | |
1205 | + <dependency> | |
1206 | 1206 | <groupId>junit</groupId> |
1207 | 1207 | <artifactId>junit</artifactId> |
1208 | 1208 | <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> | ... | ... |
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java
... | ... | @@ -67,6 +67,7 @@ class AlarmState { |
67 | 67 | initCurrentAlarm(ctx); |
68 | 68 | lastMsgMetaData = msg.getMetaData(); |
69 | 69 | lastMsgQueueName = msg.getQueueName(); |
70 | + this.dataSnapshot = data; | |
70 | 71 | return createOrClearAlarms(ctx, data, update, AlarmRuleState::eval); |
71 | 72 | } |
72 | 73 | |
... | ... | @@ -90,8 +91,7 @@ class AlarmState { |
90 | 91 | resultState = state; |
91 | 92 | break; |
92 | 93 | } else if (AlarmEvalResult.FALSE.equals(evalResult)) { |
93 | - state.clear(); | |
94 | - stateUpdate |= state.checkUpdate(); | |
94 | + stateUpdate = clearAlarmState(stateUpdate, state); | |
95 | 95 | } |
96 | 96 | } |
97 | 97 | if (resultState != null) { |
... | ... | @@ -99,6 +99,7 @@ class AlarmState { |
99 | 99 | if (result != null) { |
100 | 100 | pushMsg(ctx, result); |
101 | 101 | } |
102 | + stateUpdate = clearAlarmState(stateUpdate, clearState); | |
102 | 103 | } else if (currentAlarm != null && clearState != null) { |
103 | 104 | if (!validateUpdate(update, clearState)) { |
104 | 105 | log.debug("[{}] Update is not valid for current clear state", alarmDefinition.getId()); |
... | ... | @@ -106,23 +107,26 @@ class AlarmState { |
106 | 107 | } |
107 | 108 | AlarmEvalResult evalResult = evalFunction.apply(clearState, data); |
108 | 109 | if (AlarmEvalResult.TRUE.equals(evalResult)) { |
109 | - clearState.clear(); | |
110 | - stateUpdate |= clearState.checkUpdate(); | |
110 | + stateUpdate = clearAlarmState(stateUpdate, clearState); | |
111 | 111 | for (AlarmRuleState state : createRulesSortedBySeverityDesc) { |
112 | - state.clear(); | |
113 | - stateUpdate |= state.checkUpdate(); | |
112 | + stateUpdate = clearAlarmState(stateUpdate, state); | |
114 | 113 | } |
115 | 114 | ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), JacksonUtil.OBJECT_MAPPER.createObjectNode(), System.currentTimeMillis()); |
116 | 115 | pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm)); |
117 | 116 | currentAlarm = null; |
118 | 117 | } else if (AlarmEvalResult.FALSE.equals(evalResult)) { |
119 | - clearState.clear(); | |
120 | - stateUpdate |= clearState.checkUpdate(); | |
118 | + stateUpdate = clearAlarmState(stateUpdate, clearState); | |
121 | 119 | } |
122 | 120 | } |
123 | 121 | return stateUpdate; |
124 | 122 | } |
125 | 123 | |
124 | + public boolean clearAlarmState(boolean stateUpdate, AlarmRuleState state) { | |
125 | + state.clear(); | |
126 | + stateUpdate |= state.checkUpdate(); | |
127 | + return stateUpdate; | |
128 | + } | |
129 | + | |
126 | 130 | public boolean validateUpdate(SnapshotUpdate update, AlarmRuleState state) { |
127 | 131 | if (update != null) { |
128 | 132 | //Check that the update type and that keys match. |
... | ... | @@ -190,7 +194,7 @@ class AlarmState { |
190 | 194 | } |
191 | 195 | } |
192 | 196 | |
193 | - private <T> TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmRuleState ruleState) { | |
197 | + private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmRuleState ruleState) { | |
194 | 198 | AlarmSeverity severity = ruleState.getSeverity(); |
195 | 199 | if (currentAlarm != null) { |
196 | 200 | // TODO: In some extremely rare cases, we might miss the event of alarm clear (If one use in-mem queue and restarted the server) or (if one manipulated the rule chain). |
... | ... | @@ -230,7 +234,7 @@ class AlarmState { |
230 | 234 | } |
231 | 235 | } |
232 | 236 | |
233 | - private <T> JsonNode createDetails(AlarmRuleState ruleState) { | |
237 | + private JsonNode createDetails(AlarmRuleState ruleState) { | |
234 | 238 | ObjectNode details = JacksonUtil.OBJECT_MAPPER.createObjectNode(); |
235 | 239 | String alarmDetails = ruleState.getAlarmRule().getAlarmDetails(); |
236 | 240 | |
... | ... | @@ -273,8 +277,7 @@ class AlarmState { |
273 | 277 | if (currentAlarm != null && currentAlarm.getId().equals(alarmNf.getId())) { |
274 | 278 | currentAlarm = null; |
275 | 279 | for (AlarmRuleState state : createRulesSortedBySeverityDesc) { |
276 | - state.clear(); | |
277 | - updated |= state.checkUpdate(); | |
280 | + updated = clearAlarmState(updated, state); | |
278 | 281 | } |
279 | 282 | } |
280 | 283 | return updated; | ... | ... |
... | ... | @@ -630,6 +630,9 @@ export class EntityService { |
630 | 630 | case EntityType.DASHBOARD: |
631 | 631 | entityFieldKeys.push(entityFields.title.keyName); |
632 | 632 | break; |
633 | + case EntityType.API_USAGE_STATE: | |
634 | + entityFieldKeys.push(entityFields.name.keyName); | |
635 | + break; | |
633 | 636 | } |
634 | 637 | return query ? entityFieldKeys.filter((entityField) => entityField.toLowerCase().indexOf(query) === 0) : entityFieldKeys; |
635 | 638 | } | ... | ... |
... | ... | @@ -52,6 +52,9 @@ |
52 | 52 | </mat-error> |
53 | 53 | </mat-form-field> |
54 | 54 | </div> |
55 | + <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration').hasError('unique')"> | |
56 | + {{ 'device-profile.mqtt-device-topic-filters-unique' | translate }} | |
57 | + </mat-error> | |
55 | 58 | <div class="tb-hint" innerHTML="{{ 'device-profile.support-level-wildcards' | translate }}"></div> |
56 | 59 | <div class="tb-hint" innerHTML="{{ 'device-profile.single-level-wildcards-hint' | translate }}"></div> |
57 | 60 | <div class="tb-hint" innerHTML="{{ 'device-profile.multi-level-wildcards-hint' | translate }}"></div> | ... | ... |
... | ... | @@ -52,7 +52,6 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control |
52 | 52 | |
53 | 53 | mqttTransportPayloadTypeTranslations = mqttTransportPayloadTypeTranslationMap; |
54 | 54 | |
55 | - | |
56 | 55 | mqttDeviceProfileTransportConfigurationFormGroup: FormGroup; |
57 | 56 | |
58 | 57 | private requiredValue: boolean; |
... | ... | @@ -90,7 +89,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control |
90 | 89 | transportPayloadTypeConfiguration: this.fb.group({ |
91 | 90 | transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required] |
92 | 91 | }) |
93 | - }) | |
92 | + }, {validator: this.uniqueDeviceTopicValidator}) | |
94 | 93 | }); |
95 | 94 | this.mqttDeviceProfileTransportConfigurationFormGroup.get('configuration.transportPayloadTypeConfiguration.transportPayloadType').valueChanges.subscribe(payloadType => { |
96 | 95 | this.updateTransportPayloadBasedControls(payloadType); |
... | ... | @@ -171,4 +170,14 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control |
171 | 170 | return null; |
172 | 171 | }; |
173 | 172 | } |
173 | + | |
174 | + private uniqueDeviceTopicValidator(control: FormGroup): { [key: string]: boolean } | null { | |
175 | + if (control.value) { | |
176 | + const formValue = control.value as MqttDeviceProfileTransportConfiguration; | |
177 | + if (formValue.deviceAttributesTopic === formValue.deviceTelemetryTopic) { | |
178 | + return {unique: true}; | |
179 | + } | |
180 | + } | |
181 | + return null; | |
182 | + } | |
174 | 183 | } | ... | ... |
... | ... | @@ -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, []], | ... | ... |
... | ... | @@ -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<string>`, 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: { | ... | ... |
... | ... | @@ -885,6 +885,7 @@ |
885 | 885 | "no-device-profiles-found": "No device profiles found.", |
886 | 886 | "create-new-device-profile": "Create a new one!", |
887 | 887 | "mqtt-device-topic-filters": "MQTT device topic filters", |
888 | + "mqtt-device-topic-filters-unique": "MQTT device topic filters need to be unique.", | |
888 | 889 | "mqtt-device-payload-type": "MQTT device payload", |
889 | 890 | "mqtt-device-payload-type-json": "JSON", |
890 | 891 | "mqtt-device-payload-type-proto": "Protobuf", |
... | ... | @@ -1949,6 +1950,18 @@ |
1949 | 1950 | "maximum-assets": "Maximum number of assets (0 - unlimited)", |
1950 | 1951 | "maximum-assets-required": "Maximum number of assets is required.", |
1951 | 1952 | "maximum-assets-range": "Maximum number of assets can't be negative", |
1953 | + "maximum-customers": "Maximum number of customers (0 - unlimited)", | |
1954 | + "maximum-customers-required": "Maximum number of customers is required.", | |
1955 | + "maximum-customers-range": "Maximum number of customers can't be negative", | |
1956 | + "maximum-users": "Maximum number of users (0 - unlimited)", | |
1957 | + "maximum-users-required": "Maximum number of users is required.", | |
1958 | + "maximum-users-range": "Maximum number of users can't be negative", | |
1959 | + "maximum-dashboards": "Maximum number of dashboards (0 - unlimited)", | |
1960 | + "maximum-dashboards-required": "Maximum number of dashboards is required.", | |
1961 | + "maximum-dashboards-range": "Maximum number of dashboards can't be negative", | |
1962 | + "maximum-rule-chains": "Maximum number of rule chains (0 - unlimited)", | |
1963 | + "maximum-rule-chains-required": "Maximum number of rule chains is required.", | |
1964 | + "maximum-rule-chains-range": "Maximum number of rule chains can't be negative", | |
1952 | 1965 | "transport-tenant-msg-rate-limit": "Transport tenant messages rate limit.", |
1953 | 1966 | "transport-tenant-telemetry-msg-rate-limit": "Transport tenant telemetry messages rate limit.", |
1954 | 1967 | "transport-tenant-telemetry-data-points-rate-limit": "Transport tenant telemetry data points rate limit.", | ... | ... |