From 7a09f8c7185cbd6c00e90211852cc3fb1d1bf70b Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 13 Oct 2020 14:46:35 +0300 Subject: [PATCH 01/23] Minor improvemetns --- .../service/install/update/DefaultDataUpdateService.java | 2 +- .../server/transport/mqtt/MqttTransportHandler.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index bc86857c4d..aded7868bd 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -170,7 +170,7 @@ public class DefaultDataUpdateService implements DataUpdateService { ruleChainService.saveRuleChainMetaData(tenant.getId(), md); } } catch (Exception e) { - log.error("Unable to update Tenant", e); + log.error("[{}] Unable to update Tenant: {}", tenant.getId(), tenant.getName(), e); } } }; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index fab0579bb2..3723b19f6d 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -263,12 +263,12 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private void processDevicePublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, String topicName, int msgId) { try { MqttTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor(); - if (deviceSessionCtx.isDeviceTelemetryTopic(topicName)) { - TransportProtos.PostTelemetryMsg postTelemetryMsg = payloadAdaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg); - transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg)); - } else if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) { + if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) { TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg)); + } else if (deviceSessionCtx.isDeviceTelemetryTopic(topicName)) { + TransportProtos.PostTelemetryMsg postTelemetryMsg = payloadAdaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg); + transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg)); } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) { TransportProtos.GetAttributeRequestMsg getAttributeMsg = payloadAdaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg)); From b0126a9d47f435922bf9bfe08c85754f67afced1 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 13 Oct 2020 15:17:18 +0300 Subject: [PATCH 02/23] added new APIs --- .../thingsboard/rest/client/RestClient.java | 392 +++++++++++++++++- 1 file changed, 388 insertions(+), 4 deletions(-) diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index d2ba4d43b4..0a92d4b1a8 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -38,10 +38,18 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceInfo; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileInfo; +import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.EntityViewInfo; import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.TenantInfo; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.UpdateMessage; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; @@ -50,6 +58,7 @@ import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.asset.AssetSearchQuery; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.AuditLog; @@ -60,28 +69,41 @@ import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.kv.Aggregation; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.data.query.AlarmData; +import org.thingsboard.server.common.data.query.AlarmDataQuery; +import org.thingsboard.server.common.data.query.EntityCountQuery; +import org.thingsboard.server.common.data.query.EntityData; +import org.thingsboard.server.common.data.query.EntityDataQuery; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelationInfo; import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.data.rule.DefaultRuleChainCreateRequest; import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainData; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; @@ -325,6 +347,19 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } + public Optional getAssetInfoById(AssetId assetId) { + try { + ResponseEntity asset = restTemplate.getForEntity(baseURL + "/api/asset/info/{assetId}", AssetInfo.class, assetId.getId()); + return Optional.ofNullable(asset.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + public Asset saveAsset(Asset asset) { return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody(); } @@ -390,6 +425,20 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return assets.getBody(); } + public PageData getTenantAssetInfos(PageLink pageLink, String assetType) { + Map params = new HashMap<>(); + params.put("type", assetType); + addPageLinkToParam(params, pageLink); + + ResponseEntity> assets = restTemplate.exchange( + baseURL + "/api/tenant/assetInfos?type={type}&" + getUrlParams(pageLink), + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params); + return assets.getBody(); + } + public Optional getTenantAsset(String assetName) { try { ResponseEntity asset = restTemplate.getForEntity(baseURL + "/api/tenant/assets?assetName={assetName}", Asset.class, assetName); @@ -419,6 +468,22 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return assets.getBody(); } + public PageData getCustomerAssetInfos(CustomerId customerId, PageLink pageLink, String assetType) { + Map params = new HashMap<>(); + params.put("customerId", customerId.getId().toString()); + params.put("type", assetType); + addPageLinkToParam(params, pageLink); + + ResponseEntity> assets = restTemplate.exchange( + baseURL + "/api/customer/{customerId}/assetInfos?type={type}&" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params); + return assets.getBody(); + } + public List getAssetsByIds(List assetIds) { return restTemplate.exchange( baseURL + "/api/assets?assetIds={assetIds}", @@ -593,11 +658,15 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } public Optional activateUser(UserId userId, String password) { + return activateUser(userId, password, true); + } + + public Optional activateUser(UserId userId, String password, boolean sendActivationMail) { ObjectNode activateRequest = objectMapper.createObjectNode(); activateRequest.put("activateToken", getActivateToken(userId)); activateRequest.put("password", password); try { - ResponseEntity jsonNode = restTemplate.postForEntity(baseURL + "/api/noauth/activate", activateRequest, JsonNode.class); + ResponseEntity jsonNode = restTemplate.postForEntity(baseURL + "/api/noauth/activate?sendActivationMail={sendActivationMail}", activateRequest, JsonNode.class, sendActivationMail); return Optional.ofNullable(jsonNode.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -936,8 +1005,25 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } + public Optional getDeviceInfoById(DeviceId deviceId) { + try { + ResponseEntity device = restTemplate.getForEntity(baseURL + "/api/device/info/{deviceId}", DeviceInfo.class, deviceId); + return Optional.ofNullable(device.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + public Device saveDevice(Device device) { - return restTemplate.postForEntity(baseURL + "/api/device", device, Device.class).getBody(); + return saveDevice(device, null); + } + + public Device saveDevice(Device device, String accessToken) { + return restTemplate.postForEntity(baseURL + "/api/device?accessToken={accessToken}", device, Device.class, accessToken).getBody(); } public void deleteDevice(DeviceId deviceId) { @@ -1011,6 +1097,18 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { }, params).getBody(); } + public PageData getTenantDeviceInfos(String type, DeviceProfileId deviceProfileId, PageLink pageLink) { + Map params = new HashMap<>(); + params.put("type", type); + params.put("deviceProfileId", deviceProfileId != null ? deviceProfileId.toString() : null); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/tenant/deviceInfos?type={type}&deviceProfileId={deviceProfileId}&" + getUrlParams(pageLink), + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + public Optional getTenantDevice(String deviceName) { try { ResponseEntity device = restTemplate.getForEntity(baseURL + "/api/tenant/devices?deviceName={deviceName}", Device.class, deviceName); @@ -1036,6 +1134,19 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { }, params).getBody(); } + public PageData getCustomerDeviceInfos(CustomerId customerId, String deviceType, DeviceProfileId deviceProfileId, PageLink pageLink) { + Map params = new HashMap<>(); + params.put("customerId", customerId.toString()); + params.put("type", deviceType); + params.put("deviceProfileId", deviceProfileId != null ? deviceProfileId.toString() : null); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/customer/{customerId}/devices?type={type}&deviceProfileId={deviceProfileId}&" + getUrlParams(pageLink), + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + public List getDevicesByIds(List deviceIds) { return restTemplate.exchange(baseURL + "/api/devices?deviceIds={deviceIds}", HttpMethod.GET, @@ -1074,6 +1185,12 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { restTemplate.delete(baseURL + "/api/customer/device/{deviceName}/claim", deviceName); } + public Device assignDeviceToTenant(TenantId tenantId, DeviceId deviceId) { + return restTemplate.postForEntity( + baseURL + "/api/tenant/{tenantId}/device/{deviceId}", + HttpEntity.EMPTY, Device.class, tenantId, deviceId).getBody(); + } + @Deprecated public Device createDevice(String name, String type) { Device device = new Device(); @@ -1138,6 +1255,91 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { customerId.toString(), deviceId.toString()).getBody(); } + public Optional getDeviceProfileById(DeviceProfileId deviceProfileId) { + try { + ResponseEntity deviceProfile = restTemplate.getForEntity(baseURL + "/api/deviceProfile/{deviceProfileId}", DeviceProfile.class, deviceProfileId); + return Optional.ofNullable(deviceProfile.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional getDeviceProfileInfoById(DeviceProfileId deviceProfileId) { + try { + ResponseEntity deviceProfileInfo = restTemplate.getForEntity(baseURL + "/api/deviceProfileInfo/{deviceProfileId}", DeviceProfileInfo.class, deviceProfileId); + return Optional.ofNullable(deviceProfileInfo.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public DeviceProfileInfo getDefaultDeviceProfileInfo() { + return restTemplate.getForEntity(baseURL + "/api/deviceProfileInfo/default", DeviceProfileInfo.class).getBody(); + } + + public DeviceProfile saveDeviceProfile(DeviceProfile deviceProfile) { + return restTemplate.postForEntity(baseURL + "/api/deviceProfile", deviceProfile, DeviceProfile.class).getBody(); + } + + public void deleteDeviceProfile(DeviceProfileId deviceProfileId) { + restTemplate.delete(baseURL + "/api/deviceProfile/{deviceProfileId}", deviceProfileId); + } + + public DeviceProfile setDefaultDeviceProfile(DeviceProfileId deviceProfileId) { + return restTemplate.postForEntity( + baseURL + "/api/deviceProfile/{deviceProfileId}/default", + HttpEntity.EMPTY, DeviceProfile.class, deviceProfileId).getBody(); + } + + public PageData getTenantDevices(PageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/deviceProfiles?" + getUrlParams(pageLink), + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + + public PageData getDeviceProfileInfos(PageLink pageLink, DeviceTransportType deviceTransportType) { + Map params = new HashMap<>(); + params.put("deviceTransportType", deviceTransportType != null ? deviceTransportType.name() : null); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/deviceProfileInfos?deviceTransportType={deviceTransportType}&" + getUrlParams(pageLink), + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + + public Long countEntitiesByQuery(EntityCountQuery query) { + return restTemplate.postForObject(baseURL + "/api/entitiesQuery/count", query, Long.class); + } + + public PageData findEntityDataByQuery(EntityDataQuery query) { + return restTemplate.exchange( + baseURL + "/api/entitiesQuery/find", + HttpMethod.POST, new HttpEntity<>(query), + new ParameterizedTypeReference>() { + }).getBody(); + } + + public PageData findAlarmDataByQuery(AlarmDataQuery query) { + return restTemplate.exchange( + baseURL + "/api/alarmsQuery/find", + HttpMethod.POST, new HttpEntity<>(query), + new ParameterizedTypeReference>() { + }).getBody(); + } + public void saveRelation(EntityRelation relation) { restTemplate.postForLocation(baseURL + "/api/relation", relation); } @@ -1313,6 +1515,19 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } + public Optional getEntityViewInfoById(EntityViewId entityViewId) { + try { + ResponseEntity entityView = restTemplate.getForEntity(baseURL + "/api/entityView/info/{entityViewId}", EntityViewInfo.class, entityViewId); + return Optional.ofNullable(entityView.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + public EntityView saveEntityView(EntityView entityView) { return restTemplate.postForEntity(baseURL + "/api/entityView", entityView, EntityView.class).getBody(); } @@ -1373,6 +1588,19 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { }, params).getBody(); } + public PageData getCustomerEntityViewInfos(CustomerId customerId, String entityViewType, PageLink pageLink) { + Map params = new HashMap<>(); + params.put("customerId", customerId.toString()); + params.put("type", entityViewType); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/customer/{customerId}/entityViewInfos?type={type}&" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + public PageData getTenantEntityViews(String entityViewType, PageLink pageLink) { Map params = new HashMap<>(); params.put("type", entityViewType); @@ -1385,6 +1613,18 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { }, params).getBody(); } + public PageData getTenantEntityViewInfos(String entityViewType, PageLink pageLink) { + Map params = new HashMap<>(); + params.put("type", entityViewType); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/tenant/entityViewInfos?type={type}&" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + public List findByQuery(EntityViewSearchQuery query) { return restTemplate.exchange(baseURL + "/api/entityViews", HttpMethod.POST, new HttpEntity<>(query), new ParameterizedTypeReference>() { }).getBody(); @@ -1437,8 +1677,41 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { - }, - params).getBody(); + }, params).getBody(); + } + + public OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { + return restTemplate.postForEntity(baseURL + "/api/oauth2/config/template", clientRegistrationTemplate, OAuth2ClientRegistrationTemplate.class).getBody(); + } + + public void deleteClientRegistrationTemplate(OAuth2ClientRegistrationTemplateId oAuth2ClientRegistrationTemplateId) { + restTemplate.delete(baseURL + "/api/oauth2/config/template/{clientRegistrationTemplateId}", oAuth2ClientRegistrationTemplateId); + } + + public List getClientRegistrationTemplates() { + return restTemplate.exchange( + baseURL + "/api/oauth2/config/template", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }).getBody(); + } + + public List getOAuth2Clients() { + return restTemplate.exchange( + baseURL + "/api/noauth/oauth2Clients", + HttpMethod.POST, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }).getBody(); + } + + public OAuth2ClientsParams getCurrentOAuth2Params() { + return restTemplate.getForEntity(baseURL + "/api/oauth2/config", OAuth2ClientsParams.class).getBody(); + } + + public OAuth2ClientsParams saveOAuth2Params(OAuth2ClientsParams oauth2Params) { + return restTemplate.postForEntity(baseURL + "/api/oauth2/config", oauth2Params, OAuth2ClientsParams.class).getBody(); } public void handleOneWayDeviceRPCRequest(DeviceId deviceId, JsonNode requestBody) { @@ -1485,6 +1758,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return restTemplate.postForEntity(baseURL + "/api/ruleChain", ruleChain, RuleChain.class).getBody(); } + public RuleChain saveRuleChain(DefaultRuleChainCreateRequest request) { + return restTemplate.postForEntity(baseURL + "/api/ruleChain/device/default", request, RuleChain.class).getBody(); + } + public Optional setRootRuleChain(RuleChainId ruleChainId) { try { ResponseEntity ruleChain = restTemplate.postForEntity(baseURL + "/api/ruleChain/{ruleChainId}/root", null, RuleChain.class, ruleChainId.getId()); @@ -1544,6 +1821,14 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } + public RuleChainData exportRuleChains(int limit) { + return restTemplate.getForEntity(baseURL + "/api/ruleChains/export?limit=" + limit, RuleChainData.class).getBody(); + } + + public void importRuleChains(RuleChainData ruleChainData, boolean overwrite) { + restTemplate.postForLocation(baseURL + "/api/ruleChains/import?overwrite=" + overwrite, ruleChainData); + } + public List getAttributeKeys(EntityId entityId) { return restTemplate.exchange( baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/keys/attributes", @@ -1805,6 +2090,19 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } + public Optional getTenantInfoById(TenantId tenantId) { + try { + ResponseEntity tenant = restTemplate.getForEntity(baseURL + "/api/tenant/info/{tenantId}", TenantInfo.class, tenantId); + return Optional.ofNullable(tenant.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + public Tenant saveTenant(Tenant tenant) { return restTemplate.postForEntity(baseURL + "/api/tenant", tenant, Tenant.class).getBody(); } @@ -1824,6 +2122,81 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { }, params).getBody(); } + public PageData getTenantInfos(PageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/tenantInfos?" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + + public Optional getTenantProfileById(TenantProfileId tenantProfileId) { + try { + ResponseEntity tenantProfile = restTemplate.getForEntity(baseURL + "/api/tenantProfile/{tenantProfileId}", TenantProfile.class, tenantProfileId); + return Optional.ofNullable(tenantProfile.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional getTenantProfileInfoById(TenantProfileId tenantProfileId) { + try { + ResponseEntity entityInfo = restTemplate.getForEntity(baseURL + "/api/tenantProfileInfo/{tenantProfileId}", EntityInfo.class, tenantProfileId); + return Optional.ofNullable(entityInfo.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public EntityInfo getDefaultTenantProfileInfo() { + return restTemplate.getForEntity(baseURL + "/api/tenantProfileInfo/default", EntityInfo.class).getBody(); + } + + public TenantProfile saveTenantProfile(TenantProfile tenantProfile) { + return restTemplate.postForEntity(baseURL + "/api/tenantProfile", tenantProfile, TenantProfile.class).getBody(); + } + + public void deleteTenantProfile(TenantProfileId tenantProfileId) { + restTemplate.delete(baseURL + "/api/tenantProfile/{tenantProfileId}", tenantProfileId); + } + + public TenantProfile setDefaultTenantProfile(TenantProfileId tenantProfileId) { + return restTemplate.exchange(baseURL + "/api/tenantProfile/{tenantProfileId}/default", HttpMethod.POST, HttpEntity.EMPTY, TenantProfile.class, tenantProfileId).getBody(); + } + + public PageData getTenantProfiles(PageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/tenantProfiles" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + + public PageData getTenantProfileInfos(PageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/tenantProfileInfos" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + public Optional getUserById(UserId userId) { try { ResponseEntity user = restTemplate.getForEntity(baseURL + "/api/user/{userId}", User.class, userId.getId()); @@ -1870,6 +2243,17 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { restTemplate.delete(baseURL + "/api/user/{userId}", userId.getId()); } + public PageData getUsers(PageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/users" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + public PageData getTenantAdmins(TenantId tenantId, PageLink pageLink) { Map params = new HashMap<>(); params.put("tenantId", tenantId.getId().toString()); From 52e6e76ac6c46cbead43f17b6c53b7ebc967dba6 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 13 Oct 2020 19:48:46 +0300 Subject: [PATCH 03/23] Oauth2 - set provider name to created user additionalInfo. UI: Improve device profile alarm rules. --- .../oauth2/AbstractOAuth2ClientMapper.java | 22 ++++++--- .../auth/oauth2/BasicOAuth2ClientMapper.java | 6 ++- .../auth/oauth2/CustomOAuth2ClientMapper.java | 6 ++- .../auth/oauth2/GithubOAuth2ClientMapper.java | 6 ++- .../auth/oauth2/OAuth2ClientMapper.java | 4 +- .../Oauth2AuthenticationSuccessHandler.java | 4 +- .../entity/add-entity-dialog.component.html | 10 ++--- .../filter/filter-text.component.html | 2 +- .../filter/filter-text.component.scss | 5 +++ .../filter/filter-text.component.ts | 3 ++ .../add-device-profile-dialog.component.html | 45 +++++++++---------- .../add-device-profile-dialog.component.scss | 4 +- .../alarm/alarm-rule-condition.component.html | 25 +++++------ .../alarm/alarm-rule-condition.component.scss | 7 +-- .../profile/alarm/alarm-rule.component.html | 45 +++++++------------ .../profile/alarm/alarm-rule.component.scss | 30 ++++--------- .../profile/alarm/alarm-rule.component.ts | 3 +- .../alarm/alarm-schedule-info.component.html | 25 +++++------ .../alarm/alarm-schedule-info.component.scss | 10 ++--- .../alarm/alarm-schedule-info.component.ts | 2 +- .../alarm/create-alarm-rules.component.html | 4 +- .../alarm/device-profile-alarm.component.html | 2 +- .../edit-alarm-details-dialog.component.html | 10 ++--- .../edit-alarm-details-dialog.component.ts | 4 ++ .../device-wizard-dialog.component.html | 43 +++++++++--------- .../device-wizard-dialog.component.scss | 3 +- .../assets/locale/locale.constant-en_US.json | 5 ++- ui-ngx/src/styles.scss | 5 +-- 28 files changed, 165 insertions(+), 175 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java index 651e234f0a..f76521ac48 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java @@ -31,6 +31,8 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.IdBased; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; @@ -76,12 +78,15 @@ public abstract class AbstractOAuth2ClientMapper { private final Lock userCreationLock = new ReentrantLock(); - protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, boolean allowUserCreation, boolean activateUser) { + protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2ClientRegistrationInfo clientRegistration) { + + OAuth2MapperConfig config = clientRegistration.getMapperConfig(); + UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, oauth2User.getEmail()); User user = userService.findUserByEmail(TenantId.SYS_TENANT_ID, oauth2User.getEmail()); - if (user == null && !allowUserCreation) { + if (user == null && !config.isAllowUserCreation()) { throw new UsernameNotFoundException("User not found: " + oauth2User.getEmail()); } @@ -106,21 +111,28 @@ public abstract class AbstractOAuth2ClientMapper { user.setFirstName(oauth2User.getFirstName()); user.setLastName(oauth2User.getLastName()); + ObjectNode additionalInfo = objectMapper.createObjectNode(); + if (!StringUtils.isEmpty(oauth2User.getDefaultDashboardName())) { Optional dashboardIdOpt = user.getAuthority() == Authority.TENANT_ADMIN ? getDashboardId(tenantId, oauth2User.getDefaultDashboardName()) : getDashboardId(tenantId, customerId, oauth2User.getDefaultDashboardName()); if (dashboardIdOpt.isPresent()) { - ObjectNode additionalInfo = objectMapper.createObjectNode(); additionalInfo.put("defaultDashboardFullscreen", oauth2User.isAlwaysFullScreen()); additionalInfo.put("defaultDashboardId", dashboardIdOpt.get().getId().toString()); - user.setAdditionalInfo(additionalInfo); } } + if (clientRegistration.getAdditionalInfo() != null && + clientRegistration.getAdditionalInfo().has("providerName")) { + additionalInfo.put("authProviderName", clientRegistration.getAdditionalInfo().get("providerName").asText()); + } + + user.setAdditionalInfo(additionalInfo); + user = userService.saveUser(user); - if (activateUser) { + if (config.isActivateUser()) { UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId()); userService.activateUserCredentials(user.getTenantId(), userCredentials.getActivateToken(), passwordEncoder.encode("")); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java index 73da9e539f..bd6434f50a 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.security.auth.oauth2; import lombok.extern.slf4j.Slf4j; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.service.security.model.SecurityUser; @@ -29,11 +30,12 @@ import java.util.Map; public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { @Override - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) { + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) { + OAuth2MapperConfig config = clientRegistration.getMapperConfig(); Map attributes = token.getPrincipal().getAttributes(); String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); - return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser()); + return getOrCreateSecurityUserFromOAuth2User(oauth2User, clientRegistration); } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java index a85da830b0..0cd292f76f 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java @@ -23,6 +23,7 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.dao.oauth2.OAuth2User; @@ -38,9 +39,10 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); @Override - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) { + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) { + OAuth2MapperConfig config = clientRegistration.getMapperConfig(); OAuth2User oauth2User = getOAuth2User(token, providerAccessToken, config.getCustom()); - return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser()); + return getOrCreateSecurityUserFromOAuth2User(oauth2User, clientRegistration); } private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2CustomMapperConfig custom) { diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java index dcca2b71a1..f259720626 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java @@ -23,6 +23,7 @@ import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.dao.oauth2.OAuth2Configuration; import org.thingsboard.server.dao.oauth2.OAuth2User; @@ -45,12 +46,13 @@ public class GithubOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme private OAuth2Configuration oAuth2Configuration; @Override - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config) { + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) { + OAuth2MapperConfig config = clientRegistration.getMapperConfig(); Map githubMapperConfig = oAuth2Configuration.getGithubMapper(); String email = getEmail(githubMapperConfig.get(EMAIL_URL_KEY), providerAccessToken); Map attributes = token.getPrincipal().getAttributes(); OAuth2User oAuth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); - return getOrCreateSecurityUserFromOAuth2User(oAuth2User, config.isAllowUserCreation(), config.isActivateUser()); + return getOrCreateSecurityUserFromOAuth2User(oAuth2User, clientRegistration); } private synchronized String getEmail(String emailUrl, String oauth2Token) { diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java index 27b24043a5..4c9804930e 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java @@ -16,9 +16,9 @@ package org.thingsboard.server.service.security.auth.oauth2; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; import org.thingsboard.server.service.security.model.SecurityUser; public interface OAuth2ClientMapper { - SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2MapperConfig config); + SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index 5bb02b9a63..4840f03cf2 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -74,7 +74,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS token.getPrincipal().getName()); OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(clientRegistration.getMapperConfig().getType()); SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(), - clientRegistration.getMapperConfig()); + clientRegistration); JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); @@ -85,4 +85,4 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8.toString())); } } -} \ No newline at end of file +} diff --git a/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.html b/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.html index f1e427445f..78cad36bc5 100644 --- a/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.html @@ -33,11 +33,6 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-text.component.html b/ui-ngx/src/app/modules/home/components/filter/filter-text.component.html index 8aeacc51a2..b8d7103a3b 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-text.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filter-text.component.html @@ -15,5 +15,5 @@ limitations under the License. --> -
diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-text.component.scss b/ui-ngx/src/app/modules/home/components/filter/filter-text.component.scss index 3f232f3ce2..aaa95f0f3f 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-text.component.scss +++ b/ui-ngx/src/app/modules/home/components/filter/filter-text.component.scss @@ -21,6 +21,11 @@ } &.required { color: #f44336; + padding: 0 4px; + } + &.nowrap { + white-space: nowrap; + overflow: hidden; } } } diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-text.component.ts b/ui-ngx/src/app/modules/home/components/filter/filter-text.component.ts index e6eb5955b0..5499cdc765 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-text.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/filter-text.component.ts @@ -54,6 +54,9 @@ export class FilterTextComponent implements ControlValueAccessor, OnInit { @Input() addFilterPrompt = this.translate.instant('filter.add-filter-prompt'); + @Input() + nowrap = false; + requiredClass = false; private filterText: string; diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html index b5e4fd9795..fd5b5b17c2 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+

device-profile.add

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

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

@@ -37,11 +37,6 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html index 2d637c4066..f1d6790ae8 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html @@ -53,17 +53,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html index a61b3a80c6..46a0d213a4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html @@ -147,16 +147,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.html index d09b7aae24..8127b77e5d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.html @@ -38,16 +38,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarm-filter-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/alarm-filter-panel.component.html index b150110fca..3a834621e6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarm-filter-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarm-filter-panel.component.html @@ -50,17 +50,16 @@
+ -
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.html index 2ee1222168..05aabbb7a7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.html @@ -28,7 +28,6 @@
diff --git a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.html b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.html index 0b33828bc4..2fcc346477 100644 --- a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.html +++ b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.html @@ -40,17 +40,17 @@
- +
diff --git a/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.html b/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.html index 5c98c02fa1..18d71968fd 100644 --- a/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.html +++ b/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.html @@ -39,17 +39,17 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/add-widget-dialog.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/add-widget-dialog.component.html index 614c63d980..fb0d17e73a 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/add-widget-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/add-widget-dialog.component.html @@ -41,11 +41,6 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-settings-dialog.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-settings-dialog.component.html index 082ff62633..80d5412226 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-settings-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-settings-dialog.component.html @@ -152,17 +152,17 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/layout/manage-dashboard-layouts-dialog.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/layout/manage-dashboard-layouts-dialog.component.html index 8c539a8cb8..0014fcb0cb 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/layout/manage-dashboard-layouts-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/layout/manage-dashboard-layouts-dialog.component.html @@ -54,11 +54,6 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.html index 0a20f6a48e..3097e0e303 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.html @@ -54,7 +54,6 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/dashboard-state-dialog.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/states/dashboard-state-dialog.component.html index cc27d98d9a..52788ca0e8 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/dashboard-state-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/dashboard-state-dialog.component.html @@ -52,16 +52,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.html index d75b4f41c3..7f4f486278 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.html @@ -136,16 +136,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html index d927e5222c..a803d35b1d 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html @@ -70,7 +70,7 @@ - diff --git a/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html index 0c99fd7040..2936b60911 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html @@ -36,17 +36,17 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.html b/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.html index 9cd30ab768..926c4d7bcc 100644 --- a/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.html @@ -46,16 +46,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.html index e8df333ecd..75ccbb764e 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.html @@ -40,16 +40,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-link-dialog.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-link-dialog.component.html index 3016ab6d70..12de2706f5 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-link-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-link-dialog.component.html @@ -39,16 +39,16 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html index 52ac55275c..a66a3ab995 100644 --- a/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html @@ -41,11 +41,6 @@
- +
diff --git a/ui-ngx/src/app/modules/home/pages/widget/save-widget-type-as-dialog.component.html b/ui-ngx/src/app/modules/home/pages/widget/save-widget-type-as-dialog.component.html index 8af9a379d9..0ae1e24a07 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/save-widget-type-as-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/widget/save-widget-type-as-dialog.component.html @@ -46,17 +46,17 @@
- +
diff --git a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html index 8e1b64381d..4de07b1f80 100644 --- a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html @@ -34,7 +34,6 @@ diff --git a/ui-ngx/src/app/shared/components/dialog/json-object-edit-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/json-object-edit-dialog.component.html index 8dc4f5b1d2..9d5ee6c6ef 100644 --- a/ui-ngx/src/app/shared/components/dialog/json-object-edit-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/json-object-edit-dialog.component.html @@ -41,17 +41,16 @@
- +
diff --git a/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html index 698ecb75ae..365445ed02 100644 --- a/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html @@ -109,18 +109,17 @@ {{ 'rulenode.test' | translate }} - + diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index c462c150c0..398d5dd9d1 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -140,19 +140,18 @@
+ -
diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 9e92defa45..f6850dbc57 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -26,7 +26,7 @@ import { EntityInfoData } from '@shared/models/entity.models'; import { KeyFilter } from '@shared/models/query/query.models'; import { TimeUnit } from '@shared/models/time/time.models'; import * as _moment from 'moment-timezone'; -import { AbstractControl, FormGroup } from '@angular/forms'; +import { AbstractControl, FormGroup, ValidationErrors } from '@angular/forms'; export enum DeviceProfileType { DEFAULT = 'DEFAULT' @@ -87,7 +87,7 @@ export const deviceProvisionTypeTranslationMap = new Map( [ @@ -303,6 +303,18 @@ export interface AlarmRule { schedule?: AlarmSchedule; } +export function alarmRuleValidator(control: AbstractControl): ValidationErrors | null { + const alarmRule: AlarmRule = control.value; + return alarmRuleValid(alarmRule) ? null : {alarmRule: true}; +} + +function alarmRuleValid(alarmRule: AlarmRule): boolean { + if (!alarmRule || !alarmRule.condition || !alarmRule.condition.condition || !alarmRule.condition.condition.length) { + return false; + } + return true; +} + export interface DeviceProfileAlarm { id: string; alarmType: string; @@ -312,6 +324,34 @@ export interface DeviceProfileAlarm { propagateRelationTypes?: Array; } +export function deviceProfileAlarmValidator(control: AbstractControl): ValidationErrors | null { + const deviceProfileAlarm: DeviceProfileAlarm = control.value; + if (deviceProfileAlarm && deviceProfileAlarm.id && deviceProfileAlarm.alarmType && + deviceProfileAlarm.createRules) { + const severities = Object.keys(deviceProfileAlarm.createRules); + if (severities.length) { + let alarmRulesValid = true; + for (const severity of severities) { + const alarmRule = deviceProfileAlarm.createRules[severity]; + if (!alarmRuleValid(alarmRule)) { + alarmRulesValid = false; + break; + } + } + if (alarmRulesValid) { + if (deviceProfileAlarm.clearRule && !alarmRuleValid(deviceProfileAlarm.clearRule)) { + alarmRulesValid = false; + } + } + if (alarmRulesValid) { + return null; + } + } + } + return {deviceProfileAlarm: true}; +} + + export interface DeviceProfileData { configuration: DeviceProfileConfiguration; transportConfiguration: DeviceProfileTransportConfiguration; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index b5b34f8fe5..453d5d2abd 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -908,6 +908,7 @@ "create-alarm-pattern": "Create {{alarmType}} alarm", "create-alarm-rules": "Create alarm rules", "no-create-alarm-rules": "No create conditions configured", + "add-create-alarm-rule-prompt": "Please add create alarm rule", "clear-alarm-rule": "Clear alarm rule", "no-clear-alarm-rule": "No clear condition configured", "add-create-alarm-rule": "Add create condition", diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index ac717e783e..190fbb1ba7 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -331,6 +331,9 @@ pre.tb-highlight { font-weight: 400; line-height: 18px; color: rgba(0, 0, 0, .38); + &.required { + color: rgb(221, 44, 0); + } } .tb-fullscreen { From c5459fe9e534706d05f7b8cb5a3727c2c15bc69d Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Wed, 14 Oct 2020 14:34:56 +0300 Subject: [PATCH 06/23] Entities table: fix data displaying and sorting, when labels with custom translations are used --- .../components/widget/lib/entities-table-widget.component.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts index dae33f3cfe..b9791b5ae9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts @@ -352,7 +352,8 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni } dataKeys.push(dataKey); - dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label); + dataKey.label = this.utils.customTranslation(dataKey.label, dataKey.label); + dataKey.title = dataKey.label; dataKey.def = 'def' + this.columns.length; const keySettings: TableWidgetDataKeySettings = dataKey.settings; if (dataKey.type === DataKeyType.entityField && @@ -374,7 +375,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni } if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) { - this.defaultSortOrder = this.settings.defaultSortOrder; + this.defaultSortOrder = this.utils.customTranslation(this.settings.defaultSortOrder, this.settings.defaultSortOrder); } this.pageLink.sortOrder = entityDataSortOrderFromString(this.defaultSortOrder, this.columns); From 7565afca3a478928f52ad10d4f4774af4a742dc0 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 14 Oct 2020 16:43:07 +0300 Subject: [PATCH 07/23] Several OAuth2 improvements --- .../github_config.json | 1 + .../server/controller/OAuth2Controller.java | 18 ++++ .../oauth2/OAuth2ClientsDomainParams.java | 6 +- .../data/oauth2/OAuth2ClientsParams.java | 6 +- .../server/dao/oauth2/OAuth2Utils.java | 12 +-- .../dao/service/BaseOAuth2ServiceTest.java | 93 ++++++++++--------- ui-ngx/src/app/core/http/oauth2.service.ts | 4 + .../home/pages/admin/admin-routing.module.ts | 31 ++++++- .../pages/admin/oauth2-settings.component.ts | 31 +++++-- 9 files changed, 134 insertions(+), 68 deletions(-) diff --git a/application/src/main/data/json/system/oauth2_config_templates/github_config.json b/application/src/main/data/json/system/oauth2_config_templates/github_config.json index 0a1ae5779b..439043d961 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/github_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/github_config.json @@ -10,6 +10,7 @@ "mapperConfig": { "type": "GITHUB", "basic": { + "firstNameAttributeKey": "name", "tenantNameStrategy": "DOMAIN" } }, diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index 323eb19bcd..fc5e0a1426 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -16,6 +16,7 @@ package org.thingsboard.server.controller; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @@ -23,6 +24,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; import org.thingsboard.server.common.data.oauth2.SchemeType; +import org.thingsboard.server.dao.oauth2.OAuth2Configuration; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -36,6 +38,10 @@ import java.util.List; @RequestMapping("/api") @Slf4j public class OAuth2Controller extends BaseController { + + @Autowired + private OAuth2Configuration oAuth2Configuration; + @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) @ResponseBody public List getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { @@ -70,4 +76,16 @@ public class OAuth2Controller extends BaseController { throw handleException(e); } } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + @RequestMapping(value = "/oauth2/loginProcessingUrl", method = RequestMethod.GET) + @ResponseBody + public String getLoginProcessingUrl() throws ThingsboardException { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ); + return "\"" + oAuth2Configuration.getLoginProcessingUrl() + "\""; + } catch (Exception e) { + throw handleException(e); + } + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java index d93401f5cc..4e932fad85 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java @@ -27,6 +27,6 @@ import java.util.Set; @NoArgsConstructor @AllArgsConstructor public class OAuth2ClientsDomainParams { - private Set domainInfos; - private Set clientRegistrations; -} \ No newline at end of file + private List domainInfos; + private List clientRegistrations; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java index ee20021aca..ad5d5305bd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java @@ -16,6 +16,8 @@ package org.thingsboard.server.common.data.oauth2; import lombok.*; + +import java.util.List; import java.util.Set; @EqualsAndHashCode @@ -26,5 +28,5 @@ import java.util.Set; @AllArgsConstructor public class OAuth2ClientsParams { private boolean enabled; - private Set domainsParams; -} \ No newline at end of file + private List domainsParams; +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java index a96a0d36c8..1ed0332bf8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java @@ -32,19 +32,19 @@ public class OAuth2Utils { } public static OAuth2ClientsParams toOAuth2Params(List extendedOAuth2ClientRegistrationInfos) { - Map> domainsByInfoId = new HashMap<>(); - Map infoById = new HashMap<>(); + Map> domainsByInfoId = new LinkedHashMap<>(); + Map infoById = new LinkedHashMap<>(); for (ExtendedOAuth2ClientRegistrationInfo extendedClientRegistrationInfo : extendedOAuth2ClientRegistrationInfos) { String domainName = extendedClientRegistrationInfo.getDomainName(); SchemeType domainScheme = extendedClientRegistrationInfo.getDomainScheme(); - domainsByInfoId.computeIfAbsent(extendedClientRegistrationInfo.getId(), key -> new HashSet<>()) + domainsByInfoId.computeIfAbsent(extendedClientRegistrationInfo.getId(), key -> new ArrayList<>()) .add(new DomainInfo(domainScheme, domainName)); infoById.put(extendedClientRegistrationInfo.getId(), extendedClientRegistrationInfo); } - Map, OAuth2ClientsDomainParams> domainParamsMap = new HashMap<>(); + Map, OAuth2ClientsDomainParams> domainParamsMap = new HashMap<>(); domainsByInfoId.forEach((clientRegistrationInfoId, domainInfos) -> { domainParamsMap.computeIfAbsent(domainInfos, - key -> new OAuth2ClientsDomainParams(key, new HashSet<>()) + key -> new OAuth2ClientsDomainParams(key, new ArrayList<>()) ) .getClientRegistrations() .add(toClientRegistrationDto(infoById.get(clientRegistrationInfoId))); @@ -52,7 +52,7 @@ public class OAuth2Utils { boolean enabled = extendedOAuth2ClientRegistrationInfos.stream() .map(OAuth2ClientRegistrationInfo::isEnabled) .findFirst().orElse(false); - return new OAuth2ClientsParams(enabled, new HashSet<>(domainParamsMap.values())); + return new OAuth2ClientsParams(enabled, new ArrayList<>(domainParamsMap.values())); } public static ClientRegistrationDto toClientRegistrationDto(OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo) { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java index b54505a3d7..a927f1e78c 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.service; +import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.junit.After; import org.junit.Assert; @@ -29,7 +30,7 @@ import java.util.*; import java.util.stream.Collectors; public class BaseOAuth2ServiceTest extends AbstractServiceTest { - private static final OAuth2ClientsParams EMPTY_PARAMS = new OAuth2ClientsParams(false, new HashSet<>()); + private static final OAuth2ClientsParams EMPTY_PARAMS = new OAuth2ClientsParams(false, new ArrayList<>()); @Autowired protected OAuth2Service oAuth2Service; @@ -48,14 +49,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test(expected = DataValidationException.class) public void testSaveHttpAndMixedDomainsTogether() { - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto(), validClientRegistrationDto() @@ -67,14 +68,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test(expected = DataValidationException.class) public void testSaveHttpsAndMixedDomainsTogether() { - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build(), DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto(), validClientRegistrationDto() @@ -131,20 +132,20 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { Assert.assertNotNull(foundClientsParams); Assert.assertEquals(clientsParams, foundClientsParams); - OAuth2ClientsParams newClientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsParams newClientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("another-domain").scheme(SchemeType.HTTPS).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto() )) .build(), OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("test-domain").scheme(SchemeType.MIXED).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto() )) .build() @@ -157,22 +158,22 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testGetOAuth2Clients() { - Set firstGroup = Sets.newHashSet( + List firstGroup = Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto(), validClientRegistrationDto(), validClientRegistrationDto() ); - Set secondGroup = Sets.newHashSet( + List secondGroup = Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto() ); - Set thirdGroup = Sets.newHashSet( + List thirdGroup = Lists.newArrayList( validClientRegistrationDto() ); - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() @@ -180,14 +181,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { .clientRegistrations(firstGroup) .build(), OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() )) .clientRegistrations(secondGroup) .build(), OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() )) @@ -285,15 +286,15 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testGetOAuth2ClientsForHttpAndHttps() { - Set firstGroup = Sets.newHashSet( + List firstGroup = Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto(), validClientRegistrationDto(), validClientRegistrationDto() ); - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build() @@ -335,25 +336,25 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testGetDisabledOAuth2Clients() { - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto(), validClientRegistrationDto() )) .build(), OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto() )) @@ -374,35 +375,35 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testFindAllClientRegistrationInfos() { - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto(), validClientRegistrationDto() )) .build(), OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto() )) .build(), OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto() )) .build() @@ -423,35 +424,35 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testFindClientRegistrationById() { - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Sets.newHashSet( + OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto(), validClientRegistrationDto() )) .build(), OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto() )) .build(), OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto() )) .build() @@ -466,14 +467,14 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { } private OAuth2ClientsParams createDefaultClientsParams() { - return new OAuth2ClientsParams(true, Sets.newHashSet( + return new OAuth2ClientsParams(true, Lists.newArrayList( OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto(), validClientRegistrationDto(), @@ -481,11 +482,11 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { )) .build(), OAuth2ClientsDomainParams.builder() - .domainInfos(Sets.newHashSet( + .domainInfos(Lists.newArrayList( DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() )) - .clientRegistrations(Sets.newHashSet( + .clientRegistrations(Lists.newArrayList( validClientRegistrationDto(), validClientRegistrationDto() )) diff --git a/ui-ngx/src/app/core/http/oauth2.service.ts b/ui-ngx/src/app/core/http/oauth2.service.ts index a8f890223d..2eff9fa339 100644 --- a/ui-ngx/src/app/core/http/oauth2.service.ts +++ b/ui-ngx/src/app/core/http/oauth2.service.ts @@ -41,4 +41,8 @@ export class OAuth2Service { return this.http.post('/api/oauth2/config', OAuth2Setting, defaultHttpOptionsFromConfig(config)); } + + public getLoginProcessingUrl(config?: RequestConfig): Observable { + return this.http.get(`/api/oauth2/loginProcessingUrl`, defaultHttpOptionsFromConfig(config)); + } } diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts index af7dc98e19..7fefbfb521 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts @@ -14,8 +14,8 @@ /// limitations under the License. /// -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; +import { Injectable, NgModule } from '@angular/core'; +import { Resolve, RouterModule, Routes } from '@angular/router'; import { MailServerComponent } from '@modules/home/pages/admin/mail-server.component'; import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; @@ -23,6 +23,25 @@ import { Authority } from '@shared/models/authority.enum'; import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component'; import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component'; import { OAuth2SettingsComponent } from '@home/pages/admin/oauth2-settings.component'; +import { User } from '@shared/models/user.model'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { UserService } from '@core/http/user.service'; +import { Observable } from 'rxjs'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { OAuth2Service } from '@core/http/oauth2.service'; +import { UserProfileResolver } from '@home/pages/profile/profile-routing.module'; + +@Injectable() +export class OAuth2LoginProcessingUrlResolver implements Resolve { + + constructor(private oauth2Service: OAuth2Service) { + } + + resolve(): Observable { + return this.oauth2Service.getLoginProcessingUrl(); + } +} const routes: Routes = [ { @@ -90,6 +109,9 @@ const routes: Routes = [ label: 'admin.oauth2.oauth2', icon: 'security' } + }, + resolve: { + loginProcessingUrl: OAuth2LoginProcessingUrlResolver } } ] @@ -98,6 +120,9 @@ const routes: Routes = [ @NgModule({ imports: [RouterModule.forChild(routes)], - exports: [RouterModule] + exports: [RouterModule], + providers: [ + OAuth2LoginProcessingUrlResolver + ] }) export class AdminRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index 58cbe536d7..37ae0433d3 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -43,6 +43,7 @@ import { DialogService } from '@core/services/dialog.service'; import { TranslateService } from '@ngx-translate/core'; import { isDefined, isDefinedAndNotNull } from '@core/utils'; import { OAuth2Service } from '@core/http/oauth2.service'; +import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'tb-oauth2-settings', @@ -87,7 +88,10 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha templateProvider = ['Custom']; + private loginProcessingUrl: string = this.route.snapshot.data.loginProcessingUrl; + constructor(protected store: Store, + private route: ActivatedRoute, private oauth2Service: OAuth2Service, private fb: FormBuilder, private dialogService: DialogService, @@ -130,7 +134,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha return this.oauth2SettingsForm.get('domainsParams') as FormArray; } - private formBasicGroup(mapperConfigBasic?: MapperConfigBasic): FormGroup { + private formBasicGroup(type: MapperConfigType, mapperConfigBasic?: MapperConfigBasic): FormGroup { let tenantNamePattern; if (mapperConfigBasic?.tenantNamePattern) { tenantNamePattern = mapperConfigBasic.tenantNamePattern; @@ -138,16 +142,20 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha tenantNamePattern = {value: null, disabled: true}; } const basicGroup = this.fb.group({ - emailAttributeKey: [mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required], firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : ''], lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : ''], tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : TenantNameStrategy.DOMAIN], tenantNamePattern: [tenantNamePattern, Validators.required], customerNamePattern: [mapperConfigBasic?.customerNamePattern ? mapperConfigBasic.customerNamePattern : null], defaultDashboardName: [mapperConfigBasic?.defaultDashboardName ? mapperConfigBasic.defaultDashboardName : null], - alwaysFullScreen: [mapperConfigBasic?.alwaysFullScreen ? mapperConfigBasic.alwaysFullScreen : false] + alwaysFullScreen: [isDefinedAndNotNull(mapperConfigBasic?.alwaysFullScreen) ? mapperConfigBasic.alwaysFullScreen : false] }); + if (MapperConfigType.GITHUB !== type) { + basicGroup.addControl('emailAttributeKey', + this.fb.control( mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required)); + } + this.subscriptions.push(basicGroup.get('tenantNameStrategy').valueChanges.subscribe((domain) => { if (domain === 'CUSTOM') { basicGroup.get('tenantNamePattern').enable(); @@ -279,9 +287,12 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha clientRegistration?.userNameAttributeName ? clientRegistration.userNameAttributeName : 'email', Validators.required], mapperConfig: this.fb.group({ allowUserCreation: [ - clientRegistration?.mapperConfig?.allowUserCreation ? clientRegistration.mapperConfig.allowUserCreation : true + isDefinedAndNotNull(clientRegistration?.mapperConfig?.allowUserCreation) ? + clientRegistration.mapperConfig.allowUserCreation : true + ], + activateUser: [ + isDefinedAndNotNull(clientRegistration?.mapperConfig?.activateUser) ? clientRegistration.mapperConfig.activateUser : false ], - activateUser: [clientRegistration?.mapperConfig?.activateUser ? clientRegistration.mapperConfig.activateUser : false], type: [ clientRegistration?.mapperConfig?.type ? clientRegistration.mapperConfig.type : MapperConfigType.BASIC, Validators.required ] @@ -308,7 +319,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha return clientRegistrationFormGroup; } - private validateScope (control: AbstractControl): ValidationErrors | null { + private validateScope(control: AbstractControl): ValidationErrors | null { const scope: string[] = control.value; if (!scope || !scope.length) { return { @@ -347,7 +358,11 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom)); } else { mapperConfig.removeControl('custom'); - mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic)); + if (mapperConfig.get('basic')) { + mapperConfig.setControl('basic', this.formBasicGroup(type, predefinedValue?.basic)); + } else { + mapperConfig.addControl('basic', this.formBasicGroup(type, predefinedValue?.basic)); + } } } @@ -490,7 +505,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } else { protocol = domainInfo.scheme === DomainSchema.MIXED ? DomainSchema.HTTPS.toLowerCase() : domainInfo.scheme.toLowerCase(); } - return `${protocol}://${domainInfo.name}/login/oauth2/code/`; + return `${protocol}://${domainInfo.name}${this.loginProcessingUrl}`; } return ''; } From 9a7d5a437e2cc165ed7fe6976a92d7a268779ea0 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 14 Oct 2020 18:22:03 +0300 Subject: [PATCH 08/23] Fix OAuth2 domain params order --- .../java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java index 1ed0332bf8..00b4f7e941 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java @@ -41,7 +41,7 @@ public class OAuth2Utils { .add(new DomainInfo(domainScheme, domainName)); infoById.put(extendedClientRegistrationInfo.getId(), extendedClientRegistrationInfo); } - Map, OAuth2ClientsDomainParams> domainParamsMap = new HashMap<>(); + Map, OAuth2ClientsDomainParams> domainParamsMap = new LinkedHashMap<>(); domainsByInfoId.forEach((clientRegistrationInfoId, domainInfos) -> { domainParamsMap.computeIfAbsent(domainInfos, key -> new OAuth2ClientsDomainParams(key, new ArrayList<>()) From 965870a980ec613076e0ec1aa311d5bff85f1c40 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 15 Oct 2020 12:16:14 +0300 Subject: [PATCH 09/23] UI: Fixed clear content for ace editor and show toast error --- .../shared/components/js-func.component.ts | 10 ++-- .../components/json-content.component.ts | 49 +++++++++++-------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/ui-ngx/src/app/shared/components/js-func.component.ts b/ui-ngx/src/app/shared/components/js-func.component.ts index 72e30462e5..5515ba055f 100644 --- a/ui-ngx/src/app/shared/components/js-func.component.ts +++ b/ui-ngx/src/app/shared/components/js-func.component.ts @@ -36,7 +36,6 @@ import { TranslateService } from '@ngx-translate/core'; import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; import { ResizeObserver } from '@juggle/resize-observer'; import { TbEditorCompleter } from '@shared/models/ace/completion.models'; -import { widgetEditorCompleter } from '@home/pages/widget/widget-editor.models'; @Component({ selector: 'tb-js-func', @@ -64,6 +63,7 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, private jsEditor: ace.Ace.Editor; private editorsResizeCaf: CancelAnimationFrame; private editorResize$: ResizeObserver; + private ignoreChange = false; toastTargetId = `jsFuncEditor-${guid()}`; @@ -154,8 +154,10 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, this.jsEditor.session.setUseWrapMode(true); this.jsEditor.setValue(this.modelValue ? this.modelValue : '', -1); this.jsEditor.on('change', () => { - this.cleanupJsErrors(); - this.updateView(); + if (!this.ignoreChange) { + this.cleanupJsErrors(); + this.updateView(); + } }); if (this.editorCompleter) { this.jsEditor.completers = [this.editorCompleter, ...(this.jsEditor.completers || [])]; @@ -332,7 +334,9 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, writeValue(value: string): void { this.modelValue = value; if (this.jsEditor) { + this.ignoreChange = true; this.jsEditor.setValue(this.modelValue ? this.modelValue : '', -1); + this.ignoreChange = false; } } diff --git a/ui-ngx/src/app/shared/components/json-content.component.ts b/ui-ngx/src/app/shared/components/json-content.component.ts index 242b4b6359..3339863cd2 100644 --- a/ui-ngx/src/app/shared/components/json-content.component.ts +++ b/ui-ngx/src/app/shared/components/json-content.component.ts @@ -61,6 +61,7 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid private jsonEditor: ace.Ace.Editor; private editorsResizeCaf: CancelAnimationFrame; private editorResize$: ResizeObserver; + private ignoreChange = false; toastTargetId = `jsonContentEditor-${guid()}`; @@ -140,8 +141,13 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid this.jsonEditor.session.setUseWrapMode(true); this.jsonEditor.setValue(this.contentBody ? this.contentBody : '', -1); this.jsonEditor.on('change', () => { - this.cleanupJsonErrors(); - this.updateView(); + if (!this.ignoreChange) { + this.cleanupJsonErrors(); + this.updateView(); + } + }); + this.jsonEditor.on('blur', () => { + this.contentValid = !this.validateContent || this.doValidate(true); }); this.editorResize$ = new ResizeObserver(() => { this.onAceEditorResize(); @@ -210,34 +216,36 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid this.cleanupJsonErrors(); this.contentValid = true; this.propagateChange(this.contentBody); - this.contentValid = this.doValidate(); + this.contentValid = this.doValidate(true); this.propagateChange(this.contentBody); } } - private doValidate(): boolean { + private doValidate(showErrorToast = false): boolean { try { if (this.validateContent && this.contentType === ContentType.JSON) { JSON.parse(this.contentBody); } return true; } catch (ex) { - let errorInfo = 'Error:'; - if (ex.name) { - errorInfo += ' ' + ex.name + ':'; - } - if (ex.message) { - errorInfo += ' ' + ex.message; + if (showErrorToast) { + let errorInfo = 'Error:'; + if (ex.name) { + errorInfo += ' ' + ex.name + ':'; + } + if (ex.message) { + errorInfo += ' ' + ex.message; + } + this.store.dispatch(new ActionNotificationShow( + { + message: errorInfo, + type: 'error', + target: this.toastTargetId, + verticalPosition: 'bottom', + horizontalPosition: 'left' + })); + this.errorShowed = true; } - this.store.dispatch(new ActionNotificationShow( - { - message: errorInfo, - type: 'error', - target: this.toastTargetId, - verticalPosition: 'bottom', - horizontalPosition: 'left' - })); - this.errorShowed = true; return false; } } @@ -256,8 +264,9 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid this.contentBody = value; this.contentValid = true; if (this.jsonEditor) { + this.ignoreChange = true; this.jsonEditor.setValue(this.contentBody ? this.contentBody : '', -1); - // this.jsonEditor. + this.ignoreChange = false; } } From a29aa64497400535c22de442ba933c1e6a5c7948 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 15 Oct 2020 12:51:02 +0300 Subject: [PATCH 10/23] Add OAuth2 settings menu to SysAdmin home page --- ui-ngx/src/app/core/services/menu.service.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index e44c3c6ec2..4e98f375c2 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -191,6 +191,11 @@ export class MenuService { name: 'admin.security-settings', icon: 'security', path: '/settings/security-settings' + }, + { + name: 'admin.oauth2.oauth2', + icon: 'security', + path: '/settings/oauth2' } ] } From 02932ce2531a670a2c41757164674c0e55cb3363 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 15 Oct 2020 15:23:39 +0300 Subject: [PATCH 11/23] UI: Fixed null id in entity-select component for aliasType CURRENT_TENANT, CURRENT_USER, CURRENT_USER_OWNER --- .../entity/entity-select.component.ts | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts index fb1c23e93a..0fc3d95086 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts @@ -97,10 +97,6 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte ngOnInit() { this.entitySelectFormGroup.get('entityType').valueChanges.subscribe( (value) => { - if(value === AliasEntityType.CURRENT_TENANT || value === AliasEntityType.CURRENT_USER || - value === AliasEntityType.CURRENT_USER_OWNER) { - this.modelValue.id = NULL_UUID; - } this.updateView(value, this.modelValue.id); } ); @@ -140,20 +136,23 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte } updateView(entityType: EntityType | AliasEntityType | null, entityId: string | null) { - if (this.modelValue.entityType !== entityType || - this.modelValue.id !== entityId) { - this.modelValue = { - entityType, - id: this.modelValue.entityType !== entityType ? null : entityId - }; - if (this.modelValue.entityType && (this.modelValue.id || - this.modelValue.entityType === AliasEntityType.CURRENT_TENANT || - this.modelValue.entityType === AliasEntityType.CURRENT_USER || - this.modelValue.entityType === AliasEntityType.CURRENT_USER_OWNER)) { - this.propagateChange(this.modelValue); - } else { - this.propagateChange(null); - } + if (this.modelValue.entityType !== entityType || this.modelValue.id !== entityId) { + this.modelValue = { + entityType, + id: this.modelValue.entityType !== entityType ? null : entityId + }; + + if (this.modelValue.entityType === AliasEntityType.CURRENT_TENANT + || this.modelValue.entityType === AliasEntityType.CURRENT_USER + || this.modelValue.entityType === AliasEntityType.CURRENT_USER_OWNER) { + this.modelValue.id = NULL_UUID; + } + + if (this.modelValue.entityType && this.modelValue.id) { + this.propagateChange(this.modelValue); + } else { + this.propagateChange(null); + } } } } From 6ea39e835e3203bfb4bcc6d6e6f7cf7e4552d16d Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 15 Oct 2020 18:16:27 +0300 Subject: [PATCH 12/23] Transport Rate Limits are now configurable via Tenant Profile --- .../device_profile/rule_chain_template.json | 3 +- .../server/controller/TenantController.java | 4 +- .../controller/TenantProfileController.java | 5 +- .../profile/DefaultTbDeviceProfileCache.java | 16 +- .../service/profile/TbDeviceProfileCache.java | 3 + .../queue/DefaultTbClusterService.java | 56 ++++-- .../service/queue/TbClusterService.java | 13 +- .../transport/DefaultTransportApiService.java | 63 +++---- .../src/main/resources/thingsboard.yml | 6 +- common/queue/src/main/proto/queue.proto | 47 +++-- ....java => TransportDeviceProfileCache.java} | 2 +- .../common/transport/TransportService.java | 9 +- .../TransportTenantProfileCache.java | 38 +++++ .../DefaultTransportRateLimitFactory.java | 47 +++++ .../DefaultTransportRateLimitService.java | 115 +++++++++++++ .../limits/DummyTransportRateLimit.java | 30 ++++ .../limits/SimpleTransportRateLimit.java | 34 ++++ .../transport/limits/TransportRateLimit.java | 24 +++ .../limits/TransportRateLimitFactory.java | 24 +++ .../limits/TransportRateLimitService.java | 36 ++++ .../limits/TransportRateLimitType.java | 33 ++++ .../profile/TenantProfileUpdateResult.java | 30 ++++ ...> DefaultTransportDeviceProfileCache.java} | 6 +- .../service/DefaultTransportService.java | 160 ++++++++---------- .../DefaultTransportTenantProfileCache.java | 154 +++++++++++++++++ .../TransportTenantRoutingInfoService.java | 24 +-- .../util/DataDecodingEncodingService.java | 2 - .../src/main/resources/tb-coap-transport.yml | 4 - .../src/main/resources/tb-http-transport.yml | 4 - .../src/main/resources/tb-mqtt-transport.yml | 4 - 30 files changed, 785 insertions(+), 211 deletions(-) rename common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/{TransportProfileCache.java => TransportDeviceProfileCache.java} (95%) create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportTenantProfileCache.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitFactory.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DummyTransportRateLimit.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimit.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitFactory.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/profile/TenantProfileUpdateResult.java rename common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/{DefaultTransportProfileCache.java => DefaultTransportDeviceProfileCache.java} (91%) create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java diff --git a/application/src/main/data/json/tenant/device_profile/rule_chain_template.json b/application/src/main/data/json/tenant/device_profile/rule_chain_template.json index 3d076ff812..da9a95b423 100644 --- a/application/src/main/data/json/tenant/device_profile/rule_chain_template.json +++ b/application/src/main/data/json/tenant/device_profile/rule_chain_template.json @@ -94,7 +94,8 @@ "name": "Device Profile Node", "debugMode": false, "configuration": { - "persistAlarmRulesState": false + "persistAlarmRulesState": false, + "fetchAlarmRulesStateOnStart": false } } ], diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantController.java b/application/src/main/java/org/thingsboard/server/controller/TenantController.java index 7c23545374..83e3d98b1b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantController.java @@ -92,6 +92,7 @@ public class TenantController extends BaseController { installScripts.createDefaultRuleChains(tenant.getId()); } tenantProfileCache.evict(tenant.getId()); + tbClusterService.onTenantChange(tenant, null); return tenant; } catch (Exception e) { throw handleException(e); @@ -105,9 +106,10 @@ public class TenantController extends BaseController { checkParameter("tenantId", strTenantId); try { TenantId tenantId = new TenantId(toUUID(strTenantId)); - checkTenantId(tenantId, Operation.DELETE); + Tenant tenant = checkTenantId(tenantId, Operation.DELETE); tenantService.deleteTenant(tenantId); tenantProfileCache.evict(tenantId); + tbClusterService.onTenantDelete(tenant, null); tbClusterService.onEntityStateChange(tenantId, tenantId, ComponentLifecycleEvent.DELETED); } catch (Exception e) { throw handleException(e); diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java index 4c9d541ea6..0941fac725 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -96,6 +97,7 @@ public class TenantProfileController extends BaseController { tenantProfile = checkNotNull(tenantProfileService.saveTenantProfile(getTenantId(), tenantProfile)); tenantProfileCache.put(tenantProfile); + tbClusterService.onTenantProfileChange(tenantProfile, null); tbClusterService.onEntityStateChange(TenantId.SYS_TENANT_ID, tenantProfile.getId(), newTenantProfile ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); return tenantProfile; @@ -111,8 +113,9 @@ public class TenantProfileController extends BaseController { checkParameter("tenantProfileId", strTenantProfileId); try { TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId)); - checkTenantProfileId(tenantProfileId, Operation.DELETE); + TenantProfile profile = checkTenantProfileId(tenantProfileId, Operation.DELETE); tenantProfileService.deleteTenantProfile(getTenantId(), tenantProfileId); + tbClusterService.onTenantProfileDelete(profile, null); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java b/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java index 2d0861f636..6784405adb 100644 --- a/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java +++ b/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java @@ -61,7 +61,7 @@ public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache { profile = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId); if (profile != null) { deviceProfilesMap.put(deviceProfileId, profile); - log.info("[{}] Fetch device profile into cache: {}", profile.getId(), profile); + log.debug("[{}] Fetch device profile into cache: {}", profile.getId(), profile); } } } finally { @@ -91,7 +91,7 @@ public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache { public void put(DeviceProfile profile) { if (profile.getId() != null) { deviceProfilesMap.put(profile.getId(), profile); - log.info("[{}] pushed device profile to cache: {}", profile.getId(), profile); + log.debug("[{}] pushed device profile to cache: {}", profile.getId(), profile); notifyListeners(profile); } } @@ -99,7 +99,7 @@ public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache { @Override public void evict(TenantId tenantId, DeviceProfileId profileId) { DeviceProfile oldProfile = deviceProfilesMap.remove(profileId); - log.info("[{}] evict device profile from cache: {}", profileId, oldProfile); + log.debug("[{}] evict device profile from cache: {}", profileId, oldProfile); DeviceProfile newProfile = get(tenantId, profileId); if (newProfile != null) { notifyListeners(newProfile); @@ -116,6 +116,16 @@ public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache { listeners.computeIfAbsent(tenantId, id -> new ConcurrentHashMap<>()).put(listenerId, listener); } + @Override + public DeviceProfile find(DeviceProfileId deviceProfileId) { + return deviceProfileService.findDeviceProfileById(TenantId.SYS_TENANT_ID, deviceProfileId); + } + + @Override + public DeviceProfile findOrCreateDeviceProfile(TenantId tenantId, String profileName) { + return deviceProfileService.findOrCreateDeviceProfile(tenantId, profileName); + } + @Override public void removeListener(TenantId tenantId, EntityId listenerId) { ConcurrentMap> tenantListeners = listeners.get(tenantId); diff --git a/application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java b/application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java index c067ead6d2..e65f297d53 100644 --- a/application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java +++ b/application/src/main/java/org/thingsboard/server/service/profile/TbDeviceProfileCache.java @@ -29,4 +29,7 @@ public interface TbDeviceProfileCache extends RuleEngineDeviceProfileCache { void evict(DeviceId id); + DeviceProfile find(DeviceProfileId deviceProfileId); + + DeviceProfile findOrCreateDeviceProfile(TenantId tenantId, String deviceType); } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index a4de2d9737..9451b58e9f 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -23,11 +23,15 @@ import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; @@ -189,21 +193,51 @@ public class DefaultTbClusterService implements TbClusterService { @Override public void onDeviceProfileChange(DeviceProfile deviceProfile, TbQueueCallback callback) { - log.trace("[{}][{}] Processing device profile [{}] change event", deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile.getName()); - TransportProtos.DeviceProfileUpdateMsg profileUpdateMsg = TransportProtos.DeviceProfileUpdateMsg.newBuilder() - .setData(ByteString.copyFrom(encodingService.encode(deviceProfile))).build(); - ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setDeviceProfileUpdateMsg(profileUpdateMsg).build(); - broadcast(transportMsg); + onEntityChange(deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile, callback); + } + + @Override + public void onTenantProfileChange(TenantProfile tenantProfile, TbQueueCallback callback) { + onEntityChange(TenantId.SYS_TENANT_ID, tenantProfile.getId(), tenantProfile, callback); + } + + @Override + public void onTenantChange(Tenant tenant, TbQueueCallback callback) { + onEntityChange(TenantId.SYS_TENANT_ID, tenant.getId(), tenant, callback); + } + + @Override + public void onDeviceProfileDelete(DeviceProfile entity, TbQueueCallback callback) { + onEntityDelete(entity.getTenantId(), entity.getId(), entity.getName(), callback); } @Override - public void onDeviceProfileDelete(DeviceProfile deviceProfile, TbQueueCallback callback) { - log.trace("[{}][{}] Processing device profile [{}] delete event", deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile.getName()); - TransportProtos.DeviceProfileDeleteMsg profileDeleteMsg = TransportProtos.DeviceProfileDeleteMsg.newBuilder() - .setProfileIdMSB(deviceProfile.getId().getId().getMostSignificantBits()) - .setProfileIdLSB(deviceProfile.getId().getId().getLeastSignificantBits()) + public void onTenantProfileDelete(TenantProfile entity, TbQueueCallback callback) { + onEntityDelete(TenantId.SYS_TENANT_ID, entity.getId(), entity.getName(), callback); + } + + @Override + public void onTenantDelete(Tenant entity, TbQueueCallback callback) { + onEntityDelete(TenantId.SYS_TENANT_ID, entity.getId(), entity.getName(), callback); + } + + public void onEntityChange(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) { + log.trace("[{}][{}][{}] Processing [{}] change event", tenantId, entityid.getEntityType(), entityid.getId(), entity.getName()); + TransportProtos.EntityUpdateMsg entityUpdateMsg = TransportProtos.EntityUpdateMsg.newBuilder() + .setEntityType(entityid.getEntityType().name()) + .setData(ByteString.copyFrom(encodingService.encode(entity))).build(); + ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setEntityUpdateMsg(entityUpdateMsg).build(); + broadcast(transportMsg); + } + + private void onEntityDelete(TenantId tenantId, EntityId entityId, String name, TbQueueCallback callback) { + log.trace("[{}][{}][{}] Processing [{}] delete event", tenantId, entityId.getEntityType(), entityId.getId(), name); + TransportProtos.EntityDeleteMsg entityDeleteMsg = TransportProtos.EntityDeleteMsg.newBuilder() + .setEntityType(entityId.getEntityType().name()) + .setEntityIdMSB(entityId.getId().getMostSignificantBits()) + .setEntityIdLSB(entityId.getId().getLeastSignificantBits()) .build(); - ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setDeviceProfileDeleteMsg(profileDeleteMsg).build(); + ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setEntityDeleteMsg(entityDeleteMsg).build(); broadcast(transportMsg); } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java index cf212a06f5..838dbe0eef 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java @@ -17,7 +17,8 @@ package org.thingsboard.server.service.queue; import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; @@ -53,5 +54,13 @@ public interface TbClusterService { void onDeviceProfileChange(DeviceProfile deviceProfile, TbQueueCallback callback); - void onDeviceProfileDelete(DeviceProfile deviceProfileId, TbQueueCallback callback); + void onDeviceProfileDelete(DeviceProfile deviceProfile, TbQueueCallback callback); + + void onTenantProfileChange(TenantProfile tenantProfile, TbQueueCallback callback); + + void onTenantProfileDelete(TenantProfile tenantProfile, TbQueueCallback callback); + + void onTenantChange(Tenant tenant, TbQueueCallback callback); + + void onTenantDelete(Tenant tenant, TbQueueCallback callback); } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index c806757ddb..10360769a7 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -28,6 +28,7 @@ import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData; @@ -45,21 +46,19 @@ import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.dao.device.DeviceCredentialsService; -import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceProvisionService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; import org.thingsboard.server.dao.relation.RelationService; -import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; @@ -70,6 +69,7 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.service.executors.DbCallbackExecutorService; +import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.state.DeviceStateService; @@ -90,9 +90,7 @@ public class DefaultTransportApiService implements TransportApiService { private static final ObjectMapper mapper = new ObjectMapper(); - //TODO: Constructor dependencies; - private final DeviceProfileService deviceProfileService; - private final TenantService tenantService; + private final TbDeviceProfileCache deviceProfileCache; private final TbTenantProfileCache tenantProfileCache; private final DeviceService deviceService; private final RelationService relationService; @@ -103,17 +101,15 @@ public class DefaultTransportApiService implements TransportApiService { private final DataDecodingEncodingService dataDecodingEncodingService; private final DeviceProvisionService deviceProvisionService; - private final ConcurrentMap deviceCreationLocks = new ConcurrentHashMap<>(); - public DefaultTransportApiService(DeviceProfileService deviceProfileService, TenantService tenantService, + public DefaultTransportApiService(TbDeviceProfileCache deviceProfileCache, TbTenantProfileCache tenantProfileCache, DeviceService deviceService, RelationService relationService, DeviceCredentialsService deviceCredentialsService, DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService, TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService, DeviceProvisionService deviceProvisionService) { - this.deviceProfileService = deviceProfileService; - this.tenantService = tenantService; + this.deviceProfileCache = deviceProfileCache; this.tenantProfileCache = tenantProfileCache; this.deviceService = deviceService; this.relationService = relationService; @@ -143,11 +139,8 @@ public class DefaultTransportApiService implements TransportApiService { } else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) { return Futures.transform(handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()), value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); - } else if (transportApiRequestMsg.hasGetTenantRoutingInfoRequestMsg()) { - return Futures.transform(handle(transportApiRequestMsg.getGetTenantRoutingInfoRequestMsg()), - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); - } else if (transportApiRequestMsg.hasGetDeviceProfileRequestMsg()) { - return Futures.transform(handle(transportApiRequestMsg.getGetDeviceProfileRequestMsg()), + } else if (transportApiRequestMsg.hasEntityProfileRequestMsg()) { + return Futures.transform(handle(transportApiRequestMsg.getEntityProfileRequestMsg()), value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); } else if (transportApiRequestMsg.hasProvisionDeviceRequestMsg()) { return Futures.transform(handle(transportApiRequestMsg.getProvisionDeviceRequestMsg()), @@ -238,7 +231,7 @@ public class DefaultTransportApiService implements TransportApiService { device.setName(requestMsg.getDeviceName()); device.setType(requestMsg.getDeviceType()); device.setCustomerId(gateway.getCustomerId()); - DeviceProfile deviceProfile = deviceProfileService.findOrCreateDeviceProfile(gateway.getTenantId(), requestMsg.getDeviceType()); + DeviceProfile deviceProfile = deviceProfileCache.findOrCreateDeviceProfile(gateway.getTenantId(), requestMsg.getDeviceType()); device.setDeviceProfileId(deviceProfile.getId()); device = deviceService.saveDevice(device); relationService.saveRelationAsync(TenantId.SYS_TENANT_ID, new EntityRelation(gateway.getId(), device.getId(), "Created")); @@ -258,7 +251,7 @@ public class DefaultTransportApiService implements TransportApiService { } GetOrCreateDeviceFromGatewayResponseMsg.Builder builder = GetOrCreateDeviceFromGatewayResponseMsg.newBuilder() .setDeviceInfo(getDeviceInfoProto(device)); - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); + DeviceProfile deviceProfile = deviceProfileCache.get(device.getTenantId(), device.getDeviceProfileId()); if (deviceProfile != null) { builder.setProfileBody(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile))); } else { @@ -320,23 +313,21 @@ public class DefaultTransportApiService implements TransportApiService { .build(); } - private ListenableFuture handle(GetTenantRoutingInfoRequestMsg requestMsg) { - TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB())); - - ListenableFuture tenantProfileFuture = Futures.immediateFuture(tenantProfileCache.get(tenantId)); - return Futures.transform(tenantProfileFuture, tenantProfile -> TransportApiResponseMsg.newBuilder() - .setGetTenantRoutingInfoResponseMsg(GetTenantRoutingInfoResponseMsg.newBuilder().setIsolatedTbCore(tenantProfile.isIsolatedTbCore()) - .setIsolatedTbRuleEngine(tenantProfile.isIsolatedTbRuleEngine()).build()).build(), dbCallbackExecutorService); - } - - private ListenableFuture handle(TransportProtos.GetDeviceProfileRequestMsg requestMsg) { - DeviceProfileId profileId = new DeviceProfileId(new UUID(requestMsg.getProfileIdMSB(), requestMsg.getProfileIdLSB())); - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(TenantId.SYS_TENANT_ID, profileId); - return Futures.immediateFuture(TransportApiResponseMsg.newBuilder() - .setGetDeviceProfileResponseMsg( - TransportProtos.GetDeviceProfileResponseMsg.newBuilder() - .setData(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile))) - .build()).build()); + private ListenableFuture handle(GetEntityProfileRequestMsg requestMsg) { + EntityType entityType = EntityType.valueOf(requestMsg.getEntityType()); + UUID entityUuid = new UUID(requestMsg.getEntityIdMSB(), requestMsg.getEntityIdLSB()); + ByteString data; + if (entityType.equals(EntityType.DEVICE_PROFILE)) { + DeviceProfileId deviceProfileId = new DeviceProfileId(entityUuid); + DeviceProfile deviceProfile = deviceProfileCache.find(deviceProfileId); + data = ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile)); + } else if (entityType.equals(EntityType.TENANT)) { + TenantProfile tenantProfile = tenantProfileCache.get(new TenantId(entityUuid)); + data = ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile)); + } else { + throw new RuntimeException("Invalid entity profile request: " + entityType); + } + return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setEntityProfileResponseMsg(GetEntityProfileResponseMsg.newBuilder().setData(data).build()).build()); } private ListenableFuture getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) { @@ -348,7 +339,7 @@ public class DefaultTransportApiService implements TransportApiService { try { ValidateDeviceCredentialsResponseMsg.Builder builder = ValidateDeviceCredentialsResponseMsg.newBuilder(); builder.setDeviceInfo(getDeviceInfoProto(device)); - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); + DeviceProfile deviceProfile = deviceProfileCache.get(device.getTenantId(), device.getDeviceProfileId()); if (deviceProfile != null) { builder.setProfileBody(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile))); } else { diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index d0ddeb611a..deb28ffd99 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -502,7 +502,7 @@ js: remote: # Maximum allowed JavaScript execution errors before JavaScript will be blacklisted max_errors: "${REMOTE_JS_SANDBOX_MAX_ERRORS:3}" - # Maximum time in seconds for black listed function to stay in the list. + # Maximum time in seconds for black listed function to stay in 1:the list. max_black_list_duration_sec: "${REMOTE_JS_SANDBOX_MAX_BLACKLIST_DURATION_SEC:60}" stats: enabled: "${TB_JS_REMOTE_STATS_ENABLED:false}" @@ -512,10 +512,6 @@ transport: sessions: inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" - rate_limits: - enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}" - tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}" - device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}" json: # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 166f94b864..01d8ca4524 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -177,32 +177,26 @@ message GetOrCreateDeviceFromGatewayResponseMsg { bytes profileBody = 2; } -message GetTenantRoutingInfoRequestMsg { - int64 tenantIdMSB = 1; - int64 tenantIdLSB = 2; -} - -message GetTenantRoutingInfoResponseMsg { - bool isolatedTbCore = 1; - bool isolatedTbRuleEngine = 2; -} - -message GetDeviceProfileRequestMsg { - int64 profileIdMSB = 1; - int64 profileIdLSB = 2; +message GetEntityProfileRequestMsg { + string entityType = 1; + int64 entityIdMSB = 2; + int64 entityIdLSB = 3; } -message GetDeviceProfileResponseMsg { - bytes data = 1; +message GetEntityProfileResponseMsg { + string entityType = 1; + bytes data = 2; } -message DeviceProfileUpdateMsg { - bytes data = 1; +message EntityUpdateMsg { + string entityType = 1; + bytes data = 2; } -message DeviceProfileDeleteMsg { - int64 profileIdMSB = 1; - int64 profileIdLSB = 2; +message EntityDeleteMsg { + string entityType = 1; + int64 entityIdMSB = 2; + int64 entityIdLSB = 3; } message SessionCloseNotificationProto { @@ -482,8 +476,7 @@ message TransportApiRequestMsg { ValidateDeviceTokenRequestMsg validateTokenRequestMsg = 1; ValidateDeviceX509CertRequestMsg validateX509CertRequestMsg = 2; GetOrCreateDeviceFromGatewayRequestMsg getOrCreateDeviceRequestMsg = 3; - GetTenantRoutingInfoRequestMsg getTenantRoutingInfoRequestMsg = 4; - GetDeviceProfileRequestMsg getDeviceProfileRequestMsg = 5; + GetEntityProfileRequestMsg entityProfileRequestMsg = 4; ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6; ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7; } @@ -492,9 +485,8 @@ message TransportApiRequestMsg { message TransportApiResponseMsg { ValidateDeviceCredentialsResponseMsg validateCredResponseMsg = 1; GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2; - GetTenantRoutingInfoResponseMsg getTenantRoutingInfoResponseMsg = 4; - GetDeviceProfileResponseMsg getDeviceProfileResponseMsg = 5; - ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 6; + GetEntityProfileResponseMsg entityProfileResponseMsg = 3; + ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4; } /* Messages that are handled by ThingsBoard Core Service */ @@ -535,7 +527,8 @@ message ToTransportMsg { AttributeUpdateNotificationMsg attributeUpdateNotification = 5; ToDeviceRpcRequestMsg toDeviceRequest = 6; ToServerRpcResponseMsg toServerResponse = 7; - DeviceProfileUpdateMsg deviceProfileUpdateMsg = 8; - DeviceProfileDeleteMsg deviceProfileDeleteMsg = 9; + /* For Tenant, TenantProfile and DeviceProfile */ + EntityUpdateMsg entityUpdateMsg = 8; + EntityDeleteMsg entityDeleteMsg = 9; ProvisionDeviceResponseMsg provisionResponse = 10; } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportDeviceProfileCache.java similarity index 95% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportProfileCache.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportDeviceProfileCache.java index ee05e59010..d56c0291b7 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportProfileCache.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportDeviceProfileCache.java @@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; import java.util.Optional; -public interface TransportProfileCache { +public interface TransportDeviceProfileCache { DeviceProfile getOrCreate(DeviceProfileId id, ByteString profileBody); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 775a91f720..9a2fd75d47 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -20,11 +20,12 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; @@ -47,7 +48,7 @@ import java.util.concurrent.ScheduledExecutorService; */ public interface TransportService { - GetTenantRoutingInfoResponseMsg getRoutingInfo(GetTenantRoutingInfoRequestMsg msg); + GetEntityProfileResponseMsg getRoutingInfo(GetEntityProfileRequestMsg msg); void process(DeviceTransportType transportType, ValidateDeviceTokenRequestMsg msg, TransportServiceCallback callback); @@ -64,8 +65,6 @@ public interface TransportService { void process(ProvisionDeviceRequestMsg msg, TransportServiceCallback callback); - void getDeviceProfile(DeviceProfileId deviceProfileId, TransportServiceCallback callback); - void onProfileUpdate(DeviceProfile deviceProfile); boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportTenantProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportTenantProfileCache.java new file mode 100644 index 0000000000..1f6b31a356 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportTenantProfileCache.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport; + +import com.google.protobuf.ByteString; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; +import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; +import org.thingsboard.server.queue.discovery.TenantRoutingInfo; +import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; + +import java.util.Set; + +public interface TransportTenantProfileCache { + + TenantProfile get(TenantId tenantId); + + TenantProfileUpdateResult put(ByteString profileBody); + + boolean put(TenantId tenantId, TenantProfileId profileId); + + Set remove(TenantProfileId profileId); + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitFactory.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitFactory.java new file mode 100644 index 0000000000..362502bf17 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitFactory.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.limits; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.msg.tools.TbRateLimits; + +@Slf4j +@Component +public class DefaultTransportRateLimitFactory implements TransportRateLimitFactory { + + private static final DummyTransportRateLimit ALWAYS_TRUE = new DummyTransportRateLimit(); + + @Override + public TransportRateLimit create(TransportRateLimitType type, Object configuration) { + if (!StringUtils.isEmpty(configuration)) { + try { + return new SimpleTransportRateLimit(new TbRateLimits(configuration.toString()), configuration.toString()); + } catch (Exception e) { + log.warn("[{}] Failed to init rate limit with configuration: {}", type, configuration, e); + return ALWAYS_TRUE; + } + } else { + return ALWAYS_TRUE; + } + } + + @Override + public TransportRateLimit createDefault(TransportRateLimitType type) { + return ALWAYS_TRUE; + } +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java new file mode 100644 index 0000000000..9446022ec5 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java @@ -0,0 +1,115 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.limits; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.TenantProfileData; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.transport.TransportTenantProfileCache; +import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +@Service +@Slf4j +public class DefaultTransportRateLimitService implements TransportRateLimitService { + + private final ConcurrentMap perTenantLimits = new ConcurrentHashMap<>(); + private final ConcurrentMap perDeviceLimits = new ConcurrentHashMap<>(); + + private final TransportRateLimitFactory rateLimitFactory; + private final TransportTenantProfileCache tenantProfileCache; + + public DefaultTransportRateLimitService(TransportRateLimitFactory rateLimitFactory, TransportTenantProfileCache tenantProfileCache) { + this.rateLimitFactory = rateLimitFactory; + this.tenantProfileCache = tenantProfileCache; + } + + @Override + public TransportRateLimit getRateLimit(TenantId tenantId, TransportRateLimitType limitType) { + TransportRateLimit[] limits = perTenantLimits.get(tenantId); + if (limits == null) { + limits = fetchProfileAndInit(tenantId); + perTenantLimits.put(tenantId, limits); + } + return limits[limitType.ordinal()]; + } + + @Override + public TransportRateLimit getRateLimit(TenantId tenantId, DeviceId deviceId, TransportRateLimitType limitType) { + TransportRateLimit[] limits = perDeviceLimits.get(deviceId); + if (limits == null) { + limits = fetchProfileAndInit(tenantId); + perDeviceLimits.put(deviceId, limits); + } + return limits[limitType.ordinal()]; + } + + @Override + public void update(TenantProfileUpdateResult update) { + TransportRateLimit[] newLimits = createTransportRateLimits(update.getProfile()); + for (TenantId tenantId : update.getAffectedTenants()) { + mergeLimits(tenantId, newLimits); + } + } + + @Override + public void update(TenantId tenantId) { + mergeLimits(tenantId, fetchProfileAndInit(tenantId)); + } + + public void mergeLimits(TenantId tenantId, TransportRateLimit[] newRateLimits) { + TransportRateLimit[] oldRateLimits = perTenantLimits.get(tenantId); + if (oldRateLimits == null) { + perTenantLimits.put(tenantId, newRateLimits); + } else { + for (int i = 0; i < TransportRateLimitType.values().length; i++) { + TransportRateLimit newLimit = newRateLimits[i]; + TransportRateLimit oldLimit = oldRateLimits[i]; + if (newLimit != null && (oldLimit == null || !oldLimit.getConfiguration().equals(newLimit.getConfiguration()))) { + oldRateLimits[i] = newLimit; + } + } + } + } + + @Override + public void remove(TenantId tenantId) { + perTenantLimits.remove(tenantId); + } + + @Override + public void remove(DeviceId deviceId) { + perDeviceLimits.remove(deviceId); + } + + private TransportRateLimit[] fetchProfileAndInit(TenantId tenantId) { + return perTenantLimits.computeIfAbsent(tenantId, tmp -> createTransportRateLimits(tenantProfileCache.get(tenantId))); + } + + private TransportRateLimit[] createTransportRateLimits(TenantProfile tenantProfile) { + TenantProfileData profileData = tenantProfile.getProfileData(); + TransportRateLimit[] rateLimits = new TransportRateLimit[TransportRateLimitType.values().length]; + for (TransportRateLimitType type : TransportRateLimitType.values()) { + rateLimits[type.ordinal()] = rateLimitFactory.create(type, profileData.getProperties().get(type.getConfigurationKey())); + } + return rateLimits; + } +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DummyTransportRateLimit.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DummyTransportRateLimit.java new file mode 100644 index 0000000000..d6d58d55ba --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DummyTransportRateLimit.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.limits; + +public class DummyTransportRateLimit implements TransportRateLimit { + + @Override + public String getConfiguration() { + return ""; + } + + @Override + public boolean tryConsume() { + return true; + } + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java new file mode 100644 index 0000000000..08fbb7ec5d --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.limits; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.thingsboard.server.common.msg.tools.TbRateLimits; + +@RequiredArgsConstructor +public class SimpleTransportRateLimit implements TransportRateLimit { + + private final TbRateLimits rateLimit; + @Getter + private final String configuration; + + @Override + public boolean tryConsume() { + return rateLimit.tryConsume(); + } + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimit.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimit.java new file mode 100644 index 0000000000..0901e1becc --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimit.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.limits; + +public interface TransportRateLimit { + + String getConfiguration(); + + boolean tryConsume(); + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitFactory.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitFactory.java new file mode 100644 index 0000000000..54d83610c2 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitFactory.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.limits; + +public interface TransportRateLimitFactory { + + TransportRateLimit create(TransportRateLimitType type, Object config); + + TransportRateLimit createDefault(TransportRateLimitType type); + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java new file mode 100644 index 0000000000..2f2e808a36 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.limits; + +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; + +public interface TransportRateLimitService { + + TransportRateLimit getRateLimit(TenantId tenantId, TransportRateLimitType limit); + + TransportRateLimit getRateLimit(TenantId tenantId, DeviceId deviceId, TransportRateLimitType limit); + + void update(TenantProfileUpdateResult update); + + void update(TenantId tenantId); + + void remove(TenantId tenantId); + + void remove(DeviceId deviceId); + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java new file mode 100644 index 0000000000..becc5fbc86 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.limits; + +import lombok.Getter; + +public enum TransportRateLimitType { + + TENANT_MAX_MSGS("transport.tenant.max.msg"), + TENANT_MAX_DATA_POINTS("transport.tenant.max.dataPoints"), + DEVICE_MAX_MSGS("transport.device.max.msg"), + DEVICE_MAX_DATA_POINTS("transport.device.max.dataPoints"); + + @Getter + private final String configurationKey; + + TransportRateLimitType(String configurationKey) { + this.configurationKey = configurationKey; + } +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/profile/TenantProfileUpdateResult.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/profile/TenantProfileUpdateResult.java new file mode 100644 index 0000000000..53950e6de8 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/profile/TenantProfileUpdateResult.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.profile; + +import lombok.Data; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.id.TenantId; + +import java.util.Set; + +@Data +public class TenantProfileUpdateResult { + + private final TenantProfile profile; + private final Set affectedTenants; + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java similarity index 91% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportProfileCache.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java index 4d955de70c..3b44fd6a54 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportProfileCache.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java @@ -21,7 +21,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.transport.TransportProfileCache; +import org.thingsboard.server.common.transport.TransportDeviceProfileCache; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import java.util.Optional; @@ -31,13 +31,13 @@ import java.util.concurrent.ConcurrentMap; @Slf4j @Component @ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") -public class DefaultTransportProfileCache implements TransportProfileCache { +public class DefaultTransportDeviceProfileCache implements TransportDeviceProfileCache { private final ConcurrentMap deviceProfiles = new ConcurrentHashMap<>(); private final DataDecodingEncodingService dataDecodingEncodingService; - public DefaultTransportProfileCache(DataDecodingEncodingService dataDecodingEncodingService) { + public DefaultTransportDeviceProfileCache(DataDecodingEncodingService dataDecodingEncodingService) { this.dataDecodingEncodingService = dataDecodingEncodingService; } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 47abc07286..7631658602 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -29,25 +29,35 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceQueue; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.common.msg.tools.TbRateLimitsException; +import org.thingsboard.server.common.stats.MessagesStats; +import org.thingsboard.server.common.stats.StatsFactory; +import org.thingsboard.server.common.stats.StatsType; import org.thingsboard.server.common.transport.SessionMsgListener; -import org.thingsboard.server.common.transport.TransportProfileCache; +import org.thingsboard.server.common.transport.TransportDeviceProfileCache; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.common.transport.limits.TransportRateLimit; +import org.thingsboard.server.common.transport.limits.TransportRateLimitService; +import org.thingsboard.server.common.transport.limits.TransportRateLimitType; +import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; +import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; @@ -69,15 +79,13 @@ import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbTransportQueueFactory; -import org.thingsboard.server.common.stats.MessagesStats; -import org.thingsboard.server.common.stats.StatsFactory; -import org.thingsboard.server.common.stats.StatsType; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -98,12 +106,6 @@ import java.util.concurrent.atomic.AtomicInteger; @ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") public class DefaultTransportService implements TransportService { - @Value("${transport.rate_limits.enabled}") - private boolean rateLimitEnabled; - @Value("${transport.rate_limits.tenant}") - private String perTenantLimitsConf; - @Value("${transport.rate_limits.device}") - private String perDevicesLimitsConf; @Value("${transport.sessions.inactivity_timeout}") private long sessionInactivityTimeout; @Value("${transport.sessions.report_timeout}") @@ -119,7 +121,10 @@ public class DefaultTransportService implements TransportService { private final PartitionService partitionService; private final TbServiceInfoProvider serviceInfoProvider; private final StatsFactory statsFactory; - private final TransportProfileCache transportProfileCache; + private final TransportDeviceProfileCache deviceProfileCache; + private final TransportTenantProfileCache tenantProfileCache; + private final TransportRateLimitService rateLimitService; + private final DataDecodingEncodingService dataDecodingEncodingService; protected TbQueueRequestTemplate, TbProtoQueueMsg> transportApiRequestTemplate; protected TbQueueProducer> ruleEngineMsgProducer; @@ -132,14 +137,11 @@ public class DefaultTransportService implements TransportService { protected ScheduledExecutorService schedulerExecutor; protected ExecutorService transportCallbackExecutor; + private ExecutorService mainConsumerExecutor; private final ConcurrentMap sessions = new ConcurrentHashMap<>(); private final Map toServerRpcPendingMap = new ConcurrentHashMap<>(); - //TODO 3.2: @ybondarenko Implement cleanup of this maps. - private final ConcurrentMap perTenantLimits = new ConcurrentHashMap<>(); - private final ConcurrentMap perDeviceLimits = new ConcurrentHashMap<>(); - private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("transport-consumer")); private volatile boolean stopped = false; public DefaultTransportService(TbServiceInfoProvider serviceInfoProvider, @@ -147,22 +149,22 @@ public class DefaultTransportService implements TransportService { TbQueueProducerProvider producerProvider, PartitionService partitionService, StatsFactory statsFactory, - TransportProfileCache transportProfileCache) { + TransportDeviceProfileCache deviceProfileCache, + TransportTenantProfileCache tenantProfileCache, + TransportRateLimitService rateLimitService, DataDecodingEncodingService dataDecodingEncodingService) { this.serviceInfoProvider = serviceInfoProvider; this.queueProvider = queueProvider; this.producerProvider = producerProvider; this.partitionService = partitionService; this.statsFactory = statsFactory; - this.transportProfileCache = transportProfileCache; + this.deviceProfileCache = deviceProfileCache; + this.tenantProfileCache = tenantProfileCache; + this.rateLimitService = rateLimitService; + this.dataDecodingEncodingService = dataDecodingEncodingService; } @PostConstruct public void init() { - if (rateLimitEnabled) { - //Just checking the configuration parameters - new TbRateLimits(perTenantLimitsConf); - new TbRateLimits(perDevicesLimitsConf); - } this.ruleEngineProducerStats = statsFactory.createMessagesStats(StatsType.RULE_ENGINE.getName() + ".producer"); this.tbCoreProducerStats = statsFactory.createMessagesStats(StatsType.CORE.getName() + ".producer"); this.transportApiStats = statsFactory.createMessagesStats(StatsType.TRANSPORT.getName() + ".producer"); @@ -177,6 +179,7 @@ public class DefaultTransportService implements TransportService { TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_TRANSPORT, serviceInfoProvider.getServiceId()); transportNotificationsConsumer.subscribe(Collections.singleton(tpi)); transportApiRequestTemplate.init(); + mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("transport-consumer")); mainConsumerExecutor.execute(() -> { while (!stopped) { try { @@ -208,10 +211,6 @@ public class DefaultTransportService implements TransportService { @PreDestroy public void destroy() { - if (rateLimitEnabled) { - perTenantLimits.clear(); - perDeviceLimits.clear(); - } stopped = true; if (transportNotificationsConsumer != null) { @@ -232,7 +231,7 @@ public class DefaultTransportService implements TransportService { } @Override - public ScheduledExecutorService getSchedulerExecutor(){ + public ScheduledExecutorService getSchedulerExecutor() { return this.schedulerExecutor; } @@ -242,12 +241,12 @@ public class DefaultTransportService implements TransportService { } @Override - public TransportProtos.GetTenantRoutingInfoResponseMsg getRoutingInfo(TransportProtos.GetTenantRoutingInfoRequestMsg msg) { + public TransportProtos.GetEntityProfileResponseMsg getRoutingInfo(TransportProtos.GetEntityProfileRequestMsg msg) { TbProtoQueueMsg protoMsg = - new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setGetTenantRoutingInfoRequestMsg(msg).build()); + new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setEntityProfileRequestMsg(msg).build()); try { TbProtoQueueMsg response = transportApiRequestTemplate.send(protoMsg).get(); - return response.getValue().getGetTenantRoutingInfoResponseMsg(); + return response.getValue().getEntityProfileResponseMsg(); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } @@ -289,7 +288,7 @@ public class DefaultTransportService implements TransportService { result.deviceInfo(tdi); ByteString profileBody = msg.getProfileBody(); if (profileBody != null && !profileBody.isEmpty()) { - DeviceProfile profile = transportProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody); + DeviceProfile profile = deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody); if (transportType != DeviceTransportType.DEFAULT && profile != null && profile.getTransportType() != DeviceTransportType.DEFAULT && profile.getTransportType() != transportType) { log.debug("[{}] Device profile [{}] has different transport type: {}, expected: {}", tdi.getDeviceId(), tdi.getDeviceProfileId(), profile.getTransportType(), transportType); @@ -315,7 +314,7 @@ public class DefaultTransportService implements TransportService { result.deviceInfo(tdi); ByteString profileBody = msg.getProfileBody(); if (profileBody != null && !profileBody.isEmpty()) { - result.deviceProfile(transportProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody)); + result.deviceProfile(deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody)); } } return result.build(); @@ -339,8 +338,8 @@ public class DefaultTransportService implements TransportService { log.trace("Processing msg: {}", requestMsg); TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setProvisionDeviceRequestMsg(requestMsg).build()); ListenableFuture response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> - tmp.getValue().getProvisionDeviceResponseMsg() - , MoreExecutors.directExecutor()); + tmp.getValue().getProvisionDeviceResponseMsg() + , MoreExecutors.directExecutor()); AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor); } @@ -580,12 +579,11 @@ public class DefaultTransportService implements TransportService { if (log.isTraceEnabled()) { log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg); } - if (!rateLimitEnabled) { - return true; - } TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); - TbRateLimits rateLimits = perTenantLimits.computeIfAbsent(tenantId, id -> new TbRateLimits(perTenantLimitsConf)); - if (!rateLimits.tryConsume()) { + + TransportRateLimit tenantRateLimit = rateLimitService.getRateLimit(tenantId, TransportRateLimitType.TENANT_MAX_MSGS); + + if (!tenantRateLimit.tryConsume()) { if (callback != null) { callback.onError(new TbRateLimitsException(EntityType.TENANT)); } @@ -595,8 +593,8 @@ public class DefaultTransportService implements TransportService { return false; } DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); - rateLimits = perDeviceLimits.computeIfAbsent(deviceId, id -> new TbRateLimits(perDevicesLimitsConf)); - if (!rateLimits.tryConsume()) { + TransportRateLimit deviceRateLimit = rateLimitService.getRateLimit(tenantId, deviceId, TransportRateLimitType.DEVICE_MAX_MSGS); + if (!deviceRateLimit.tryConsume()) { if (callback != null) { callback.onError(new TbRateLimitsException(EntityType.DEVICE)); } @@ -637,16 +635,40 @@ public class DefaultTransportService implements TransportService { deregisterSession(md.getSessionInfo()); } } else { - if (toSessionMsg.hasDeviceProfileUpdateMsg()) { - DeviceProfile deviceProfile = transportProfileCache.put(toSessionMsg.getDeviceProfileUpdateMsg().getData()); - if (deviceProfile != null) { - onProfileUpdate(deviceProfile); + if (toSessionMsg.hasEntityUpdateMsg()) { + TransportProtos.EntityUpdateMsg msg = toSessionMsg.getEntityUpdateMsg(); + EntityType entityType = EntityType.valueOf(msg.getEntityType()); + if (EntityType.DEVICE_PROFILE.equals(entityType)) { + DeviceProfile deviceProfile = deviceProfileCache.put(msg.getData()); + if (deviceProfile != null) { + onProfileUpdate(deviceProfile); + } + } else if (EntityType.TENANT_PROFILE.equals(entityType)) { + TenantProfileUpdateResult update = tenantProfileCache.put(msg.getData()); + rateLimitService.update(update); + } else if (EntityType.TENANT.equals(entityType)) { + Optional profileOpt = dataDecodingEncodingService.decode(msg.getData().toByteArray()); + if (profileOpt.isPresent()) { + Tenant tenant = profileOpt.get(); + boolean updated = tenantProfileCache.put(tenant.getId(), tenant.getTenantProfileId()); + if (updated) { + rateLimitService.update(tenant.getId()); + } + } + } + } else if (toSessionMsg.hasEntityDeleteMsg()) { + TransportProtos.EntityDeleteMsg msg = toSessionMsg.getEntityDeleteMsg(); + EntityType entityType = EntityType.valueOf(msg.getEntityType()); + UUID entityUuid = new UUID(msg.getEntityIdMSB(), msg.getEntityIdLSB()); + if (EntityType.DEVICE_PROFILE.equals(entityType)) { + deviceProfileCache.evict(new DeviceProfileId(new UUID(msg.getEntityIdMSB(), msg.getEntityIdLSB()))); + } else if (EntityType.TENANT_PROFILE.equals(entityType)) { + tenantProfileCache.remove(new TenantProfileId(entityUuid)); + } else if (EntityType.TENANT.equals(entityType)) { + rateLimitService.remove(new TenantId(entityUuid)); + } else if (EntityType.DEVICE.equals(entityType)) { + rateLimitService.remove(new DeviceId(entityUuid)); } - } else if (toSessionMsg.hasDeviceProfileDeleteMsg()) { - transportProfileCache.evict(new DeviceProfileId(new UUID( - toSessionMsg.getDeviceProfileDeleteMsg().getProfileIdMSB(), - toSessionMsg.getDeviceProfileDeleteMsg().getProfileIdLSB() - ))); } else { //TODO: should we notify the device actor about missed session? log.debug("[{}] Missing session.", sessionId); @@ -654,38 +676,6 @@ public class DefaultTransportService implements TransportService { } } - @Override - public void getDeviceProfile(DeviceProfileId deviceProfileId, TransportServiceCallback callback) { - DeviceProfile deviceProfile = transportProfileCache.get(deviceProfileId); - if (deviceProfile != null) { - callback.onSuccess(deviceProfile); - } else { - log.trace("Processing device profile request: [{}]", deviceProfileId); - TransportProtos.GetDeviceProfileRequestMsg msg = TransportProtos.GetDeviceProfileRequestMsg.newBuilder() - .setProfileIdMSB(deviceProfileId.getId().getMostSignificantBits()) - .setProfileIdLSB(deviceProfileId.getId().getLeastSignificantBits()) - .build(); - TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), - TransportApiRequestMsg.newBuilder().setGetDeviceProfileRequestMsg(msg).build()); - AsyncCallbackTemplate.withCallback(transportApiRequestTemplate.send(protoMsg), - response -> { - ByteString devProfileBody = response.getValue().getGetDeviceProfileResponseMsg().getData(); - if (devProfileBody != null && !devProfileBody.isEmpty()) { - DeviceProfile profile = transportProfileCache.put(devProfileBody); - if (profile != null) { - callback.onSuccess(profile); - } else { - log.warn("Failed to decode device profile: {}", devProfileBody); - callback.onError(new IllegalArgumentException("Failed to decode device profile!")); - } - } else { - log.warn("Failed to find device profile: [{}]", deviceProfileId); - callback.onError(new IllegalArgumentException("Failed to find device profile!")); - } - }, callback::onError, transportCallbackExecutor); - } - } - @Override public void onProfileUpdate(DeviceProfile deviceProfile) { long deviceProfileIdMSB = deviceProfile.getId().getId().getMostSignificantBits(); @@ -750,7 +740,7 @@ public class DefaultTransportService implements TransportService { private RuleChainId resolveRuleChainId(TransportProtos.SessionInfoProto sessionInfo) { DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); - DeviceProfile deviceProfile = transportProfileCache.get(deviceProfileId); + DeviceProfile deviceProfile = deviceProfileCache.get(deviceProfileId); RuleChainId ruleChainId; if (deviceProfile == null) { log.warn("[{}] Device profile is null!", deviceProfileId); @@ -779,7 +769,7 @@ public class DefaultTransportService implements TransportService { } } - private class StatsCallback implements TbQueueCallback { + private static class StatsCallback implements TbQueueCallback { private final TbQueueCallback callback; private final MessagesStats stats; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java new file mode 100644 index 0000000000..717627a60e --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java @@ -0,0 +1,154 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.service; + +import com.google.protobuf.ByteString; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportTenantProfileCache; +import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; +import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.discovery.TenantRoutingInfo; +import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; + +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +@Component +@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") +@Slf4j +public class DefaultTransportTenantProfileCache implements TransportTenantProfileCache { + + private final Lock tenantProfileFetchLock = new ReentrantLock(); + private final ConcurrentMap profiles = new ConcurrentHashMap<>(); + private final ConcurrentMap tenantIds = new ConcurrentHashMap<>(); + private final ConcurrentMap> tenantProfileIds = new ConcurrentHashMap<>(); + private final DataDecodingEncodingService dataDecodingEncodingService; + + private TransportService transportService; + + @Lazy + @Autowired + public void setTransportService(TransportService transportService) { + this.transportService = transportService; + } + + public DefaultTransportTenantProfileCache(DataDecodingEncodingService dataDecodingEncodingService) { + this.dataDecodingEncodingService = dataDecodingEncodingService; + } + + @Override + public TenantProfile get(TenantId tenantId) { + return getTenantProfile(tenantId); + } + + @Override + public TenantProfileUpdateResult put(ByteString profileBody) { + Optional profileOpt = dataDecodingEncodingService.decode(profileBody.toByteArray()); + if (profileOpt.isPresent()) { + TenantProfile newProfile = profileOpt.get(); + log.trace("[{}] put: {}", newProfile.getId(), newProfile); + return new TenantProfileUpdateResult(newProfile, tenantProfileIds.get(newProfile.getId())); + } else { + log.warn("Failed to decode profile: {}", profileBody.toString()); + return new TenantProfileUpdateResult(null, Collections.emptySet()); + } + } + + @Override + public boolean put(TenantId tenantId, TenantProfileId profileId) { + log.trace("[{}] put: {}", tenantId, profileId); + TenantProfileId oldProfileId = tenantIds.get(tenantId); + if (oldProfileId != null && !oldProfileId.equals(profileId)) { + tenantProfileIds.computeIfAbsent(oldProfileId, id -> ConcurrentHashMap.newKeySet()).remove(tenantId); + tenantIds.put(tenantId, profileId); + tenantProfileIds.computeIfAbsent(profileId, id -> ConcurrentHashMap.newKeySet()).add(tenantId); + return true; + } else { + return false; + } + } + + @Override + public Set remove(TenantProfileId profileId) { + Set tenants = tenantProfileIds.remove(profileId); + if (tenants != null) { + tenants.forEach(tenantIds::remove); + } + profiles.remove(profileId); + return tenants; + } + + private TenantProfile getTenantProfile(TenantId tenantId) { + TenantProfile profile = null; + TenantProfileId tenantProfileId = tenantIds.get(tenantId); + if (tenantProfileId != null) { + profile = profiles.get(tenantProfileId); + } + if (profile == null) { + tenantProfileFetchLock.lock(); + try { + tenantProfileId = tenantIds.get(tenantId); + if (tenantProfileId != null) { + profile = profiles.get(tenantProfileId); + } + if (profile == null) { + TransportProtos.GetEntityProfileRequestMsg msg = TransportProtos.GetEntityProfileRequestMsg.newBuilder() + .setEntityType(EntityType.TENANT.name()) + .setEntityIdMSB(tenantId.getId().getMostSignificantBits()) + .setEntityIdLSB(tenantId.getId().getLeastSignificantBits()) + .build(); + TransportProtos.GetEntityProfileResponseMsg routingInfo = transportService.getRoutingInfo(msg); + Optional profileOpt = dataDecodingEncodingService.decode(routingInfo.getData().toByteArray()); + if (profileOpt.isPresent()) { + profile = profileOpt.get(); + TenantProfile existingProfile = profiles.get(profile.getId()); + if (existingProfile != null) { + profile = existingProfile; + } else { + profiles.put(profile.getId(), profile); + } + tenantProfileIds.computeIfAbsent(profile.getId(), id -> ConcurrentHashMap.newKeySet()).add(tenantId); + tenantIds.put(tenantId, profile.getId()); + } else { + log.warn("[{}] Can't decode tenant profile: {}", tenantId, routingInfo.getData()); + throw new RuntimeException("Can't decode tenant profile!"); + } + } + } finally { + tenantProfileFetchLock.unlock(); + } + } + return profile; + } + + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportTenantRoutingInfoService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportTenantRoutingInfoService.java index f2534a64c9..55dee282ec 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportTenantRoutingInfoService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportTenantRoutingInfoService.java @@ -16,14 +16,11 @@ package org.thingsboard.server.common.transport.service; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.transport.TransportService; -import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg; +import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.queue.discovery.TenantRoutingInfo; import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; @@ -32,21 +29,16 @@ import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; @ConditionalOnExpression("'${service.type:null}'=='tb-transport'") public class TransportTenantRoutingInfoService implements TenantRoutingInfoService { - private TransportService transportService; + private TransportTenantProfileCache tenantProfileCache; - @Lazy - @Autowired - public void setTransportService(TransportService transportService) { - this.transportService = transportService; + public TransportTenantRoutingInfoService(TransportTenantProfileCache tenantProfileCache) { + this.tenantProfileCache = tenantProfileCache; } @Override public TenantRoutingInfo getRoutingInfo(TenantId tenantId) { - GetTenantRoutingInfoRequestMsg msg = GetTenantRoutingInfoRequestMsg.newBuilder() - .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) - .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) - .build(); - GetTenantRoutingInfoResponseMsg routingInfo = transportService.getRoutingInfo(msg); - return new TenantRoutingInfo(tenantId, routingInfo.getIsolatedTbCore(), routingInfo.getIsolatedTbRuleEngine()); + TenantProfile profile = tenantProfileCache.get(tenantId); + return new TenantRoutingInfo(tenantId, profile.isIsolatedTbCore(), profile.isIsolatedTbRuleEngine()); } + } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/DataDecodingEncodingService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/DataDecodingEncodingService.java index 1b10cb5dc3..bf70fa5ef1 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/DataDecodingEncodingService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/DataDecodingEncodingService.java @@ -15,8 +15,6 @@ */ package org.thingsboard.server.common.transport.util; -import org.thingsboard.server.common.msg.TbActorMsg; - import java.util.Optional; public interface DataDecodingEncodingService { diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index 3f57b32fab..229196d325 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -49,10 +49,6 @@ transport: sessions: inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" - rate_limits: - enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}" - tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}" - device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}" json: # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index 77d5f30fa7..6aaa42ca18 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -42,10 +42,6 @@ transport: sessions: inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" - rate_limits: - enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}" - tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}" - device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}" json: # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index f01b15c77a..f9341dfb86 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -71,10 +71,6 @@ transport: sessions: inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" - rate_limits: - enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}" - tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}" - device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}" json: # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" From 51ac96d0102bb299cf86bb1ccb272aadeb03dfa5 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 15 Oct 2020 19:32:02 +0300 Subject: [PATCH 13/23] Alarm Result State --- .../rule/engine/profile/AlarmEvalResult.java | 22 +++++++++++ .../rule/engine/profile/AlarmRuleState.java | 30 ++++++++------- .../rule/engine/profile/AlarmState.java | 38 +++++++++++++------ .../engine/profile/TbDeviceProfileNode.java | 4 +- 4 files changed, 67 insertions(+), 27 deletions(-) create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmEvalResult.java diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmEvalResult.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmEvalResult.java new file mode 100644 index 0000000000..7d510fe5fe --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmEvalResult.java @@ -0,0 +1,22 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.profile; + +public enum AlarmEvalResult { + + FALSE, NOT_YET_TRUE, TRUE; + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java index d9d08efbdc..8be2b81a59 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java @@ -123,17 +123,17 @@ class AlarmRuleState { } } - public boolean eval(DataSnapshot data) { + public AlarmEvalResult eval(DataSnapshot data) { boolean active = isActive(data.getTs()); switch (spec.getType()) { case SIMPLE: - return active && eval(alarmRule.getCondition(), data); + return (active && eval(alarmRule.getCondition(), data)) ? AlarmEvalResult.TRUE : AlarmEvalResult.FALSE; case DURATION: return evalDuration(data, active); case REPEATING: return evalRepeating(data, active); default: - return false; + return AlarmEvalResult.FALSE; } } @@ -203,17 +203,17 @@ class AlarmRuleState { } } - private boolean evalRepeating(DataSnapshot data, boolean active) { + private AlarmEvalResult evalRepeating(DataSnapshot data, boolean active) { if (active && eval(alarmRule.getCondition(), data)) { state.setEventCount(state.getEventCount() + 1); updateFlag = true; - return state.getEventCount() >= requiredRepeats; + return state.getEventCount() >= requiredRepeats ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE; } else { - return false; + return AlarmEvalResult.FALSE; } } - private boolean evalDuration(DataSnapshot data, boolean active) { + private AlarmEvalResult evalDuration(DataSnapshot data, boolean active) { if (active && eval(alarmRule.getCondition(), data)) { if (state.getLastEventTs() > 0) { if (data.getTs() > state.getLastEventTs()) { @@ -226,24 +226,28 @@ class AlarmRuleState { state.setDuration(0L); updateFlag = true; } - return state.getDuration() > requiredDurationInMs; + return state.getDuration() > requiredDurationInMs ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE; } else { - return false; + return AlarmEvalResult.FALSE; } } - public boolean eval(long ts) { + public AlarmEvalResult eval(long ts) { switch (spec.getType()) { case SIMPLE: case REPEATING: - return false; + return AlarmEvalResult.NOT_YET_TRUE; case DURATION: if (requiredDurationInMs > 0 && state.getLastEventTs() > 0 && ts > state.getLastEventTs()) { long duration = state.getDuration() + (ts - state.getLastEventTs()); - return duration > requiredDurationInMs && isActive(ts); + if (isActive(ts)) { + return duration > requiredDurationInMs ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE; + } else { + return AlarmEvalResult.FALSE; + } } default: - return false; + return AlarmEvalResult.FALSE; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 5fb2c2957c..df46a4bf88 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -72,7 +72,7 @@ class AlarmState { return createOrClearAlarms(ctx, ts, null, AlarmRuleState::eval); } - public boolean createOrClearAlarms(TbContext ctx, T data, SnapshotUpdate update, BiFunction evalFunction) { + public boolean createOrClearAlarms(TbContext ctx, T data, SnapshotUpdate update, BiFunction evalFunction) { boolean stateUpdate = false; AlarmSeverity resultSeverity = null; log.debug("[{}] processing update: {}", alarmDefinition.getId(), data); @@ -81,22 +81,28 @@ class AlarmState { log.debug("[{}][{}] Update is not valid for current rule state", alarmDefinition.getId(), state.getSeverity()); continue; } - boolean evalResult = evalFunction.apply(state, data); + AlarmEvalResult evalResult = evalFunction.apply(state, data); stateUpdate |= state.checkUpdate(); - if (evalResult) { + if (AlarmEvalResult.TRUE.equals(evalResult)) { resultSeverity = state.getSeverity(); break; + } else if (AlarmEvalResult.FALSE.equals(evalResult)) { + state.clear(); + stateUpdate |= state.checkUpdate(); } } if (resultSeverity != null) { - pushMsg(ctx, calculateAlarmResult(ctx, resultSeverity)); + TbAlarmResult result = calculateAlarmResult(ctx, resultSeverity); + if (result != null) { + pushMsg(ctx, result); + } } else if (currentAlarm != null && clearState != null) { if (!validateUpdate(update, clearState)) { log.debug("[{}] Update is not valid for current clear state", alarmDefinition.getId()); return stateUpdate; } - Boolean evalResult = evalFunction.apply(clearState, data); - if (evalResult) { + AlarmEvalResult evalResult = evalFunction.apply(clearState, data); + if (AlarmEvalResult.TRUE.equals(evalResult)) { stateUpdate |= clearState.checkUpdate(); for (AlarmRuleState state : createRulesSortedBySeverityDesc) { state.clear(); @@ -105,6 +111,9 @@ class AlarmState { ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), JacksonUtil.OBJECT_MAPPER.createObjectNode(), System.currentTimeMillis()); pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm)); currentAlarm = null; + } else if (AlarmEvalResult.FALSE.equals(evalResult)) { + clearState.clear(); + stateUpdate |= clearState.checkUpdate(); } } return stateUpdate; @@ -183,13 +192,18 @@ class AlarmState { // Maybe we should fetch alarm every time? currentAlarm.setEndTs(System.currentTimeMillis()); AlarmSeverity oldSeverity = currentAlarm.getSeverity(); - if (!oldSeverity.equals(severity)) { - currentAlarm.setSeverity(severity); - currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm); - return new TbAlarmResult(false, false, true, false, currentAlarm); + // Skip update if severity is decreased. + if (severity.ordinal() <= oldSeverity.ordinal()) { + if (!oldSeverity.equals(severity)) { + currentAlarm.setSeverity(severity); + currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm); + return new TbAlarmResult(false, false, true, false, currentAlarm); + } else { + currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm); + return new TbAlarmResult(false, true, false, false, currentAlarm); + } } else { - currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm); - return new TbAlarmResult(false, true, false, false, currentAlarm); + return null; } } else { currentAlarm = new Alarm(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java index 3fff9dbc2e..b4bbf08323 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java @@ -167,14 +167,14 @@ public class TbDeviceProfileNode implements TbNode { protected void updateProfile(TbContext ctx, DeviceProfileId deviceProfileId) throws ExecutionException, InterruptedException { DeviceProfile deviceProfile = cache.get(ctx.getTenantId(), deviceProfileId); if (deviceProfile != null) { - log.info("[{}] Received device profile update notification: {}", ctx.getSelfId(), deviceProfile); + log.debug("[{}] Received device profile update notification: {}", ctx.getSelfId(), deviceProfile); for (DeviceState state : deviceStates.values()) { if (deviceProfile.getId().equals(state.getProfileId())) { state.updateProfile(ctx, deviceProfile); } } } else { - log.info("[{}] Received stale profile update notification: [{}]", ctx.getSelfId(), deviceProfileId); + log.debug("[{}] Received stale profile update notification: [{}]", ctx.getSelfId(), deviceProfileId); } } From a17383c60f87378c19f58197cefd6a51dbdff2d7 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 16 Oct 2020 12:03:27 +0300 Subject: [PATCH 14/23] UI: Added a rule chain field in the device profile to the device wizard --- .../components/wizard/device-wizard-dialog.component.html | 6 ++++++ .../components/wizard/device-wizard-dialog.component.ts | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index 7805171fe3..0d224122d6 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -91,6 +91,12 @@ +
+ + +
{{ 'device.is-gateway' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts index 5c59eb448b..c4f100b30f 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts @@ -42,6 +42,7 @@ import { ErrorStateMatcher } from '@angular/material/core'; import { StepperSelectionEvent } from '@angular/cdk/stepper'; import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; +import { RuleChainId } from '@shared/models/id/rule-chain-id'; @Component({ selector: 'tb-device-wizard', @@ -103,6 +104,7 @@ export class DeviceWizardDialogComponent extends addProfileType: [0], deviceProfileId: [null, Validators.required], newDeviceProfileTitle: [{value: null, disabled: true}], + defaultRuleChainId: [{value: null, disabled: true}], description: [''] } ); @@ -114,6 +116,7 @@ export class DeviceWizardDialogComponent extends this.deviceWizardFormGroup.get('deviceProfileId').enable(); this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators(null); this.deviceWizardFormGroup.get('newDeviceProfileTitle').disable(); + this.deviceWizardFormGroup.get('defaultRuleChainId').disable(); this.deviceWizardFormGroup.updateValueAndValidity(); this.createProfile = false; this.createTransportConfiguration = false; @@ -122,6 +125,7 @@ export class DeviceWizardDialogComponent extends this.deviceWizardFormGroup.get('deviceProfileId').disable(); this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]); this.deviceWizardFormGroup.get('newDeviceProfileTitle').enable(); + this.deviceWizardFormGroup.get('defaultRuleChainId').enable(); this.deviceWizardFormGroup.updateValueAndValidity(); this.createProfile = true; this.createTransportConfiguration = this.deviceWizardFormGroup.get('transportType').value && @@ -274,6 +278,9 @@ export class DeviceWizardDialogComponent extends provisionConfiguration: deviceProvisionConfiguration } }; + if (this.deviceWizardFormGroup.get('defaultRuleChainId').value) { + deviceProfile.defaultRuleChainId = new RuleChainId(this.deviceWizardFormGroup.get('defaultRuleChainId').value); + } return this.deviceProfileService.saveDeviceProfile(deviceProfile).pipe( map(profile => profile.id), tap((profileId) => { From f8fdbcaf5b4c2695fbfad737a73309d520ef4f87 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 16 Oct 2020 12:31:34 +0300 Subject: [PATCH 15/23] Fix DefaultTransportRateLimitService - add conditional expression --- .../transport/limits/DefaultTransportRateLimitService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java index 9446022ec5..2bf2f6a697 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.transport.limits; import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfileData; @@ -28,6 +29,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @Service +@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") @Slf4j public class DefaultTransportRateLimitService implements TransportRateLimitService { From f181beec61b1517d0099c975f40048a4b094731b Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 16 Oct 2020 16:04:06 +0300 Subject: [PATCH 16/23] OAuth2 form improvements --- .../app/modules/home/pages/admin/oauth2-settings.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index 37ae0433d3..6ec372d3b6 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -347,7 +347,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha clientRegistration.get('authorizationUri').disable(); clientRegistration.get('jwkSetUri').disable(); clientRegistration.get('userInfoUri').disable(); - clientRegistration.patchValue(template); + clientRegistration.patchValue(template, {emitEvent: false}); } } From e9bf5bae29318d6b0a63dd104d365a522b24508d Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 19 Oct 2020 11:50:06 +0300 Subject: [PATCH 17/23] Rate limit improvements --- .../server/common/msg/tools/TbRateLimits.java | 4 + .../queue/util/TbTransportComponent.java | 26 +++++++ .../common/transport/TransportService.java | 3 + .../DefaultTransportRateLimitService.java | 74 +++++++++++-------- .../limits/DummyTransportRateLimit.java | 5 ++ .../limits/SimpleTransportRateLimit.java | 4 + .../transport/limits/TransportRateLimit.java | 2 + .../limits/TransportRateLimitService.java | 4 +- .../limits/TransportRateLimitType.java | 24 ++++-- .../DefaultTransportDeviceProfileCache.java | 3 +- .../service/DefaultTransportService.java | 44 +++++------ .../DefaultTransportTenantProfileCache.java | 3 +- 12 files changed, 136 insertions(+), 60 deletions(-) create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/util/TbTransportComponent.java diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/tools/TbRateLimits.java b/common/message/src/main/java/org/thingsboard/server/common/msg/tools/TbRateLimits.java index 3c79895c8b..3550bc44a9 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/tools/TbRateLimits.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/tools/TbRateLimits.java @@ -50,4 +50,8 @@ public class TbRateLimits { return bucket.tryConsume(1); } + public boolean tryConsume(long number) { + return bucket.tryConsume(number); + } + } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/util/TbTransportComponent.java b/common/queue/src/main/java/org/thingsboard/server/queue/util/TbTransportComponent.java new file mode 100644 index 0000000000..dde1c6a620 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/util/TbTransportComponent.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.util; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") +public @interface TbTransportComponent { +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 9a2fd75d47..8f57db3897 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.common.transport.limits.TransportRateLimitType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; @@ -69,6 +70,8 @@ public interface TransportService { boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback); + boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints, TransportRateLimitType... limits); + void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback callback); void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback callback); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java index 2bf2f6a697..569f5446db 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java @@ -16,7 +16,6 @@ package org.thingsboard.server.common.transport.limits; import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfileData; @@ -24,12 +23,13 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; +import org.thingsboard.server.queue.util.TbTransportComponent; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @Service -@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") +@TbTransportComponent @Slf4j public class DefaultTransportRateLimitService implements TransportRateLimitService { @@ -45,23 +45,21 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi } @Override - public TransportRateLimit getRateLimit(TenantId tenantId, TransportRateLimitType limitType) { - TransportRateLimit[] limits = perTenantLimits.get(tenantId); - if (limits == null) { - limits = fetchProfileAndInit(tenantId); - perTenantLimits.put(tenantId, limits); - } - return limits[limitType.ordinal()]; - } - - @Override - public TransportRateLimit getRateLimit(TenantId tenantId, DeviceId deviceId, TransportRateLimitType limitType) { - TransportRateLimit[] limits = perDeviceLimits.get(deviceId); - if (limits == null) { - limits = fetchProfileAndInit(tenantId); - perDeviceLimits.put(deviceId, limits); + public TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits) { + TransportRateLimit[] tenantLimits = getTenantRateLimits(tenantId); + TransportRateLimit[] deviceLimits = getDeviceRateLimits(tenantId, deviceId); + for (TransportRateLimitType limitType : limits) { + TransportRateLimit rateLimit; + if (limitType.isTenantLevel()) { + rateLimit = tenantLimits[limitType.ordinal()]; + } else { + rateLimit = deviceLimits[limitType.ordinal()]; + } + if (!rateLimit.tryConsume(limitType.isMessageLevel() ? 1L : dataPoints)) { + return limitType; + } } - return limits[limitType.ordinal()]; + return null; } @Override @@ -77,7 +75,17 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi mergeLimits(tenantId, fetchProfileAndInit(tenantId)); } - public void mergeLimits(TenantId tenantId, TransportRateLimit[] newRateLimits) { + @Override + public void remove(TenantId tenantId) { + perTenantLimits.remove(tenantId); + } + + @Override + public void remove(DeviceId deviceId) { + perDeviceLimits.remove(deviceId); + } + + private void mergeLimits(TenantId tenantId, TransportRateLimit[] newRateLimits) { TransportRateLimit[] oldRateLimits = perTenantLimits.get(tenantId); if (oldRateLimits == null) { perTenantLimits.put(tenantId, newRateLimits); @@ -92,16 +100,6 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi } } - @Override - public void remove(TenantId tenantId) { - perTenantLimits.remove(tenantId); - } - - @Override - public void remove(DeviceId deviceId) { - perDeviceLimits.remove(deviceId); - } - private TransportRateLimit[] fetchProfileAndInit(TenantId tenantId) { return perTenantLimits.computeIfAbsent(tenantId, tmp -> createTransportRateLimits(tenantProfileCache.get(tenantId))); } @@ -114,4 +112,22 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi } return rateLimits; } + + private TransportRateLimit[] getTenantRateLimits(TenantId tenantId) { + TransportRateLimit[] limits = perTenantLimits.get(tenantId); + if (limits == null) { + limits = fetchProfileAndInit(tenantId); + perTenantLimits.put(tenantId, limits); + } + return limits; + } + + private TransportRateLimit[] getDeviceRateLimits(TenantId tenantId, DeviceId deviceId) { + TransportRateLimit[] limits = perDeviceLimits.get(deviceId); + if (limits == null) { + limits = fetchProfileAndInit(tenantId); + perDeviceLimits.put(deviceId, limits); + } + return limits; + } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DummyTransportRateLimit.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DummyTransportRateLimit.java index d6d58d55ba..a93487632f 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DummyTransportRateLimit.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DummyTransportRateLimit.java @@ -22,6 +22,11 @@ public class DummyTransportRateLimit implements TransportRateLimit { return ""; } + @Override + public boolean tryConsume(long number) { + return true; + } + @Override public boolean tryConsume() { return true; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java index 08fbb7ec5d..3253272ded 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java @@ -31,4 +31,8 @@ public class SimpleTransportRateLimit implements TransportRateLimit { return rateLimit.tryConsume(); } + @Override + public boolean tryConsume(long number) { + return number <= 0 || rateLimit.tryConsume(number); + } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimit.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimit.java index 0901e1becc..a2eea81d3a 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimit.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimit.java @@ -21,4 +21,6 @@ public interface TransportRateLimit { boolean tryConsume(); + boolean tryConsume(long number); + } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java index 2f2e808a36..a97fbfc61d 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java @@ -21,9 +21,7 @@ import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult public interface TransportRateLimitService { - TransportRateLimit getRateLimit(TenantId tenantId, TransportRateLimitType limit); - - TransportRateLimit getRateLimit(TenantId tenantId, DeviceId deviceId, TransportRateLimitType limit); + TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits); void update(TenantProfileUpdateResult update); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java index becc5fbc86..a3e6da6683 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java @@ -19,15 +19,29 @@ import lombok.Getter; public enum TransportRateLimitType { - TENANT_MAX_MSGS("transport.tenant.max.msg"), - TENANT_MAX_DATA_POINTS("transport.tenant.max.dataPoints"), - DEVICE_MAX_MSGS("transport.device.max.msg"), - DEVICE_MAX_DATA_POINTS("transport.device.max.dataPoints"); + TENANT_MAX_MSGS("transport.tenant.msg", true, true), + TENANT_TELEMETRY_MSGS("transport.tenant.telemetry", true, true), + TENANT_MAX_DATA_POINTS("transport.tenant.dataPoints", true, false), + DEVICE_MAX_MSGS("transport.device.msg", false, true), + DEVICE_TELEMETRY_MSGS("transport.device.telemetry", false, true), + DEVICE_MAX_DATA_POINTS("transport.device.dataPoints", false, false); @Getter private final String configurationKey; + @Getter + private final boolean tenantLevel; + @Getter + private final boolean deviceLevel; + @Getter + private final boolean messageLevel; + @Getter + private final boolean dataPointLevel; - TransportRateLimitType(String configurationKey) { + TransportRateLimitType(String configurationKey, boolean tenantLevel, boolean messageLevel) { this.configurationKey = configurationKey; + this.tenantLevel = tenantLevel; + this.deviceLevel = !tenantLevel; + this.messageLevel = messageLevel; + this.dataPointLevel = !messageLevel; } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java index 3b44fd6a54..b12aab1a8c 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.transport.TransportDeviceProfileCache; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; +import org.thingsboard.server.queue.util.TbTransportComponent; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; @@ -30,7 +31,7 @@ import java.util.concurrent.ConcurrentMap; @Slf4j @Component -@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") +@TbTransportComponent public class DefaultTransportDeviceProfileCache implements TransportDeviceProfileCache { private final ConcurrentMap deviceProfiles = new ConcurrentHashMap<>(); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 7631658602..3f37685383 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -79,6 +79,7 @@ import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbTransportQueueFactory; +import org.thingsboard.server.queue.util.TbTransportComponent; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -103,7 +104,7 @@ import java.util.concurrent.atomic.AtomicInteger; */ @Slf4j @Service -@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") +@TbTransportComponent public class DefaultTransportService implements TransportService { @Value("${transport.sessions.inactivity_timeout}") @@ -363,7 +364,11 @@ public class DefaultTransportService implements TransportService { @Override public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback callback) { - if (checkLimits(sessionInfo, msg, callback)) { + int dataPoints = 0; + for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { + dataPoints += tsKv.getKvCount(); + } + if (checkLimits(sessionInfo, msg, callback, dataPoints, TELEMETRY)) { reportActivityInternal(sessionInfo); TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); @@ -384,7 +389,7 @@ public class DefaultTransportService implements TransportService { @Override public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback callback) { - if (checkLimits(sessionInfo, msg, callback)) { + if (checkLimits(sessionInfo, msg, callback, msg.getKvCount(), TELEMETRY)) { reportActivityInternal(sessionInfo); TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); @@ -574,37 +579,34 @@ public class DefaultTransportService implements TransportService { sessions.remove(toSessionId(sessionInfo)); } + private TransportRateLimitType[] DEFAULT = new TransportRateLimitType[]{TransportRateLimitType.TENANT_MAX_MSGS, TransportRateLimitType.DEVICE_MAX_MSGS}; + private TransportRateLimitType[] TELEMETRY = TransportRateLimitType.values(); + @Override public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback) { + return checkLimits(sessionInfo, msg, callback, 0, DEFAULT); + } + + @Override + public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints, TransportRateLimitType... limits) { if (log.isTraceEnabled()) { log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg); } TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); - - TransportRateLimit tenantRateLimit = rateLimitService.getRateLimit(tenantId, TransportRateLimitType.TENANT_MAX_MSGS); - - if (!tenantRateLimit.tryConsume()) { - if (callback != null) { - callback.onError(new TbRateLimitsException(EntityType.TENANT)); - } - if (log.isTraceEnabled()) { - log.trace("[{}][{}] Tenant level rate limit detected: {}", toSessionId(sessionInfo), tenantId, msg); - } - return false; - } DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); - TransportRateLimit deviceRateLimit = rateLimitService.getRateLimit(tenantId, deviceId, TransportRateLimitType.DEVICE_MAX_MSGS); - if (!deviceRateLimit.tryConsume()) { + + TransportRateLimitType limit = rateLimitService.checkLimits(tenantId, deviceId, 0, limits); + if (limit == null) { + return true; + } else { if (callback != null) { - callback.onError(new TbRateLimitsException(EntityType.DEVICE)); + callback.onError(new TbRateLimitsException(limit.isTenantLevel() ? EntityType.TENANT : EntityType.DEVICE)); } if (log.isTraceEnabled()) { - log.trace("[{}][{}] Device level rate limit detected: {}", toSessionId(sessionInfo), deviceId, msg); + log.trace("[{}][{}] {} rateLimit detected: {}", toSessionId(sessionInfo), tenantId, limit, msg); } return false; } - - return true; } protected void processToTransportMsg(TransportProtos.ToTransportMsg toSessionMsg) { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java index 717627a60e..784f7b3ae8 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.TenantRoutingInfo; import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; +import org.thingsboard.server.queue.util.TbTransportComponent; import java.util.Collections; import java.util.Optional; @@ -43,7 +44,7 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @Component -@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") +@TbTransportComponent @Slf4j public class DefaultTransportTenantProfileCache implements TransportTenantProfileCache { From c5cc55156feda1ced2f1befb2ba93b6131ad8811 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 19 Oct 2020 12:16:11 +0300 Subject: [PATCH 18/23] UI: Improvement used templated in OAuth2 --- .../pages/admin/oauth2-settings.component.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index 6ec372d3b6..ac26797899 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -142,6 +142,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha tenantNamePattern = {value: null, disabled: true}; } const basicGroup = this.fb.group({ + emailAttributeKey: [mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required], firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : ''], lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : ''], tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : TenantNameStrategy.DOMAIN], @@ -151,11 +152,6 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha alwaysFullScreen: [isDefinedAndNotNull(mapperConfigBasic?.alwaysFullScreen) ? mapperConfigBasic.alwaysFullScreen : false] }); - if (MapperConfigType.GITHUB !== type) { - basicGroup.addControl('emailAttributeKey', - this.fb.control( mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required)); - } - this.subscriptions.push(basicGroup.get('tenantNameStrategy').valueChanges.subscribe((domain) => { if (domain === 'CUSTOM') { basicGroup.get('tenantNamePattern').enable(); @@ -347,7 +343,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha clientRegistration.get('authorizationUri').disable(); clientRegistration.get('jwkSetUri').disable(); clientRegistration.get('userInfoUri').disable(); - clientRegistration.patchValue(template, {emitEvent: false}); + clientRegistration.patchValue(template); } } @@ -358,11 +354,15 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom)); } else { mapperConfig.removeControl('custom'); - if (mapperConfig.get('basic')) { - mapperConfig.setControl('basic', this.formBasicGroup(type, predefinedValue?.basic)); - } else { + if (!mapperConfig.get('basic')) { mapperConfig.addControl('basic', this.formBasicGroup(type, predefinedValue?.basic)); } + if (type === MapperConfigType.GITHUB) { + mapperConfig.get('basic.emailAttributeKey').disable(); + mapperConfig.get('basic.emailAttributeKey').patchValue(null, {emitEvent: false}); + } else { + mapperConfig.get('basic.emailAttributeKey').enable(); + } } } From 9cdf3dd80c2f6af64d313969e2d1b74635a78d03 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 19 Oct 2020 16:43:39 +0300 Subject: [PATCH 19/23] Improvements in MQTT publish processing --- .../src/main/java/org/thingsboard/mqtt/MqttClientImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java index aef2cad684..c493f2b860 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java @@ -340,6 +340,7 @@ final class MqttClientImpl implements MqttClient { MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(topic, getNewMessageId().messageId()); MqttPublishMessage message = new MqttPublishMessage(fixedHeader, variableHeader, payload); MqttPendingPublish pendingPublish = new MqttPendingPublish(variableHeader.packetId(), future, payload.retain(), message, qos); + this.pendingPublishes.put(pendingPublish.getMessageId(), pendingPublish); ChannelFuture channelFuture = this.sendAndFlushPacket(message); if (channelFuture != null) { @@ -350,9 +351,9 @@ final class MqttClientImpl implements MqttClient { } } if (pendingPublish.isSent() && pendingPublish.getQos() == MqttQoS.AT_MOST_ONCE) { + this.pendingPublishes.remove(pendingPublish.getMessageId()); pendingPublish.getFuture().setSuccess(null); //We don't get an ACK for QOS 0 } else if (pendingPublish.isSent()) { - this.pendingPublishes.put(pendingPublish.getMessageId(), pendingPublish); pendingPublish.startPublishRetransmissionTimer(this.eventLoop.next(), this::sendAndFlushPacket); } return future; From 47d5aee3dc4a3d88b54e06f814443e34ff4afd05 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 19 Oct 2020 19:05:34 +0300 Subject: [PATCH 20/23] UI: Improvement alarm widget and fixed timeseries --- .../lib/alarms-table-widget.component.ts | 6 ++--- .../widget/lib/table-widget.models.ts | 2 +- .../lib/timeseries-table-widget.component.ts | 22 ++++++++++++------- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts index 26055c55b2..58176a48d1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts @@ -29,7 +29,7 @@ import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { WidgetAction, WidgetContext } from '@home/models/widget-component.models'; -import { DataKey, Datasource, WidgetActionDescriptor, WidgetConfig } from '@shared/models/widget.models'; +import { DataKey, WidgetActionDescriptor, WidgetConfig } from '@shared/models/widget.models'; import { IWidgetSubscription } from '@core/api/widget-api.models'; import { UtilsService } from '@core/services/utils.service'; import { TranslateService } from '@ngx-translate/core'; @@ -394,7 +394,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, this.displayedColumns.push(...this.columns.map(column => column.def)); } if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) { - this.defaultSortOrder = this.settings.defaultSortOrder; + this.defaultSortOrder = this.utils.customTranslation(this.settings.defaultSortOrder, this.settings.defaultSortOrder); } this.pageLink.sortOrder = entityDataSortOrderFromString(this.defaultSortOrder, this.columns); let sortColumn: EntityColumn; @@ -959,7 +959,7 @@ class AlarmsDatasource implements DataSource { } } } - alarm[dataKey.name] = value; + alarm[dataKey.label] = value; }); return alarm; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts index 9e079bc247..1ab9ea6fc4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts @@ -189,7 +189,7 @@ export function getAlarmValue(alarm: AlarmDataInfo, key: EntityColumn) { if (alarmField) { return getDescendantProp(alarm, alarmField.value); } else { - return getDescendantProp(alarm, key.name); + return getDescendantProp(alarm, key.label); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts index ebf5387862..1b820ab722 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts @@ -524,16 +524,22 @@ class TimeseriesDatasource implements DataSource { }); } - const rows: TimeseriesRow[] = []; - - for (const value of Object.values(rowsMap)) { - if (this.hideEmptyLines && isDefinedAndNotNull(value[1])) { - rows.push(value); - } else { - rows.push(value); + let rows: TimeseriesRow[] = []; + if (this.hideEmptyLines) { + for (const t of Object.keys(rowsMap)) { + let hideLine = true; + for (let c = 0; (c < data.length) && hideLine; c++) { + if (rowsMap[t][c + 1]) { + hideLine = false; + } + } + if (!hideLine) { + rows.push(rowsMap[t]); + } } + } else { + rows = Object.values(rowsMap); } - return rows; } From ce591f9b38deebb6a0ef99d7ca53af98e934eb8a Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 20 Oct 2020 08:50:31 +0300 Subject: [PATCH 21/23] Refactoring --- .../org/thingsboard/mqtt/MqttClientImpl.java | 47 ++++++++++++++----- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java index c493f2b860..f72b8d0f4e 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java @@ -19,18 +19,39 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableSet; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; -import io.netty.channel.*; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; -import io.netty.handler.codec.mqtt.*; +import io.netty.handler.codec.mqtt.MqttDecoder; +import io.netty.handler.codec.mqtt.MqttEncoder; +import io.netty.handler.codec.mqtt.MqttFixedHeader; +import io.netty.handler.codec.mqtt.MqttMessage; +import io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader; +import io.netty.handler.codec.mqtt.MqttMessageType; +import io.netty.handler.codec.mqtt.MqttPublishMessage; +import io.netty.handler.codec.mqtt.MqttPublishVariableHeader; +import io.netty.handler.codec.mqtt.MqttQoS; +import io.netty.handler.codec.mqtt.MqttSubscribeMessage; +import io.netty.handler.codec.mqtt.MqttSubscribePayload; +import io.netty.handler.codec.mqtt.MqttTopicSubscription; +import io.netty.handler.codec.mqtt.MqttUnsubscribeMessage; +import io.netty.handler.codec.mqtt.MqttUnsubscribePayload; import io.netty.handler.ssl.SslContext; import io.netty.handler.timeout.IdleStateHandler; -import io.netty.util.collection.IntObjectHashMap; import io.netty.util.concurrent.DefaultPromise; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Promise; -import java.util.*; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -41,11 +62,11 @@ import java.util.concurrent.atomic.AtomicInteger; final class MqttClientImpl implements MqttClient { private final Set serverSubscriptions = new HashSet<>(); - private final IntObjectHashMap pendingServerUnsubscribes = new IntObjectHashMap<>(); - private final IntObjectHashMap qos2PendingIncomingPublishes = new IntObjectHashMap<>(); - private final IntObjectHashMap pendingPublishes = new IntObjectHashMap<>(); + private final ConcurrentHashMap pendingServerUnsubscribes = new ConcurrentHashMap<>(); + private final ConcurrentHashMap qos2PendingIncomingPublishes = new ConcurrentHashMap<>(); + private final ConcurrentHashMap pendingPublishes = new ConcurrentHashMap<>(); private final HashMultimap subscriptions = HashMultimap.create(); - private final IntObjectHashMap pendingSubscriptions = new IntObjectHashMap<>(); + private final ConcurrentHashMap pendingSubscriptions = new ConcurrentHashMap<>(); private final Set pendingSubscribeTopics = new HashSet<>(); private final HashMultimap handlerToSubscribtion = HashMultimap.create(); private final AtomicInteger nextMessageId = new AtomicInteger(1); @@ -355,6 +376,8 @@ final class MqttClientImpl implements MqttClient { pendingPublish.getFuture().setSuccess(null); //We don't get an ACK for QOS 0 } else if (pendingPublish.isSent()) { pendingPublish.startPublishRetransmissionTimer(this.eventLoop.next(), this::sendAndFlushPacket); + } else { + this.pendingPublishes.remove(pendingPublish.getMessageId()); } return future; } @@ -466,7 +489,7 @@ final class MqttClientImpl implements MqttClient { } } - IntObjectHashMap getPendingSubscriptions() { + ConcurrentHashMap getPendingSubscriptions() { return pendingSubscriptions; } @@ -486,15 +509,15 @@ final class MqttClientImpl implements MqttClient { return serverSubscriptions; } - IntObjectHashMap getPendingServerUnsubscribes() { + ConcurrentHashMap getPendingServerUnsubscribes() { return pendingServerUnsubscribes; } - IntObjectHashMap getPendingPublishes() { + ConcurrentHashMap getPendingPublishes() { return pendingPublishes; } - IntObjectHashMap getQos2PendingIncomingPublishes() { + ConcurrentHashMap getQos2PendingIncomingPublishes() { return qos2PendingIncomingPublishes; } From cbcb050e81d4004c4369350551def65dd66e92fa Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 20 Oct 2020 09:14:58 +0300 Subject: [PATCH 22/23] Refactoring --- .../components/widget/lib/alarms-table-widget.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts index 58176a48d1..3e4b37dc79 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts @@ -371,7 +371,8 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, this.subscription.alarmSource.dataKeys.forEach((alarmDataKey) => { const dataKey: EntityColumn = deepClone(alarmDataKey) as EntityColumn; dataKey.entityKey = dataKeyToEntityKey(alarmDataKey); - dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label); + dataKey.label = this.utils.customTranslation(dataKey.label, dataKey.label); + dataKey.title = dataKey.label; dataKey.def = 'def' + this.columns.length; const keySettings: TableWidgetDataKeySettings = dataKey.settings; if (dataKey.type === DataKeyType.alarm && !isDefined(keySettings.columnWidth)) { From 444af2e57b4cfc63a1229e1a2621de1a9a5d8414 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Wed, 21 Oct 2020 11:25:14 +0300 Subject: [PATCH 23/23] Refactoring --- .../org/thingsboard/mqtt/MqttClientImpl.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java index f72b8d0f4e..289f73dc52 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java @@ -52,6 +52,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -62,11 +63,11 @@ import java.util.concurrent.atomic.AtomicInteger; final class MqttClientImpl implements MqttClient { private final Set serverSubscriptions = new HashSet<>(); - private final ConcurrentHashMap pendingServerUnsubscribes = new ConcurrentHashMap<>(); - private final ConcurrentHashMap qos2PendingIncomingPublishes = new ConcurrentHashMap<>(); - private final ConcurrentHashMap pendingPublishes = new ConcurrentHashMap<>(); + private final ConcurrentMap pendingServerUnsubscribes = new ConcurrentHashMap<>(); + private final ConcurrentMap qos2PendingIncomingPublishes = new ConcurrentHashMap<>(); + private final ConcurrentMap pendingPublishes = new ConcurrentHashMap<>(); private final HashMultimap subscriptions = HashMultimap.create(); - private final ConcurrentHashMap pendingSubscriptions = new ConcurrentHashMap<>(); + private final ConcurrentMap pendingSubscriptions = new ConcurrentHashMap<>(); private final Set pendingSubscribeTopics = new HashSet<>(); private final HashMultimap handlerToSubscribtion = HashMultimap.create(); private final AtomicInteger nextMessageId = new AtomicInteger(1); @@ -489,7 +490,7 @@ final class MqttClientImpl implements MqttClient { } } - ConcurrentHashMap getPendingSubscriptions() { + ConcurrentMap getPendingSubscriptions() { return pendingSubscriptions; } @@ -509,15 +510,15 @@ final class MqttClientImpl implements MqttClient { return serverSubscriptions; } - ConcurrentHashMap getPendingServerUnsubscribes() { + ConcurrentMap getPendingServerUnsubscribes() { return pendingServerUnsubscribes; } - ConcurrentHashMap getPendingPublishes() { + ConcurrentMap getPendingPublishes() { return pendingPublishes; } - ConcurrentHashMap getQos2PendingIncomingPublishes() { + ConcurrentMap getQos2PendingIncomingPublishes() { return qos2PendingIncomingPublishes; }