Commit c03356e94456c526bc17c56acfc1d9563fb20483
Merge branch 'master' into feature/log-telemetry-updated
Showing
31 changed files
with
206 additions
and
63 deletions
... | ... | @@ -392,6 +392,11 @@ public class TelemetryController extends BaseController { |
392 | 392 | if (attributes.isEmpty()) { |
393 | 393 | return getImmediateDeferredResult("No attributes data found in request body!", HttpStatus.BAD_REQUEST); |
394 | 394 | } |
395 | + for (AttributeKvEntry attributeKvEntry: attributes) { | |
396 | + if (attributeKvEntry.getKey().isEmpty() || attributeKvEntry.getKey().trim().length() == 0) { | |
397 | + return getImmediateDeferredResult("Key cannot be empty or contains only spaces", HttpStatus.BAD_REQUEST); | |
398 | + } | |
399 | + } | |
395 | 400 | SecurityUser user = getCurrentUser(); |
396 | 401 | return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> { |
397 | 402 | tsSubService.saveAndNotify(tenantId, entityId, scope, attributes, new FutureCallback<Void>() { | ... | ... |
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
... | ... | @@ -221,6 +221,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { |
221 | 221 | |
222 | 222 | @Override |
223 | 223 | public void onTenantProfileUpdate(TenantProfileId tenantProfileId) { |
224 | + log.info("[{}] On Tenant Profile Update", tenantProfileId); | |
224 | 225 | TenantProfile tenantProfile = tenantProfileCache.get(tenantProfileId); |
225 | 226 | updateLock.lock(); |
226 | 227 | try { |
... | ... | @@ -236,6 +237,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { |
236 | 237 | |
237 | 238 | @Override |
238 | 239 | public void onTenantUpdate(TenantId tenantId) { |
240 | + log.info("[{}] On Tenant Update.", tenantId); | |
239 | 241 | TenantProfile tenantProfile = tenantProfileCache.get(tenantId); |
240 | 242 | updateLock.lock(); |
241 | 243 | try { |
... | ... | @@ -248,16 +250,16 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { |
248 | 250 | } |
249 | 251 | } |
250 | 252 | |
251 | - private void updateTenantState(TenantApiUsageState state, TenantProfile tenantProfile) { | |
253 | + private void updateTenantState(TenantApiUsageState state, TenantProfile profile) { | |
252 | 254 | TenantProfileData oldProfileData = state.getTenantProfileData(); |
253 | - state.setTenantProfileId(tenantProfile.getId()); | |
254 | - state.setTenantProfileData(tenantProfile.getProfileData()); | |
255 | + state.setTenantProfileId(profile.getId()); | |
256 | + state.setTenantProfileData(profile.getProfileData()); | |
255 | 257 | Map<ApiFeature, ApiUsageStateValue> result = state.checkStateUpdatedDueToThresholds(); |
256 | 258 | if (!result.isEmpty()) { |
257 | 259 | persistAndNotify(state, result); |
258 | 260 | } |
259 | 261 | updateProfileThresholds(state.getTenantId(), state.getApiUsageState().getId(), |
260 | - oldProfileData.getConfiguration(), tenantProfile.getProfileData().getConfiguration()); | |
262 | + oldProfileData.getConfiguration(), profile.getProfileData().getConfiguration()); | |
261 | 263 | } |
262 | 264 | |
263 | 265 | private void updateProfileThresholds(TenantId tenantId, ApiUsageStateId id, | ... | ... |
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
... | ... | @@ -134,7 +134,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { |
134 | 134 | TenantProfile isolatedTbCoreProfile = new TenantProfile(); |
135 | 135 | isolatedTbCoreProfile.setDefault(false); |
136 | 136 | isolatedTbCoreProfile.setName("Isolated TB Core"); |
137 | - isolatedTbCoreProfile.setProfileData(new TenantProfileData()); | |
138 | 137 | isolatedTbCoreProfile.setDescription("Isolated TB Core tenant profile"); |
139 | 138 | isolatedTbCoreProfile.setIsolatedTbCore(true); |
140 | 139 | isolatedTbCoreProfile.setIsolatedTbRuleEngine(false); |
... | ... | @@ -148,7 +147,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { |
148 | 147 | TenantProfile isolatedTbRuleEngineProfile = new TenantProfile(); |
149 | 148 | isolatedTbRuleEngineProfile.setDefault(false); |
150 | 149 | isolatedTbRuleEngineProfile.setName("Isolated TB Rule Engine"); |
151 | - isolatedTbRuleEngineProfile.setProfileData(new TenantProfileData()); | |
152 | 150 | isolatedTbRuleEngineProfile.setDescription("Isolated TB Rule Engine tenant profile"); |
153 | 151 | isolatedTbRuleEngineProfile.setIsolatedTbCore(false); |
154 | 152 | isolatedTbRuleEngineProfile.setIsolatedTbRuleEngine(true); |
... | ... | @@ -163,7 +161,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { |
163 | 161 | TenantProfile isolatedTbCoreAndTbRuleEngineProfile = new TenantProfile(); |
164 | 162 | isolatedTbCoreAndTbRuleEngineProfile.setDefault(false); |
165 | 163 | isolatedTbCoreAndTbRuleEngineProfile.setName("Isolated TB Core and TB Rule Engine"); |
166 | - isolatedTbCoreAndTbRuleEngineProfile.setProfileData(new TenantProfileData()); | |
167 | 164 | isolatedTbCoreAndTbRuleEngineProfile.setDescription("Isolated TB Core and TB Rule Engine tenant profile"); |
168 | 165 | isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbCore(true); |
169 | 166 | isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbRuleEngine(true); | ... | ... |
... | ... | @@ -274,6 +274,7 @@ public class DefaultTbClusterService implements TbClusterService { |
274 | 274 | TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer(); |
275 | 275 | Set<String> tbRuleEngineServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)); |
276 | 276 | if (msg.getEntityId().getEntityType().equals(EntityType.TENANT) |
277 | + || msg.getEntityId().getEntityType().equals(EntityType.TENANT_PROFILE) | |
277 | 278 | || msg.getEntityId().getEntityType().equals(EntityType.DEVICE_PROFILE) |
278 | 279 | || msg.getEntityId().getEntityType().equals(EntityType.API_USAGE_STATE)) { |
279 | 280 | TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer(); | ... | ... |
... | ... | @@ -153,6 +153,8 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene |
153 | 153 | TbActorMsg actorMsg = actorMsgOpt.get(); |
154 | 154 | if (actorMsg instanceof ComponentLifecycleMsg) { |
155 | 155 | ComponentLifecycleMsg componentLifecycleMsg = (ComponentLifecycleMsg) actorMsg; |
156 | + log.info("[{}][{}][{}] Received Lifecycle event: {}", componentLifecycleMsg.getTenantId(), componentLifecycleMsg.getEntityId().getEntityType(), | |
157 | + componentLifecycleMsg.getEntityId(), componentLifecycleMsg.getEvent()); | |
156 | 158 | if (EntityType.TENANT_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) { |
157 | 159 | TenantProfileId tenantProfileId = new TenantProfileId(componentLifecycleMsg.getEntityId().getId()); |
158 | 160 | tenantProfileCache.evict(tenantProfileId); | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -164,22 +164,25 @@ public class DefaultTransportApiService implements TransportApiService { |
164 | 164 | } |
165 | 165 | |
166 | 166 | private ListenableFuture<TransportApiResponseMsg> validateCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) { |
167 | - DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName()); | |
168 | - if (credentials != null) { | |
169 | - if (credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) { | |
170 | - return getDeviceInfo(credentials.getDeviceId(), credentials); | |
171 | - } else if (credentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) { | |
172 | - if (!checkMqttCredentials(mqtt, credentials)) { | |
173 | - credentials = null; | |
167 | + DeviceCredentials credentials = null; | |
168 | + if (!StringUtils.isEmpty(mqtt.getUserName())) { | |
169 | + credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName()); | |
170 | + if (credentials != null) { | |
171 | + if (credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) { | |
172 | + return getDeviceInfo(credentials.getDeviceId(), credentials); | |
173 | + } else if (credentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) { | |
174 | + if (!checkMqttCredentials(mqtt, credentials)) { | |
175 | + credentials = null; | |
176 | + } | |
174 | 177 | } |
175 | 178 | } |
176 | - } | |
177 | - if (credentials == null) { | |
178 | - credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash("|", mqtt.getClientId(), mqtt.getUserName())); | |
179 | 179 | if (credentials == null) { |
180 | - credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash(mqtt.getClientId())); | |
180 | + credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash("|", mqtt.getClientId(), mqtt.getUserName())); | |
181 | 181 | } |
182 | 182 | } |
183 | + if (credentials == null) { | |
184 | + credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash(mqtt.getClientId())); | |
185 | + } | |
183 | 186 | if (credentials != null) { |
184 | 187 | return getDeviceInfo(credentials.getDeviceId(), credentials); |
185 | 188 | } else { | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -94,7 +94,9 @@ public class DefaultTbApiUsageClient implements TbApiUsageClient { |
94 | 94 | TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).newByTopic(msgProducer.getDefaultTopic()); |
95 | 95 | msgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), builder.build()), null); |
96 | 96 | })); |
97 | - log.info("Report statistics for: {} tenants", report.size()); | |
97 | + if (!report.isEmpty()) { | |
98 | + log.info("Report statistics for: {} tenants", report.size()); | |
99 | + } | |
98 | 100 | } catch (Exception e) { |
99 | 101 | log.warn("Failed to report statistics: ", e); |
100 | 102 | } | ... | ... |
... | ... | @@ -122,7 +122,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement |
122 | 122 | log.trace("[{}] Processing msg: {}", sessionId, msg); |
123 | 123 | try { |
124 | 124 | if (msg instanceof MqttMessage) { |
125 | - processMqttMsg(ctx, (MqttMessage) msg); | |
125 | + MqttMessage message = (MqttMessage) msg; | |
126 | + if (message.decoderResult().isSuccess()) { | |
127 | + processMqttMsg(ctx, message); | |
128 | + } else { | |
129 | + log.error("[{}] Message processing failed: {}", sessionId, message.decoderResult().cause().getMessage()); | |
130 | + ctx.close(); | |
131 | + } | |
126 | 132 | } else { |
127 | 133 | ctx.close(); |
128 | 134 | } |
... | ... | @@ -464,8 +470,10 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement |
464 | 470 | String userName = msg.payload().userName(); |
465 | 471 | log.info("[{}] Processing connect msg for client with user name: {}!", sessionId, userName); |
466 | 472 | TransportProtos.ValidateBasicMqttCredRequestMsg.Builder request = TransportProtos.ValidateBasicMqttCredRequestMsg.newBuilder() |
467 | - .setClientId(msg.payload().clientIdentifier()) | |
468 | - .setUserName(userName); | |
473 | + .setClientId(msg.payload().clientIdentifier()); | |
474 | + if (userName != null) { | |
475 | + request.setUserName(userName); | |
476 | + } | |
469 | 477 | String password = msg.payload().password(); |
470 | 478 | if (password != null) { |
471 | 479 | request.setPassword(password); | ... | ... |
... | ... | @@ -21,6 +21,7 @@ import org.springframework.util.StringUtils; |
21 | 21 | import org.thingsboard.server.common.data.EntityType; |
22 | 22 | import org.thingsboard.server.common.data.TenantProfile; |
23 | 23 | import org.thingsboard.server.common.data.id.DeviceId; |
24 | +import org.thingsboard.server.common.data.id.EntityId; | |
24 | 25 | import org.thingsboard.server.common.data.id.TenantId; |
25 | 26 | import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; |
26 | 27 | import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; |
... | ... | @@ -77,6 +78,7 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi |
77 | 78 | |
78 | 79 | @Override |
79 | 80 | public void update(TenantProfileUpdateResult update) { |
81 | + log.info("Received tenant profile update: {}", update.getProfile()); | |
80 | 82 | EntityTransportRateLimits tenantRateLimitPrototype = createRateLimits(update.getProfile(), true); |
81 | 83 | EntityTransportRateLimits deviceRateLimitPrototype = createRateLimits(update.getProfile(), false); |
82 | 84 | for (TenantId tenantId : update.getAffectedTenants()) { |
... | ... | @@ -114,16 +116,26 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi |
114 | 116 | tenantAllowed.put(tenantId, allowed); |
115 | 117 | } |
116 | 118 | |
117 | - private <T> void mergeLimits(T deviceId, EntityTransportRateLimits newRateLimits, | |
118 | - Function<T, EntityTransportRateLimits> getFunction, | |
119 | - BiConsumer<T, EntityTransportRateLimits> putFunction) { | |
120 | - EntityTransportRateLimits oldRateLimits = getFunction.apply(deviceId); | |
119 | + private <T extends EntityId> void mergeLimits(T entityId, EntityTransportRateLimits newRateLimits, | |
120 | + Function<T, EntityTransportRateLimits> getFunction, | |
121 | + BiConsumer<T, EntityTransportRateLimits> putFunction) { | |
122 | + EntityTransportRateLimits oldRateLimits = getFunction.apply(entityId); | |
121 | 123 | if (oldRateLimits == null) { |
122 | - putFunction.accept(deviceId, newRateLimits); | |
124 | + if (EntityType.TENANT.equals(entityId.getEntityType())) { | |
125 | + log.info("[{}] New rate limits: {}", entityId, newRateLimits); | |
126 | + } else { | |
127 | + log.debug("[{}] New rate limits: {}", entityId, newRateLimits); | |
128 | + } | |
129 | + putFunction.accept(entityId, newRateLimits); | |
123 | 130 | } else { |
124 | 131 | EntityTransportRateLimits updated = merge(oldRateLimits, newRateLimits); |
125 | 132 | if (updated != null) { |
126 | - putFunction.accept(deviceId, updated); | |
133 | + if (EntityType.TENANT.equals(entityId.getEntityType())) { | |
134 | + log.info("[{}] Updated rate limits: {}", entityId, updated); | |
135 | + } else { | |
136 | + log.debug("[{}] Updated rate limits: {}", entityId, updated); | |
137 | + } | |
138 | + putFunction.accept(entityId, updated); | |
127 | 139 | } |
128 | 140 | } |
129 | 141 | } | ... | ... |
... | ... | @@ -548,7 +548,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe |
548 | 548 | |
549 | 549 | @Override |
550 | 550 | protected void validateDataImpl(TenantId tenantId, Device device) { |
551 | - if (StringUtils.isEmpty(device.getName())) { | |
551 | + if (StringUtils.isEmpty(device.getName()) || device.getName().trim().length() == 0) { | |
552 | 552 | throw new DataValidationException("Device name should be specified!"); |
553 | 553 | } |
554 | 554 | if (device.getTenantId() == null) { | ... | ... |
... | ... | @@ -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, "(select aus.id, aus.created_time, aus.tenant_id, '' as name, '' as additional_info from api_usage_state as aus)"); | |
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,6 +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 = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME)); | |
83 | 84 | public static final Set<String> commonEntityFieldsSet = new HashSet<>(commonEntityFields); |
84 | 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)); |
85 | 86 | |
... | ... | @@ -99,6 +100,7 @@ public class EntityKeyMapping { |
99 | 100 | allowedEntityFieldMap.put(EntityType.RULE_NODE, new HashSet<>(commonEntityFields)); |
100 | 101 | allowedEntityFieldMap.put(EntityType.WIDGET_TYPE, new HashSet<>(widgetEntityFields)); |
101 | 102 | allowedEntityFieldMap.put(EntityType.WIDGETS_BUNDLE, new HashSet<>(widgetEntityFields)); |
103 | + allowedEntityFieldMap.put(EntityType.API_USAGE_STATE, apiUsageStateEntityFields); | |
102 | 104 | |
103 | 105 | entityFieldColumnMap.put(CREATED_TIME, ModelConstants.CREATED_TIME_PROPERTY); |
104 | 106 | entityFieldColumnMap.put(ENTITY_TYPE, ModelConstants.ENTITY_TYPE_PROPERTY); | ... | ... |
... | ... | @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.usagerecord; |
17 | 17 | |
18 | 18 | import lombok.extern.slf4j.Slf4j; |
19 | 19 | import org.springframework.stereotype.Service; |
20 | +import org.thingsboard.server.common.data.ApiFeature; | |
20 | 21 | import org.thingsboard.server.common.data.ApiUsageRecordKey; |
21 | 22 | import org.thingsboard.server.common.data.ApiUsageState; |
22 | 23 | import org.thingsboard.server.common.data.ApiUsageStateValue; |
... | ... | @@ -27,6 +28,7 @@ import org.thingsboard.server.common.data.id.ApiUsageStateId; |
27 | 28 | import org.thingsboard.server.common.data.id.TenantId; |
28 | 29 | import org.thingsboard.server.common.data.kv.BasicTsKvEntry; |
29 | 30 | import org.thingsboard.server.common.data.kv.LongDataEntry; |
31 | +import org.thingsboard.server.common.data.kv.StringDataEntry; | |
30 | 32 | import org.thingsboard.server.common.data.kv.TsKvEntry; |
31 | 33 | import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; |
32 | 34 | import org.thingsboard.server.dao.entity.AbstractEntityService; |
... | ... | @@ -83,7 +85,19 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A |
83 | 85 | Tenant tenant = tenantDao.findById(tenantId, tenantId.getId()); |
84 | 86 | TenantProfile tenantProfile = tenantProfileDao.findById(tenantId, tenant.getTenantProfileId().getId()); |
85 | 87 | TenantProfileConfiguration configuration = tenantProfile.getProfileData().getConfiguration(); |
88 | + List<TsKvEntry> apiUsageStates = new ArrayList<>(); | |
89 | + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), | |
90 | + new StringDataEntry(ApiFeature.TRANSPORT.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); | |
91 | + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), | |
92 | + new StringDataEntry(ApiFeature.DB.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); | |
93 | + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), | |
94 | + new StringDataEntry(ApiFeature.RE.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); | |
95 | + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), | |
96 | + new StringDataEntry(ApiFeature.JS.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); | |
97 | + tsService.save(tenantId, saved.getId(), apiUsageStates, 0L); | |
98 | + | |
86 | 99 | List<TsKvEntry> profileThresholds = new ArrayList<>(); |
100 | + | |
87 | 101 | for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { |
88 | 102 | profileThresholds.add(new BasicTsKvEntry(saved.getCreatedTime(), new LongDataEntry(key.getApiLimitKey(), configuration.getProfileThreshold(key)))); |
89 | 103 | } | ... | ... |
... | ... | @@ -58,7 +58,7 @@ In case of any issues you can examine service logs for errors. |
58 | 58 | For example to see ThingsBoard node logs execute the following command: |
59 | 59 | |
60 | 60 | ` |
61 | -$ docker-compose logs -f tb-core1 tb-rule-engine1 | |
61 | +$ docker-compose logs -f tb-core1 tb-core2 tb-rule-engine1 tb-rule-engine2 tb-mqtt-transport1 tb-mqtt-transport2 | |
62 | 62 | ` |
63 | 63 | |
64 | 64 | Or use `docker-compose ps` to see the state of all the containers. | ... | ... |
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 | } | ... | ... |
... | ... | @@ -422,6 +422,12 @@ export class DashboardUtilsService { |
422 | 422 | widgetLayout.row = row; |
423 | 423 | widgetLayout.col = 0; |
424 | 424 | } |
425 | + | |
426 | + widgetLayout.sizeX = Math.floor(widgetLayout.sizeX); | |
427 | + widgetLayout.sizeY = Math.floor(widgetLayout.sizeY); | |
428 | + widgetLayout.row = Math.floor(widgetLayout.row); | |
429 | + widgetLayout.col = Math.floor(widgetLayout.col); | |
430 | + | |
425 | 431 | layout.widgets[widget.id] = widgetLayout; |
426 | 432 | } |
427 | 433 | ... | ... |
... | ... | @@ -63,6 +63,9 @@ |
63 | 63 | </mat-error> |
64 | 64 | </mat-form-field> |
65 | 65 | </div> |
66 | + <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration').hasError('unique')"> | |
67 | + {{ 'device-profile.mqtt-device-topic-filters-unique' | translate }} | |
68 | + </mat-error> | |
66 | 69 | <div class="tb-hint" innerHTML="{{ 'device-profile.support-level-wildcards' | translate }}"></div> |
67 | 70 | <div class="tb-hint" innerHTML="{{ 'device-profile.single-level-wildcards-hint' | translate }}"></div> |
68 | 71 | <div class="tb-hint" innerHTML="{{ 'device-profile.multi-level-wildcards-hint' | translate }}"></div> | ... | ... |
... | ... | @@ -51,7 +51,6 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control |
51 | 51 | |
52 | 52 | mqttTransportPayloadTypeTranslations = mqttTransportPayloadTypeTranslationMap; |
53 | 53 | |
54 | - | |
55 | 54 | mqttDeviceProfileTransportConfigurationFormGroup: FormGroup; |
56 | 55 | |
57 | 56 | private requiredValue: boolean; |
... | ... | @@ -87,7 +86,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control |
87 | 86 | deviceAttributesTopic: [null, [Validators.required, this.validationMQTTTopic()]], |
88 | 87 | deviceTelemetryTopic: [null, [Validators.required, this.validationMQTTTopic()]], |
89 | 88 | transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required] |
90 | - }) | |
89 | + }, {validator: this.uniqueDeviceTopicValidator}) | |
91 | 90 | }); |
92 | 91 | this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { |
93 | 92 | this.updateModel(); |
... | ... | @@ -147,4 +146,14 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control |
147 | 146 | return null; |
148 | 147 | }; |
149 | 148 | } |
149 | + | |
150 | + private uniqueDeviceTopicValidator(control: FormGroup): { [key: string]: boolean } | null { | |
151 | + if (control.value) { | |
152 | + const formValue = control.value as MqttDeviceProfileTransportConfiguration; | |
153 | + if (formValue.deviceAttributesTopic === formValue.deviceTelemetryTopic) { | |
154 | + return {unique: true}; | |
155 | + } | |
156 | + } | |
157 | + return null; | |
158 | + } | |
150 | 159 | } | ... | ... |
... | ... | @@ -131,10 +131,13 @@ export default abstract class LeafletMap { |
131 | 131 | tooltipAnchor: [16, -28], |
132 | 132 | shadowSize: [41, 41] |
133 | 133 | }); |
134 | + const customLatLng = this.convertToCustomFormat(mousePositionOnMap); | |
135 | + mousePositionOnMap.lat = customLatLng[this.options.latKeyName]; | |
136 | + mousePositionOnMap.lng = customLatLng[this.options.lngKeyName]; | |
137 | + | |
134 | 138 | const newMarker = L.marker(mousePositionOnMap, { icon }).addTo(this.map); |
135 | 139 | this.addMarkers.push(newMarker); |
136 | 140 | const datasourcesList = document.createElement('div'); |
137 | - const customLatLng = this.convertToCustomFormat(mousePositionOnMap); | |
138 | 141 | const header = document.createElement('p'); |
139 | 142 | header.appendChild(document.createTextNode('Select entity:')); |
140 | 143 | header.setAttribute('style', 'font-size: 14px; margin: 8px 0'); |
... | ... | @@ -410,10 +413,15 @@ export default abstract class LeafletMap { |
410 | 413 | } |
411 | 414 | |
412 | 415 | convertToCustomFormat(position: L.LatLng): object { |
413 | - return { | |
414 | - [this.options.latKeyName]: position.lat % 90, | |
415 | - [this.options.lngKeyName]: position.lng % 180 | |
416 | - }; | |
416 | + if (position.lng > 180) { | |
417 | + position.lng = 180; | |
418 | + } else if (position.lng < -180) { | |
419 | + position.lng = -180; | |
420 | + } | |
421 | + return { | |
422 | + [this.options.latKeyName]: position.lat, | |
423 | + [this.options.lngKeyName]: position.lng | |
424 | + }; | |
417 | 425 | } |
418 | 426 | |
419 | 427 | convertToPolygonFormat(points: Array<any>): Array<any> { | ... | ... |
... | ... | @@ -94,7 +94,7 @@ export class Marker { |
94 | 94 | } |
95 | 95 | |
96 | 96 | updateMarkerPosition(position: L.LatLng) { |
97 | - if (!this.location.equals(position)) { | |
97 | + if (!this.leafletMarker.getLatLng().equals(position)) { | |
98 | 98 | this.location = position; |
99 | 99 | this.leafletMarker.setLatLng(position); |
100 | 100 | } | ... | ... |
... | ... | @@ -259,9 +259,27 @@ export class ImageMap extends LeafletMap { |
259 | 259 | |
260 | 260 | convertToCustomFormat(position: L.LatLng, width = this.width, height = this.height): object { |
261 | 261 | const point = this.latLngToPoint(position); |
262 | + const customX = calculateNewPointCoordinate(point.x, width); | |
263 | + const customY = calculateNewPointCoordinate(point.y, height); | |
264 | + | |
265 | + if (customX === 0) { | |
266 | + point.x = 0; | |
267 | + } else if (customX === 1) { | |
268 | + point.x = width; | |
269 | + } | |
270 | + | |
271 | + if (customY === 0) { | |
272 | + point.y = 0; | |
273 | + } else if (customY === 1) { | |
274 | + point.y = height; | |
275 | + } | |
276 | + const customLatLng = this.pointToLatLng(point.x, point.y); | |
277 | + | |
262 | 278 | return { |
263 | - [this.options.xPosKeyName]: calculateNewPointCoordinate(point.x, width), | |
264 | - [this.options.yPosKeyName]: calculateNewPointCoordinate(point.y, height) | |
279 | + [this.options.xPosKeyName]: customX, | |
280 | + [this.options.yPosKeyName]: customY, | |
281 | + [this.options.latKeyName]: customLatLng.lat, | |
282 | + [this.options.lngKeyName]: customLatLng.lng | |
265 | 283 | }; |
266 | 284 | } |
267 | 285 | ... | ... |
... | ... | @@ -47,7 +47,8 @@ export enum EntityType { |
47 | 47 | RULE_NODE = 'RULE_NODE', |
48 | 48 | ENTITY_VIEW = 'ENTITY_VIEW', |
49 | 49 | WIDGETS_BUNDLE = 'WIDGETS_BUNDLE', |
50 | - WIDGET_TYPE = 'WIDGET_TYPE' | |
50 | + WIDGET_TYPE = 'WIDGET_TYPE', | |
51 | + API_USAGE_STATE = 'API_USAGE_STATE' | |
51 | 52 | } |
52 | 53 | |
53 | 54 | export enum AliasEntityType { |
... | ... | @@ -239,6 +240,12 @@ export const entityTypeTranslations = new Map<EntityType | AliasEntityType, Enti |
239 | 240 | } |
240 | 241 | ], |
241 | 242 | [ |
243 | + EntityType.API_USAGE_STATE, | |
244 | + { | |
245 | + type: 'entity.type-api-usage-state' | |
246 | + } | |
247 | + ], | |
248 | + [ | |
242 | 249 | EntityType.WIDGETS_BUNDLE, |
243 | 250 | { |
244 | 251 | details: 'widgets-bundle.widgets-bundle-details', | ... | ... |
... | ... | @@ -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", |
... | ... | @@ -1099,7 +1100,8 @@ |
1099 | 1100 | "details": "Entity details", |
1100 | 1101 | "no-entities-prompt": "No entities found", |
1101 | 1102 | "no-data": "No data to display", |
1102 | - "columns-to-display": "Columns to Display" | |
1103 | + "columns-to-display": "Columns to Display", | |
1104 | + "type-api-usage-state": "Api Usage State" | |
1103 | 1105 | }, |
1104 | 1106 | "entity-field": { |
1105 | 1107 | "created-time": "Created time", | ... | ... |