From 64f4b586a90bf549b6aec5839b04ec5ad9de660d Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Mon, 19 Feb 2024 15:44:45 +0200 Subject: [PATCH 01/14] Add rate limits for edge uplink message --- .../server/service/edge/EdgeContextComponent.java | 4 ++++ .../server/service/edge/rpc/EdgeGrpcSession.java | 9 +++++++++ .../server/service/limits/RateLimitServiceTest.java | 6 +++++- .../thingsboard/server/common/data/limit/LimitedApi.java | 2 ++ .../profile/DefaultTenantProfileConfiguration.java | 2 ++ .../default-tenant-profile-configuration.component.html | 8 ++++++++ .../default-tenant-profile-configuration.component.ts | 4 +++- .../profile/tenant/rate-limits/rate-limits.models.ts | 8 +++++++- ui-ngx/src/app/shared/models/limited-api.models.ts | 6 +++++- ui-ngx/src/assets/help/en_US/notification/rate_limits.md | 2 +- ui-ngx/src/assets/locale/locale.constant-en_US.json | 8 +++++++- 11 files changed, 53 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java index 2f157af70e..f5b769e568 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java @@ -19,6 +19,7 @@ import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.dao.asset.AssetProfileService; @@ -150,6 +151,9 @@ public class EdgeContextComponent { @Autowired private ResourceService resourceService; + @Autowired + private RateLimitService rateLimitService; + @Autowired private NotificationRuleProcessor notificationRuleProcessor; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index e5038d3b3a..7a5d94bcdb 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -27,6 +27,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.data.util.Pair; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.EdgeId; @@ -35,11 +36,13 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.limit.LimitedApi; import org.thingsboard.server.common.data.notification.rule.trigger.EdgeCommunicationFailureTrigger; 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.msg.tools.TbRateLimitsException; import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; @@ -264,6 +267,12 @@ public final class EdgeGrpcSession implements Closeable { } private void onUplinkMsg(UplinkMsg uplinkMsg) { + if (!ctx.getRateLimitService().checkRateLimit(LimitedApi.EDGE_UPLINK_MESSAGES, tenantId)) { + throw new TbRateLimitsException(EntityType.TENANT); + } + if (!ctx.getRateLimitService().checkRateLimit(LimitedApi.EDGE_UPLINK_MESSAGES_PER_EDGE, tenantId, edge.getId())) { + throw new TbRateLimitsException(EntityType.EDGE); + } ListenableFuture> future = processUplinkMsg(uplinkMsg); Futures.addCallback(future, new FutureCallback<>() { @Override diff --git a/application/src/test/java/org/thingsboard/server/service/limits/RateLimitServiceTest.java b/application/src/test/java/org/thingsboard/server/service/limits/RateLimitServiceTest.java index 2570b80f9f..95a2c63f52 100644 --- a/application/src/test/java/org/thingsboard/server/service/limits/RateLimitServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/limits/RateLimitServiceTest.java @@ -71,6 +71,8 @@ public class RateLimitServiceTest { profileConfiguration.setCassandraQueryTenantRateLimitsConfiguration(rateLimit); profileConfiguration.setEdgeEventRateLimits(rateLimit); profileConfiguration.setEdgeEventRateLimitsPerEdge(rateLimit); + profileConfiguration.setEdgeUplinkMessagesRateLimits(rateLimit); + profileConfiguration.setEdgeUplinkMessagesRateLimitsPerEdge(rateLimit); updateTenantProfileConfiguration(profileConfiguration); for (LimitedApi limitedApi : List.of( @@ -80,7 +82,9 @@ public class RateLimitServiceTest { LimitedApi.REST_REQUESTS_PER_CUSTOMER, LimitedApi.CASSANDRA_QUERIES, LimitedApi.EDGE_EVENTS, - LimitedApi.EDGE_EVENTS_PER_EDGE + LimitedApi.EDGE_EVENTS_PER_EDGE, + LimitedApi.EDGE_UPLINK_MESSAGES, + LimitedApi.EDGE_UPLINK_MESSAGES_PER_EDGE )) { testRateLimits(limitedApi, max, tenantId); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/limit/LimitedApi.java b/common/data/src/main/java/org/thingsboard/server/common/data/limit/LimitedApi.java index 980eb880a0..1a0f038c90 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/limit/LimitedApi.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/limit/LimitedApi.java @@ -33,6 +33,8 @@ public enum LimitedApi { CASSANDRA_QUERIES(DefaultTenantProfileConfiguration::getCassandraQueryTenantRateLimitsConfiguration, "Cassandra queries", true), EDGE_EVENTS(DefaultTenantProfileConfiguration::getEdgeEventRateLimits, "Edge events", true), EDGE_EVENTS_PER_EDGE(DefaultTenantProfileConfiguration::getEdgeEventRateLimitsPerEdge, "Edge events per edge", false), + EDGE_UPLINK_MESSAGES(DefaultTenantProfileConfiguration::getEdgeUplinkMessagesRateLimits, "Edge uplink messages", true), + EDGE_UPLINK_MESSAGES_PER_EDGE(DefaultTenantProfileConfiguration::getEdgeUplinkMessagesRateLimitsPerEdge, "Edge uplink messages per edge", false), PASSWORD_RESET(false, true), TWO_FA_VERIFICATION_CODE_SEND(false, true), TWO_FA_VERIFICATION_CODE_CHECK(false, true), diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java index 3fe110de0b..a0cfbfe1e1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java @@ -83,6 +83,8 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura private String edgeEventRateLimits; private String edgeEventRateLimitsPerEdge; + private String edgeUplinkMessagesRateLimits; + private String edgeUplinkMessagesRateLimitsPerEdge; private int defaultStorageTtlDays; private int alarmsTtlDays; diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html index 867cd74f3b..ef024fe0a2 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html @@ -571,6 +571,14 @@ [type]="rateLimitsType.EDGE_EVENTS_PER_EDGE_RATE_LIMIT"> +
+ + + + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts index 0c95324d85..c13eed9c03 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts @@ -108,7 +108,9 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA wsUpdatesPerSessionRateLimit: [null, []], cassandraQueryTenantRateLimitsConfiguration: [null, []], edgeEventRateLimits: [null, []], - edgeEventRateLimitsPerEdge: [null, []] + edgeEventRateLimitsPerEdge: [null, []], + edgeUplinkMessagesRateLimits: [null, []], + edgeUplinkMessagesRateLimitsPerEdge: [null, []] }); this.defaultTenantProfileConfigurationFormGroup.get('smsEnabled').valueChanges.pipe( diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts index 8924682aa1..ee13ea9b23 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts @@ -37,7 +37,9 @@ export enum RateLimitsType { TENANT_NOTIFICATION_REQUEST_RATE_LIMIT = 'TENANT_NOTIFICATION_REQUEST_RATE_LIMIT', TENANT_NOTIFICATION_REQUESTS_PER_RULE_RATE_LIMIT = 'TENANT_NOTIFICATION_REQUESTS_PER_RULE_RATE_LIMIT', EDGE_EVENTS_RATE_LIMIT = 'EDGE_EVENTS_RATE_LIMIT', - EDGE_EVENTS_PER_EDGE_RATE_LIMIT = 'EDGE_EVENTS_PER_EDGE_RATE_LIMIT' + EDGE_EVENTS_PER_EDGE_RATE_LIMIT = 'EDGE_EVENTS_PER_EDGE_RATE_LIMIT', + EDGE_UPLINK_MESSAGES_RATE_LIMIT = 'EDGE_UPLINK_MESSAGES_RATE_LIMIT', + EDGE_UPLINK_MESSAGES_PER_EDGE_RATE_LIMIT = 'EDGE_UPLINK_MESSAGES_PER_EDGE_RATE_LIMIT' } export const rateLimitsLabelTranslationMap = new Map( @@ -58,6 +60,8 @@ export const rateLimitsLabelTranslationMap = new Map( [RateLimitsType.TENANT_NOTIFICATION_REQUESTS_PER_RULE_RATE_LIMIT, 'tenant-profile.tenant-notification-requests-per-rule-rate-limit'], [RateLimitsType.EDGE_EVENTS_RATE_LIMIT, 'tenant-profile.rate-limits.edge-events-rate-limit'], [RateLimitsType.EDGE_EVENTS_PER_EDGE_RATE_LIMIT, 'tenant-profile.rate-limits.edge-events-per-edge-rate-limit'], + [RateLimitsType.EDGE_UPLINK_MESSAGES_RATE_LIMIT, 'tenant-profile.rate-limits.edge-uplink-messages-rate-limit'], + [RateLimitsType.EDGE_UPLINK_MESSAGES_PER_EDGE_RATE_LIMIT, 'tenant-profile.rate-limits.edge-uplink-messages-per-edge-rate-limit'], ] ); @@ -79,6 +83,8 @@ export const rateLimitsDialogTitleTranslationMap = new Map( @@ -43,5 +45,7 @@ export const LimitedApiTranslationMap = new Map( [LimitedApi.TRANSPORT_MESSAGES_PER_DEVICE, 'api-limit.transport-messages-per-device'], [LimitedApi.EDGE_EVENTS, 'api-limit.edge-events'], [LimitedApi.EDGE_EVENTS_PER_EDGE, 'api-limit.edge-events-per-edge'], + [LimitedApi.EDGE_UPLINK_MESSAGES, 'api-limit.edge-uplink-messages'], + [LimitedApi.EDGE_UPLINK_MESSAGES_PER_EDGE, 'api-limit.edge-uplink-messages-per-edge'] ] ); diff --git a/ui-ngx/src/assets/help/en_US/notification/rate_limits.md b/ui-ngx/src/assets/help/en_US/notification/rate_limits.md index d314e681c2..c17610f39a 100644 --- a/ui-ngx/src/assets/help/en_US/notification/rate_limits.md +++ b/ui-ngx/src/assets/help/en_US/notification/rate_limits.md @@ -11,7 +11,7 @@ Available template parameters: * `api` - rate-limited API label; one of: 'REST API requests', 'REST API requests per customer', 'transport messages', 'transport messages per device', 'Cassandra queries', 'WS updates per session', 'notification requests', 'notification requests per rule', - 'entity version creation', 'entity version load', 'Edge events', 'Edge events per edge'; + 'entity version creation', 'entity version load', 'Edge events', 'Edge events per edge', 'Edge uplink messages', 'Edge uplink messages per edge'; * `limitLevelEntityType` - entity type of the limit level entity, e.g. 'Tenant', 'Device', 'Notification rule', 'Customer', etc.; * `limitLevelEntityId` - id of the limit level entity; * `limitLevelEntityName` - name of the limit level entity; 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 288db8c9d2..122bf74d29 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -861,7 +861,9 @@ "transport-messages-per-device": "Transport messages per device", "ws-updates-per-session": "WS updates per session", "edge-events": "Edge events", - "edge-events-per-edge": "Edge events per edge" + "edge-events-per-edge": "Edge events per edge", + "edge-uplink-messages": "Edge uplink messages", + "edge-uplink-messages-per-edge": "Edge uplink messages per edge" }, "audit-log": { "audit": "Audit", @@ -4209,6 +4211,10 @@ "edit-edge-events-per-edge-rate-limit": "Edit edge events per edge rate limits", "edge-events-rate-limit": "Edge events", "edge-events-per-edge-rate-limit": "Edge events per edge", + "edit-edge-uplink-messages-rate-limit": "Edit edge uplink messages rate limits", + "edit-edge-uplink-messages-per-edge-rate-limit": "Edit edge uplink messages per edge rate limits", + "edge-uplink-messages-rate-limit": "Edge uplink messages", + "edge-uplink-messages-per-edge-rate-limit": "Edge uplink messages per edge", "messages-per": "messages per", "not-set": "Not set", "number-of-messages": "Number of messages", From 7d634c2ee0f4648ff62c1e4a6d6e7ffd0fb1d5c5 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Tue, 20 Feb 2024 14:37:06 +0200 Subject: [PATCH 02/14] Improve handling rate limit and send response of failure to edge --- .../service/edge/rpc/EdgeGrpcSession.java | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index 7a5d94bcdb..86a5af2a61 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -27,7 +27,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.data.util.Pair; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EdgeUtils; -import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.EdgeId; @@ -42,7 +41,6 @@ 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.msg.tools.TbRateLimitsException; import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; @@ -112,6 +110,7 @@ public final class EdgeGrpcSession implements Closeable { private static final String QUEUE_START_TS_ATTR_KEY = "queueStartTs"; private static final String QUEUE_START_SEQ_ID_ATTR_KEY = "queueStartSeqId"; + private static final String RATE_LIMIT_REACHED = "Rate limit reached"; private final UUID sessionId; private final BiConsumer sessionOpenListener; @@ -267,42 +266,51 @@ public final class EdgeGrpcSession implements Closeable { } private void onUplinkMsg(UplinkMsg uplinkMsg) { - if (!ctx.getRateLimitService().checkRateLimit(LimitedApi.EDGE_UPLINK_MESSAGES, tenantId)) { - throw new TbRateLimitsException(EntityType.TENANT); - } - if (!ctx.getRateLimitService().checkRateLimit(LimitedApi.EDGE_UPLINK_MESSAGES_PER_EDGE, tenantId, edge.getId())) { - throw new TbRateLimitsException(EntityType.EDGE); + if (isRateLimitViolated(uplinkMsg)) { + return; } ListenableFuture> future = processUplinkMsg(uplinkMsg); Futures.addCallback(future, new FutureCallback<>() { @Override public void onSuccess(@Nullable List result) { - UplinkResponseMsg uplinkResponseMsg = UplinkResponseMsg.newBuilder() - .setUplinkMsgId(uplinkMsg.getUplinkMsgId()) - .setSuccess(true).build(); - sendDownlinkMsg(ResponseMsg.newBuilder() - .setUplinkResponseMsg(uplinkResponseMsg) - .build()); + sendResponseMessage(uplinkMsg.getUplinkMsgId(), true, null); } @Override public void onFailure(Throwable t) { String errorMsg = EdgeUtils.createErrorMsgFromRootCauseAndStackTrace(t); - UplinkResponseMsg uplinkResponseMsg = UplinkResponseMsg.newBuilder() - .setUplinkMsgId(uplinkMsg.getUplinkMsgId()) - .setSuccess(false).setErrorMsg(errorMsg).build(); - sendDownlinkMsg(ResponseMsg.newBuilder() - .setUplinkResponseMsg(uplinkResponseMsg) - .build()); + sendResponseMessage(uplinkMsg.getUplinkMsgId(), false, errorMsg); } }, ctx.getGrpcCallbackExecutorService()); } + private boolean isRateLimitViolated(UplinkMsg uplinkMsg) { + if (!ctx.getRateLimitService().checkRateLimit(LimitedApi.EDGE_UPLINK_MESSAGES, tenantId) || + !ctx.getRateLimitService().checkRateLimit(LimitedApi.EDGE_UPLINK_MESSAGES_PER_EDGE, tenantId, edge.getId())) { + String errorMsg = String.format("Failed to process uplink message. %s", RATE_LIMIT_REACHED); + sendResponseMessage(uplinkMsg.getUplinkMsgId(), false, errorMsg); + return true; + } + return false; + } + + private void sendResponseMessage(int uplinkMsgId, boolean success, String errorMsg) { + UplinkResponseMsg.Builder responseBuilder = UplinkResponseMsg.newBuilder() + .setUplinkMsgId(uplinkMsgId) + .setSuccess(success); + if (errorMsg != null) { + responseBuilder.setErrorMsg(errorMsg); + } + sendDownlinkMsg(ResponseMsg.newBuilder() + .setUplinkResponseMsg(responseBuilder.build()) + .build()); + } + private void onDownlinkResponse(DownlinkResponseMsg msg) { try { if (msg.getSuccess()) { sessionState.getPendingMsgsMap().remove(msg.getDownlinkMsgId()); - log.debug("[{}][{}] Msg has been processed successfully!Msg Id: [{}], Msg: {}", this.tenantId, edge.getRoutingKey(), msg.getDownlinkMsgId(), msg); + log.debug("[{}][{}] Msg has been processed successfully! Msg Id: [{}], Msg: {}", this.tenantId, edge.getRoutingKey(), msg.getDownlinkMsgId(), msg); } else { log.error("[{}][{}] Msg processing failed! Msg Id: [{}], Error msg: {}", this.tenantId, edge.getRoutingKey(), msg.getDownlinkMsgId(), msg.getErrorMsg()); } From 076ccbba97084386f7e698e4c1ffde4c235fc9e1 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Tue, 27 Feb 2024 13:59:32 +0200 Subject: [PATCH 03/14] Open default alarm dashboard if none set for mobile notifications --- .../MobileAppNotificationChannel.java | 26 +++++++++++++------ .../AlarmAssignmentTriggerProcessor.java | 1 + .../trigger/AlarmCommentTriggerProcessor.java | 1 + .../rule/trigger/AlarmTriggerProcessor.java | 3 ++- .../server/common/data/alarm/Alarm.java | 10 +++++++ .../data/notification/NotificationType.java | 1 + .../info/AlarmAssignmentNotificationInfo.java | 7 +++++ .../info/AlarmCommentNotificationInfo.java | 7 +++++ .../info/AlarmNotificationInfo.java | 7 +++++ .../notification/info/NotificationInfo.java | 5 ++++ 10 files changed, 59 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java index 3f2fcd40f5..8b90a8c69c 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java @@ -61,14 +61,7 @@ public class MobileAppNotificationChannel implements NotificationChannel data = Optional.ofNullable(processedTemplate.getAdditionalConfig()) - .map(JacksonUtil::toFlatMap).orElseGet(HashMap::new); - Optional.ofNullable(ctx.getRequest().getInfo()) - .map(NotificationInfo::getStateEntityId) - .ifPresent(stateEntityId -> { - data.put("stateEntityId", stateEntityId.getId().toString()); - data.put("stateEntityType", stateEntityId.getEntityType().name()); - }); + Map data = getNotificationData(processedTemplate, ctx); for (String token : mobileSessions.keySet()) { try { firebaseService.sendMessage(ctx.getTenantId(), credentials, token, subject, body, data); @@ -87,6 +80,23 @@ public class MobileAppNotificationChannel implements NotificationChannel getNotificationData(MobileAppDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) { + Map data = Optional.ofNullable(processedTemplate.getAdditionalConfig()) + .map(JacksonUtil::toFlatMap).orElseGet(HashMap::new); + NotificationInfo info = ctx.getRequest().getInfo(); + Optional.ofNullable(info).map(NotificationInfo::getStateEntityId).ifPresent(stateEntityId -> { + data.put("stateEntityId", stateEntityId.getId().toString()); + data.put("stateEntityType", stateEntityId.getEntityType().name()); + if (!"true".equals(data.get("onClick.enabled")) && info.getDashboardId() != null) { + data.put("onClick.enabled", "true"); + data.put("onClick.linkType", "DASHBOARD"); + data.put("onClick.setEntityIdInState", "true"); + data.put("onClick.dashboardId", info.getDashboardId().toString()); + } + }); + return data; + } + @Override public void check(TenantId tenantId) throws Exception { NotificationSettings systemSettings = notificationSettingsService.findNotificationSettings(TenantId.SYS_TENANT_ID); diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java index 98981d0bc0..9cc7416e34 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java @@ -65,6 +65,7 @@ public class AlarmAssignmentTriggerProcessor implements NotificationRuleTriggerP .alarmSeverity(alarmInfo.getSeverity()) .alarmStatus(alarmInfo.getStatus()) .alarmCustomerId(alarmInfo.getCustomerId()) + .dashboardId(alarmInfo.getDashboardId()) .build(); } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java index a1199214ae..392f68dc9e 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java @@ -76,6 +76,7 @@ public class AlarmCommentTriggerProcessor implements NotificationRuleTriggerProc .alarmSeverity(alarm.getSeverity()) .alarmStatus(alarm.getStatus()) .alarmCustomerId(alarm.getCustomerId()) + .dashboardId(alarm.getDashboardId()) .build(); } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java index 8022f6cbb3..99d58d40a9 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java @@ -22,11 +22,11 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmStatusFilter; import org.thingsboard.server.common.data.notification.info.AlarmNotificationInfo; import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; +import org.thingsboard.server.common.data.notification.rule.trigger.AlarmTrigger; import org.thingsboard.server.common.data.notification.rule.trigger.config.AlarmNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.AlarmNotificationRuleTriggerConfig.AlarmAction; import org.thingsboard.server.common.data.notification.rule.trigger.config.AlarmNotificationRuleTriggerConfig.ClearRule; import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; -import org.thingsboard.server.common.data.notification.rule.trigger.AlarmTrigger; import static org.apache.commons.collections.CollectionUtils.isNotEmpty; import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains; @@ -111,6 +111,7 @@ public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor implements HasName, HasTenantId, Ha } } + @JsonIgnore + public DashboardId getDashboardId() { + return Optional.ofNullable(getDetails()).map(details -> details.get("dashboardId")) + .filter(JsonNode::isTextual).map(id -> new DashboardId(UUID.fromString(id.asText()))).orElse(null); + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java index cfc91d8fca..5a8fd87933 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java @@ -31,4 +31,5 @@ public enum NotificationType { RATE_LIMITS, EDGE_CONNECTION, EDGE_COMMUNICATION_FAILURE + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java index c2b12cc17c..13567405d8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.UserId; @@ -55,6 +56,7 @@ public class AlarmAssignmentNotificationInfo implements RuleOriginatedNotificati private AlarmSeverity alarmSeverity; private AlarmStatus alarmStatus; private CustomerId alarmCustomerId; + private DashboardId dashboardId; @Override public Map getTemplateData() { @@ -94,4 +96,9 @@ public class AlarmAssignmentNotificationInfo implements RuleOriginatedNotificati return alarmOriginator; } + @Override + public DashboardId getDashboardId() { + return dashboardId; + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java index aed023084f..d819b42c3c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.EntityId; import java.util.Map; @@ -50,6 +51,7 @@ public class AlarmCommentNotificationInfo implements RuleOriginatedNotificationI private AlarmSeverity alarmSeverity; private AlarmStatus alarmStatus; private CustomerId alarmCustomerId; + private DashboardId dashboardId; @Override public Map getTemplateData() { @@ -80,4 +82,9 @@ public class AlarmCommentNotificationInfo implements RuleOriginatedNotificationI return alarmOriginator; } + @Override + public DashboardId getDashboardId() { + return dashboardId; + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmNotificationInfo.java index 13b8c2e3ea..fb7fa478ea 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmNotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmNotificationInfo.java @@ -22,6 +22,7 @@ import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.EntityId; import java.util.Map; @@ -45,6 +46,7 @@ public class AlarmNotificationInfo implements RuleOriginatedNotificationInfo { private boolean acknowledged; private boolean cleared; private CustomerId alarmCustomerId; + private DashboardId dashboardId; @Override public Map getTemplateData() { @@ -70,4 +72,9 @@ public class AlarmNotificationInfo implements RuleOriginatedNotificationInfo { return alarmOriginator; } + @Override + public DashboardId getDashboardId() { + return dashboardId; + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NotificationInfo.java index 14d2a876c0..50e3f05e6d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NotificationInfo.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.notification.info; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.EntityId; import java.util.Map; @@ -33,4 +34,8 @@ public interface NotificationInfo { return null; } + default DashboardId getDashboardId() { + return null; + } + } From ff0ec47ae78b358e37068a1170f9d5b0362c3f32 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 27 Feb 2024 16:44:47 +0200 Subject: [PATCH 04/14] UI: Add hint for used default alarm dashboard in mobile push notification --- ...tion-action-button-configuration.component.html | 10 ++++++---- ...cation-action-button-configuration.component.ts | 3 +++ ...ification-template-configuration.component.html | 1 + ...otification-template-configuration.component.ts | 14 +++++++++++++- .../src/assets/locale/locale.constant-en_US.json | 1 + 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-action-button-configuration.component.html b/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-action-button-configuration.component.html index b195775d48..6ce736ead1 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-action-button-configuration.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-action-button-configuration.component.html @@ -20,10 +20,12 @@ [expanded]="actionButtonConfigForm.get('enabled').value"> - - {{ actionTitle }} - +
+ + {{ actionTitle }} + +
diff --git a/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-action-button-configuration.component.ts b/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-action-button-configuration.component.ts index af7e0a997b..e62d647694 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-action-button-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-action-button-configuration.component.ts @@ -52,6 +52,9 @@ export class NotificationActionButtonConfigurationComponent implements ControlVa @Input() actionTitle: string; + @Input() + sliderHint: string; + private hideButtonTextValue = false; get hideButtonText(): boolean { diff --git a/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html b/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html index 0a992618b9..54db7ece72 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html @@ -124,6 +124,7 @@ diff --git a/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.ts b/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.ts index 1f54784d64..2d575f593f 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.ts @@ -36,6 +36,7 @@ import { takeUntil } from 'rxjs/operators'; import { Subject } from 'rxjs'; import { isDefinedAndNotNull } from '@core/utils'; import { coerceBoolean } from '@shared/decorators/coercion'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'tb-template-configuration', @@ -96,7 +97,8 @@ export class NotificationTemplateConfigurationComponent implements OnDestroy, Co private propagateChange = (v: any) => { }; private readonly destroy$ = new Subject(); - constructor(private fb: FormBuilder) { + constructor(private fb: FormBuilder, + private translate: TranslateService) { this.templateConfigurationForm = this.buildForm(); this.templateConfigurationForm.valueChanges.pipe( takeUntil(this.destroy$) @@ -137,6 +139,16 @@ export class NotificationTemplateConfigurationComponent implements OnDestroy, Co }; } + get hotificationTapActionHint(): string { + switch (this.notificationType) { + case NotificationType.ALARM: + case NotificationType.ALARM_ASSIGNMENT: + case NotificationType.ALARM_COMMENT: + return this.translate.instant('notification.notification-tap-action-hint'); + } + return ''; + } + private updateDisabledForms(){ Object.values(NotificationDeliveryMethod).forEach((method) => { const form = this.templateConfigurationForm.get(method); 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 288db8c9d2..54a7ad15dc 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3349,6 +3349,7 @@ "notification": "Notification", "notification-center": "Notification center", "notification-tap-action": "Notification tap action", + "notification-tap-action-hint": "If not enabled, the default alarm dashboard will be used", "notify": "notify", "notify-again": "Notify again", "notify-alarm-action": { From ff37e7ede59d55653b40874776cc810c550eae3f Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 27 Feb 2024 17:16:38 +0200 Subject: [PATCH 05/14] UI: Added fixed width for embed image dialog --- .../shared/components/image/embed-image-dialog.component.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/ui-ngx/src/app/shared/components/image/embed-image-dialog.component.scss b/ui-ngx/src/app/shared/components/image/embed-image-dialog.component.scss index eeaff6e83c..045f51b808 100644 --- a/ui-ngx/src/app/shared/components/image/embed-image-dialog.component.scss +++ b/ui-ngx/src/app/shared/components/image/embed-image-dialog.component.scss @@ -16,6 +16,7 @@ :host { .mat-mdc-dialog-content { display: flex; + width: 780px; flex-direction: column; gap: 16px; .tb-embed-image-text { From 0c431c2129cc1eac8fafcaf2fb17e305398e61e7 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Wed, 28 Feb 2024 11:24:15 +0200 Subject: [PATCH 06/14] UI: fixed applying min/max y-axis parameters in flot chart --- .../app/modules/home/components/widget/lib/flot-widget.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts index 846c6b8331..2c46864388 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts @@ -206,8 +206,8 @@ export class TbFlot { if (this.settings.yaxis) { this.yaxis.font.color = this.settings.yaxis.color || this.yaxis.font.color; - this.yaxis.min = isDefined(this.settings.yaxis.min) ? this.settings.yaxis.min : null; - this.yaxis.max = isDefined(this.settings.yaxis.max) ? this.settings.yaxis.max : null; + this.yaxis.min = isNumber(this.settings.yaxis.min) ? this.settings.yaxis.min : null; + this.yaxis.max = isNumber(this.settings.yaxis.max) ? this.settings.yaxis.max : null; this.yaxis.label = this.utils.customTranslation(this.settings.yaxis.title, this.settings.yaxis.title) || null; this.yaxis.labelFont.color = this.yaxis.font.color; this.yaxis.labelFont.size = this.yaxis.font.size + 2; @@ -895,8 +895,8 @@ export class TbFlot { tickSize = yaxis.tickSize; } const position = keySettings.axisPosition && keySettings.axisPosition.length ? keySettings.axisPosition : 'left'; - const min = isDefined(keySettings.axisMin) ? keySettings.axisMin : yaxis.min; - const max = isDefined(keySettings.axisMax) ? keySettings.axisMax : yaxis.max; + const min = isNumber(keySettings.axisMin) ? keySettings.axisMin : yaxis.min; + const max = isNumber(keySettings.axisMax) ? keySettings.axisMax : yaxis.max; yaxis.label = label; yaxis.min = min; yaxis.max = max; From db975f45682cdcf002a1a04e94625e9abf9aea82 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Wed, 28 Feb 2024 12:54:05 +0200 Subject: [PATCH 07/14] Add alarm info to mobile notification data --- .../channels/MobileAppNotificationChannel.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java index 8b90a8c69c..dc7f3f9d99 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java @@ -84,7 +84,10 @@ public class MobileAppNotificationChannel implements NotificationChannel data = Optional.ofNullable(processedTemplate.getAdditionalConfig()) .map(JacksonUtil::toFlatMap).orElseGet(HashMap::new); NotificationInfo info = ctx.getRequest().getInfo(); - Optional.ofNullable(info).map(NotificationInfo::getStateEntityId).ifPresent(stateEntityId -> { + if (info == null) { + return data; + } + Optional.ofNullable(info.getStateEntityId()).ifPresent(stateEntityId -> { data.put("stateEntityId", stateEntityId.getId().toString()); data.put("stateEntityType", stateEntityId.getEntityType().name()); if (!"true".equals(data.get("onClick.enabled")) && info.getDashboardId() != null) { @@ -94,6 +97,16 @@ public class MobileAppNotificationChannel implements NotificationChannel { + data.put("info." + key, value); + }); + break; + } return data; } From de28def7bec08b34d1d8f0b176ee29774da6bb5a Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Tue, 27 Feb 2024 13:37:35 +0200 Subject: [PATCH 08/14] Fix JWT settings updating for Rule Engines --- .../server/controller/AdminController.java | 12 +++--------- .../service/queue/DefaultTbCoreConsumerService.java | 2 +- .../queue/DefaultTbRuleEngineConsumerService.java | 7 +++++-- .../queue/processing/AbstractConsumerService.java | 7 +++---- .../auth/jwt/settings/DefaultJwtSettingsService.java | 5 ++--- .../service/security/auth/JwtTokenFactoryTest.java | 1 - 6 files changed, 14 insertions(+), 20 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/AdminController.java b/application/src/main/java/org/thingsboard/server/controller/AdminController.java index 5fae72252f..ebb4bd3916 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AdminController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AdminController.java @@ -32,8 +32,6 @@ import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; @@ -52,13 +50,12 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.server.common.data.AdminSettings; -import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.FeaturesInfo; import org.thingsboard.server.common.data.FeaturesInfo; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.SystemInfo; import org.thingsboard.server.common.data.UpdateMessage; -import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; @@ -74,8 +71,8 @@ import org.thingsboard.server.common.data.sync.vc.VcUtils; import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.security.auth.oauth2.CookieUtils; import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; +import org.thingsboard.server.service.security.auth.oauth2.CookieUtils; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.permission.Operation; @@ -93,7 +90,6 @@ import java.io.IOException; import java.util.List; import java.util.Optional; -import static org.thingsboard.server.controller.ControllerConstants.*; import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; @@ -113,9 +109,7 @@ public class AdminController extends BaseController { private final SmsService smsService; private final AdminSettingsService adminSettingsService; private final SystemSecurityService systemSecurityService; - @Lazy private final JwtSettingsService jwtSettingsService; - @Lazy private final JwtTokenFactory tokenFactory; private final EntitiesVersionControlService versionControlService; private final TbAutoCommitSettingsService autoCommitSettingsService; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index f878209753..836c35aa85 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -171,7 +171,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService jwtSettingsService, + JwtSettingsService jwtSettingsService, NotificationSchedulerService notificationSchedulerService, NotificationRuleProcessor notificationRuleProcessor, TbImageService imageService) { diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java index afdd150a59..13de09bb2d 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java @@ -54,6 +54,7 @@ import org.thingsboard.server.service.queue.processing.AbstractConsumerService; import org.thingsboard.server.service.queue.ruleengine.TbRuleEngineConsumerContext; import org.thingsboard.server.service.queue.ruleengine.TbRuleEngineQueueConsumerManager; import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; +import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; import javax.annotation.PostConstruct; import java.util.ArrayList; @@ -85,9 +86,11 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< TbAssetProfileCache assetProfileCache, TbTenantProfileCache tenantProfileCache, TbApiUsageStateService apiUsageStateService, - PartitionService partitionService, ApplicationEventPublisher eventPublisher) { + PartitionService partitionService, + ApplicationEventPublisher eventPublisher, + JwtSettingsService jwtSettingsService) { super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, - eventPublisher, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer(), Optional.empty()); + eventPublisher, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer(), jwtSettingsService); this.ctx = ctx; this.tbDeviceRpcService = tbDeviceRpcService; this.queueService = queueService; 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 4a348167d0..26e23549e3 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 @@ -49,7 +49,6 @@ import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsServ import javax.annotation.PreDestroy; import java.util.List; -import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -75,14 +74,14 @@ public abstract class AbstractConsumerService> nfConsumer; - protected final Optional jwtSettingsService; + protected final JwtSettingsService jwtSettingsService; public AbstractConsumerService(ActorSystemContext actorContext, DataDecodingEncodingService encodingService, TbTenantProfileCache tenantProfileCache, TbDeviceProfileCache deviceProfileCache, TbAssetProfileCache assetProfileCache, TbApiUsageStateService apiUsageStateService, PartitionService partitionService, ApplicationEventPublisher eventPublisher, - TbQueueConsumer> nfConsumer, Optional jwtSettingsService) { + TbQueueConsumer> nfConsumer, JwtSettingsService jwtSettingsService) { this.actorContext = actorContext; this.encodingService = encodingService; this.tenantProfileCache = tenantProfileCache; @@ -181,7 +180,7 @@ public abstract class AbstractConsumerService tbClusterService; private final Optional notificationCenter; private final JwtSettingsValidator jwtSettingsValidator; @@ -107,11 +104,13 @@ public class DefaultJwtSettingsService implements JwtSettingsService { @Override public JwtSettings reloadJwtSettings() { + log.trace("Executing reloadJwtSettings"); return getJwtSettings(true); } @Override public JwtSettings getJwtSettings() { + log.trace("Executing getJwtSettings"); return getJwtSettings(false); } diff --git a/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java b/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java index d412fb2f84..2d39dd9905 100644 --- a/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java +++ b/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java @@ -37,7 +37,6 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.model.token.AccessJwtToken; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; -import org.thingsboard.server.service.security.model.token.RawAccessJwtToken; import java.util.Calendar; import java.util.Date; From b972fad5e073fb60131407e902a02d078e25d783 Mon Sep 17 00:00:00 2001 From: rusikv Date: Thu, 29 Feb 2024 16:50:11 +0200 Subject: [PATCH 09/14] UI: fixed RPC terminals not retrieving device name --- .../main/data/json/system/widget_types/rpc_debug_terminal.json | 2 +- .../main/data/json/system/widget_types/rpc_remote_shell.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/data/json/system/widget_types/rpc_debug_terminal.json b/application/src/main/data/json/system/widget_types/rpc_debug_terminal.json index eed625283a..0d3409a4b4 100644 --- a/application/src/main/data/json/system/widget_types/rpc_debug_terminal.json +++ b/application/src/main/data/json/system/widget_types/rpc_debug_terminal.json @@ -11,7 +11,7 @@ "resources": [], "templateHtml": "
", "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n\n", - "controllerScript": "var requestTimeout = 500;\nvar requestPersistent = false;\nvar persistentPollingInterval = 5000;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n if (self.ctx.settings.requestPersistent) {\n requestPersistent = self.ctx.settings.requestPersistent;\n }\n if (self.ctx.settings.persistentPollingInterval) {\n persistentPollingInterval = self.ctx.settings.persistentPollingInterval;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n var requestUUID = uuidv4();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var spaceIndex = localCommand.indexOf(' ');\n if (spaceIndex === -1 && !localCommand.length) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n } else {\n var params;\n if (spaceIndex === -1) {\n spaceIndex = localCommand.length;\n }\n var name = localCommand.substr(0, spaceIndex);\n var args = localCommand.substr(spaceIndex + 1);\n if (args.length) {\n try {\n params = JSON.parse(args);\n } catch (e) {\n params = args;\n }\n }\n performRpc(this, name, params, requestUUID);\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1:]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2:]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\n\nfunction performRpc(terminal, method, params, requestUUID) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout, requestPersistent, persistentPollingInterval, requestUUID).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\n\nfunction uuidv4() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\n \nself.onDestroy = function() {\n self.ctx.controlApi.completedCommand();\n}", + "controllerScript": "var requestTimeout = 500;\nvar requestPersistent = false;\nvar persistentPollingInterval = 5000;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetEntityName && subscription.targetEntityName.length) {\n deviceName = subscription.targetEntityName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n if (self.ctx.settings.requestPersistent) {\n requestPersistent = self.ctx.settings.requestPersistent;\n }\n if (self.ctx.settings.persistentPollingInterval) {\n persistentPollingInterval = self.ctx.settings.persistentPollingInterval;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n var requestUUID = uuidv4();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var spaceIndex = localCommand.indexOf(' ');\n if (spaceIndex === -1 && !localCommand.length) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n } else {\n var params;\n if (spaceIndex === -1) {\n spaceIndex = localCommand.length;\n }\n var name = localCommand.substr(0, spaceIndex);\n var args = localCommand.substr(spaceIndex + 1);\n if (args.length) {\n try {\n params = JSON.parse(args);\n } catch (e) {\n params = args;\n }\n }\n performRpc(this, name, params, requestUUID);\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1:]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2:]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\n\nfunction performRpc(terminal, method, params, requestUUID) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout, requestPersistent, persistentPollingInterval, requestUUID).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\n\nfunction uuidv4() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\n \nself.onDestroy = function() {\n self.ctx.controlApi.completedCommand();\n}", "settingsSchema": "", "dataKeySettingsSchema": "{}\n", "settingsDirective": "tb-rpc-terminal-widget-settings", diff --git a/application/src/main/data/json/system/widget_types/rpc_remote_shell.json b/application/src/main/data/json/system/widget_types/rpc_remote_shell.json index de9925a054..c02ce6b3d1 100644 --- a/application/src/main/data/json/system/widget_types/rpc_remote_shell.json +++ b/application/src/main/data/json/system/widget_types/rpc_remote_shell.json @@ -11,7 +11,7 @@ "resources": [], "templateHtml": "
", "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n", - "controllerScript": "var requestTimeout = 500;\nvar commandStatusPollingInterval = 200;\n\nvar welcome = 'Welcome to ThingsBoard RPC remote shell.\\n';\n\nvar terminal, rpcEnabled, simulated, deviceName, cwd;\nvar commandExecuting = false;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n rpcEnabled = subscription.rpcEnabled;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n } else {\n deviceName = 'Simulated';\n simulated = true;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n \n terminal = $('#device-terminal', self.ctx.$container).terminal(\n function (command) {\n if (command && command.trim().length) {\n try {\n if (simulated) {\n this.echo(command);\n } else {\n sendCommand(this, command);\n }\n } catch(e) {\n this.error(e + '');\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: false,\n enabled: rpcEnabled,\n prompt: rpcEnabled ? currentPrompt : '',\n name: 'shell',\n pauseEvents: false,\n keydown: function (e, term) {\n if ((e.which == 67 || e.which == 68) && e.ctrlKey) { // CTRL+C || CTRL+D\n if (commandExecuting) {\n terminateCommand(term);\n return false;\n }\n }\n },\n onInit: initTerm\n }\n );\n \n};\n\nfunction initTerm(terminal) {\n terminal.echo(welcome);\n if (!rpcEnabled) {\n terminal.error('Target device is not set!\\n');\n } else {\n terminal.echo('Current target device for RPC terminal: [[b;#fff;]' + deviceName + ']\\n');\n if (!simulated) {\n terminal.pause();\n getTermInfo(terminal,\n function (remoteTermInfo) {\n if (remoteTermInfo) {\n terminal.echo('Remote platform info:');\n terminal.echo('OS: [[b;#fff;]' + remoteTermInfo.platform + ']');\n if (remoteTermInfo.release) {\n terminal.echo('OS release: [[b;#fff;]' + remoteTermInfo.release + ']');\n }\n terminal.echo('\\r');\n } else {\n terminal.echo('[[;#f00;]Unable to get remote platform info.\\nDevice is not responding.]\\n');\n }\n terminal.resume();\n });\n }\n }\n}\n\nfunction currentPrompt(callback) {\n if (cwd) {\n callback('[[b;#2196f3;]' + deviceName +']: [[b;#8bc34a;]' + cwd +']> ');\n } else {\n callback('[[b;#8bc34a;]' + deviceName +']> ');\n }\n}\n\nfunction getTermInfo(terminal, callback) {\n self.ctx.controlApi.sendTwoWayCommand('getTermInfo', null, requestTimeout).subscribe(\n function (termInfo) {\n cwd = termInfo.cwd;\n if (callback) {\n callback(termInfo);\n } \n },\n function () {\n if (callback) {\n callback(null);\n }\n }\n );\n}\n\nfunction sendCommand(terminal, command) {\n terminal.pause();\n var sendCommandRequest = {\n command: command,\n cwd: cwd\n };\n self.ctx.controlApi.sendTwoWayCommand('sendCommand', sendCommandRequest, requestTimeout).subscribe(\n function (responseBody) {\n if (responseBody && responseBody.ok) {\n commandExecuting = true;\n setTimeout( pollCommandStatus.bind(null,terminal), commandStatusPollingInterval );\n } else {\n var error = responseBody ? responseBody.error : 'Unhandled error.';\n terminal.error(error);\n terminal.resume();\n }\n },\n function () {\n onRpcError(terminal);\n }\n );\n}\n\nfunction terminateCommand(terminal) {\n self.ctx.controlApi.sendTwoWayCommand('terminateCommand', null, requestTimeout).subscribe(\n function (responseBody) {\n if (!responseBody.ok) {\n commandExecuting = false;\n terminal.error(responseBody.error);\n terminal.resume();\n } \n },\n function () {\n onRpcError(terminal);\n }\n ); \n}\n\nfunction onRpcError(terminal) {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.resume();\n}\n\nfunction pollCommandStatus(terminal) {\n self.ctx.controlApi.sendTwoWayCommand('getCommandStatus', null, requestTimeout).subscribe(\n function (commandStatusResponse) {\n for (var i=0;i ');\n } else {\n callback('[[b;#8bc34a;]' + deviceName +']> ');\n }\n}\n\nfunction getTermInfo(terminal, callback) {\n self.ctx.controlApi.sendTwoWayCommand('getTermInfo', null, requestTimeout).subscribe(\n function (termInfo) {\n cwd = termInfo.cwd;\n if (callback) {\n callback(termInfo);\n } \n },\n function () {\n if (callback) {\n callback(null);\n }\n }\n );\n}\n\nfunction sendCommand(terminal, command) {\n terminal.pause();\n var sendCommandRequest = {\n command: command,\n cwd: cwd\n };\n self.ctx.controlApi.sendTwoWayCommand('sendCommand', sendCommandRequest, requestTimeout).subscribe(\n function (responseBody) {\n if (responseBody && responseBody.ok) {\n commandExecuting = true;\n setTimeout( pollCommandStatus.bind(null,terminal), commandStatusPollingInterval );\n } else {\n var error = responseBody ? responseBody.error : 'Unhandled error.';\n terminal.error(error);\n terminal.resume();\n }\n },\n function () {\n onRpcError(terminal);\n }\n );\n}\n\nfunction terminateCommand(terminal) {\n self.ctx.controlApi.sendTwoWayCommand('terminateCommand', null, requestTimeout).subscribe(\n function (responseBody) {\n if (!responseBody.ok) {\n commandExecuting = false;\n terminal.error(responseBody.error);\n terminal.resume();\n } \n },\n function () {\n onRpcError(terminal);\n }\n ); \n}\n\nfunction onRpcError(terminal) {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.resume();\n}\n\nfunction pollCommandStatus(terminal) {\n self.ctx.controlApi.sendTwoWayCommand('getCommandStatus', null, requestTimeout).subscribe(\n function (commandStatusResponse) {\n for (var i=0;i Date: Fri, 1 Mar 2024 12:41:25 +0200 Subject: [PATCH 10/14] UI: removed targetDeviceAliasIds from presistent RPC table default config --- .../data/json/system/widget_types/persistent_rpc_table.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/data/json/system/widget_types/persistent_rpc_table.json b/application/src/main/data/json/system/widget_types/persistent_rpc_table.json index 2dadf40b85..f50d1ac5e8 100644 --- a/application/src/main/data/json/system/widget_types/persistent_rpc_table.json +++ b/application/src/main/data/json/system/widget_types/persistent_rpc_table.json @@ -15,7 +15,7 @@ "settingsSchema": "", "dataKeySettingsSchema": "{}\n", "settingsDirective": "tb-persistent-table-widget-settings", - "defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"enableStickyAction\":true,\"enableFilter\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"enableStickyHeader\":true,\"displayColumns\":[\"rpcId\",\"messageType\",\"status\",\"method\",\"createdTime\",\"expirationTime\"],\"displayDetails\":true,\"defaultSortOrder\":\"-createdTime\",\"allowSendRequest\":true,\"allowDelete\":true},\"title\":\"Persistent RPC table\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px\"},\"targetDeviceAliasIds\":[]}" + "defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"enableStickyAction\":true,\"enableFilter\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"enableStickyHeader\":true,\"displayColumns\":[\"rpcId\",\"messageType\",\"status\",\"method\",\"createdTime\",\"expirationTime\"],\"displayDetails\":true,\"defaultSortOrder\":\"-createdTime\",\"allowSendRequest\":true,\"allowDelete\":true},\"title\":\"Persistent RPC table\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px\"}}" }, "externalId": null, "tags": [ From 19f5c427e3ebddaa7e9b5670deb9a176cf3ae71e Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 4 Mar 2024 10:04:33 +0200 Subject: [PATCH 11/14] UI: Fixed incorrect cache when open preview public image --- .../src/app/shared/components/image/image-dialog.component.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ui-ngx/src/app/shared/components/image/image-dialog.component.ts b/ui-ngx/src/app/shared/components/image/image-dialog.component.ts index b140c6bf32..40e7d2ec89 100644 --- a/ui-ngx/src/app/shared/components/image/image-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/image/image-dialog.component.ts @@ -66,7 +66,7 @@ export class ImageDialogComponent extends this.image = data.image; this.readonly = data.readonly; this.imagePreviewData = { - url: this.image.public ? this.image.publicLink : this.image.link + url: this.image.link }; } @@ -155,8 +155,6 @@ export class ImageDialogComponent extends let url; if (result.base64) { url = result.base64; - } else if (this.image.public) { - url = `${this.image.publicLink}?ts=${new Date().getTime()}`; } else { url = this.image.link; } From 02c9fe3f820b48dc2a20bcbc84405f07980af15c Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Mon, 4 Mar 2024 18:08:32 +0200 Subject: [PATCH 12/14] Deep copy of object before to inlineImageForEdge in order to correctly work with cached values --- .../rpc/constructor/asset/AssetMsgConstructorV1.java | 5 +++-- .../dashboard/DashboardMsgConstructorV1.java | 5 +++-- .../rpc/constructor/device/DeviceMsgConstructorV1.java | 5 +++-- .../rpc/constructor/widget/WidgetMsgConstructorV1.java | 10 ++++++---- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorV1.java index acdd3f506a..02318b1aaa 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorV1.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorV1.java @@ -63,7 +63,8 @@ public class AssetMsgConstructorV1 extends BaseAssetMsgConstructor { @Override public AssetProfileUpdateMsg constructAssetProfileUpdatedMsg(UpdateMsgType msgType, AssetProfile assetProfile) { - imageService.inlineImageForEdge(assetProfile); + AssetProfile copy = JacksonUtil.clone(assetProfile); + imageService.inlineImageForEdge(copy); AssetProfileUpdateMsg.Builder builder = AssetProfileUpdateMsg.newBuilder() .setMsgType(msgType) .setIdMSB(assetProfile.getId().getId().getMostSignificantBits()) @@ -81,7 +82,7 @@ public class AssetMsgConstructorV1 extends BaseAssetMsgConstructor { builder.setDescription(assetProfile.getDescription()); } if (assetProfile.getImage() != null) { - builder.setImage(ByteString.copyFrom(assetProfile.getImage().getBytes(StandardCharsets.UTF_8))); + builder.setImage(ByteString.copyFrom(copy.getImage().getBytes(StandardCharsets.UTF_8))); } if (assetProfile.getDefaultEdgeRuleChainId() != null) { builder.setDefaultRuleChainIdMSB(assetProfile.getDefaultEdgeRuleChainId().getId().getMostSignificantBits()) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV1.java index 3df0d6563f..f94b165e2d 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV1.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV1.java @@ -33,7 +33,8 @@ public class DashboardMsgConstructorV1 extends BaseDashboardMsgConstructor { @Override public DashboardUpdateMsg constructDashboardUpdatedMsg(UpdateMsgType msgType, Dashboard dashboard) { - imageService.inlineImagesForEdge(dashboard); + Dashboard copy = JacksonUtil.clone(dashboard); + imageService.inlineImagesForEdge(copy); DashboardUpdateMsg.Builder builder = DashboardUpdateMsg.newBuilder() .setMsgType(msgType) .setIdMSB(dashboard.getId().getId().getMostSignificantBits()) @@ -45,7 +46,7 @@ public class DashboardMsgConstructorV1 extends BaseDashboardMsgConstructor { builder.setAssignedCustomers(JacksonUtil.toString(dashboard.getAssignedCustomers())); } if (dashboard.getImage() != null) { - builder.setImage(dashboard.getImage()); + builder.setImage(copy.getImage()); } if (dashboard.getMobileOrder() != null) { builder.setMobileOrder(dashboard.getMobileOrder()); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV1.java index 35c07abc5a..3c6552f9d3 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV1.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV1.java @@ -95,7 +95,8 @@ public class DeviceMsgConstructorV1 extends BaseDeviceMsgConstructor { @Override public DeviceProfileUpdateMsg constructDeviceProfileUpdatedMsg(UpdateMsgType msgType, DeviceProfile deviceProfile) { - imageService.inlineImageForEdge(deviceProfile); + DeviceProfile copy = JacksonUtil.clone(deviceProfile); + imageService.inlineImageForEdge(copy); DeviceProfileUpdateMsg.Builder builder = DeviceProfileUpdateMsg.newBuilder() .setMsgType(msgType) .setIdMSB(deviceProfile.getId().getId().getMostSignificantBits()) @@ -120,7 +121,7 @@ public class DeviceMsgConstructorV1 extends BaseDeviceMsgConstructor { builder.setProvisionDeviceKey(deviceProfile.getProvisionDeviceKey()); } if (deviceProfile.getImage() != null) { - builder.setImage(ByteString.copyFrom(deviceProfile.getImage().getBytes(StandardCharsets.UTF_8))); + builder.setImage(ByteString.copyFrom(copy.getImage().getBytes(StandardCharsets.UTF_8))); } if (deviceProfile.getFirmwareId() != null) { builder.setFirmwareIdMSB(deviceProfile.getFirmwareId().getId().getMostSignificantBits()) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorV1.java index 92fdb32fee..068378bea6 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorV1.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorV1.java @@ -43,7 +43,8 @@ public class WidgetMsgConstructorV1 extends BaseWidgetMsgConstructor { @Override public WidgetsBundleUpdateMsg constructWidgetsBundleUpdateMsg(UpdateMsgType msgType, WidgetsBundle widgetsBundle, List widgets) { - imageService.inlineImageForEdge(widgetsBundle); + WidgetsBundle copy = JacksonUtil.clone(widgetsBundle); + imageService.inlineImageForEdge(copy); WidgetsBundleUpdateMsg.Builder builder = WidgetsBundleUpdateMsg.newBuilder() .setMsgType(msgType) .setIdMSB(widgetsBundle.getId().getId().getMostSignificantBits()) @@ -51,7 +52,7 @@ public class WidgetMsgConstructorV1 extends BaseWidgetMsgConstructor { .setTitle(widgetsBundle.getTitle()) .setAlias(widgetsBundle.getAlias()); if (widgetsBundle.getImage() != null) { - builder.setImage(ByteString.copyFrom(widgetsBundle.getImage().getBytes(StandardCharsets.UTF_8))); + builder.setImage(ByteString.copyFrom(copy.getImage().getBytes(StandardCharsets.UTF_8))); } if (widgetsBundle.getDescription() != null) { builder.setDescription(widgetsBundle.getDescription()); @@ -68,7 +69,8 @@ public class WidgetMsgConstructorV1 extends BaseWidgetMsgConstructor { @Override public WidgetTypeUpdateMsg constructWidgetTypeUpdateMsg(UpdateMsgType msgType, WidgetTypeDetails widgetTypeDetails, EdgeVersion edgeVersion) { - imageService.inlineImagesForEdge(widgetTypeDetails); + WidgetTypeDetails copy = JacksonUtil.clone(widgetTypeDetails); + imageService.inlineImagesForEdge(copy); WidgetTypeUpdateMsg.Builder builder = WidgetTypeUpdateMsg.newBuilder() .setMsgType(msgType) .setIdMSB(widgetTypeDetails.getId().getId().getMostSignificantBits()) @@ -93,7 +95,7 @@ public class WidgetMsgConstructorV1 extends BaseWidgetMsgConstructor { builder.setIsSystem(true); } if (widgetTypeDetails.getImage() != null) { - builder.setImage(widgetTypeDetails.getImage()); + builder.setImage(copy.getImage()); } if (widgetTypeDetails.getDescription() != null) { if (EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_6_0) && From 2bd5b1765c20f2065a0165b870aec524f163a2db Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Tue, 5 Mar 2024 10:35:04 +0200 Subject: [PATCH 13/14] Improve copy of image for edge --- .../constructor/asset/AssetMsgConstructorV1.java | 7 ++++--- .../dashboard/DashboardMsgConstructorV1.java | 7 ++++--- .../constructor/device/DeviceMsgConstructorV1.java | 7 ++++--- .../constructor/widget/WidgetMsgConstructorV1.java | 13 +++++++------ 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorV1.java index 02318b1aaa..524edbb5a5 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorV1.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorV1.java @@ -63,8 +63,8 @@ public class AssetMsgConstructorV1 extends BaseAssetMsgConstructor { @Override public AssetProfileUpdateMsg constructAssetProfileUpdatedMsg(UpdateMsgType msgType, AssetProfile assetProfile) { - AssetProfile copy = JacksonUtil.clone(assetProfile); - imageService.inlineImageForEdge(copy); + assetProfile = JacksonUtil.clone(assetProfile); + imageService.inlineImageForEdge(assetProfile); AssetProfileUpdateMsg.Builder builder = AssetProfileUpdateMsg.newBuilder() .setMsgType(msgType) .setIdMSB(assetProfile.getId().getId().getMostSignificantBits()) @@ -82,7 +82,7 @@ public class AssetMsgConstructorV1 extends BaseAssetMsgConstructor { builder.setDescription(assetProfile.getDescription()); } if (assetProfile.getImage() != null) { - builder.setImage(ByteString.copyFrom(copy.getImage().getBytes(StandardCharsets.UTF_8))); + builder.setImage(ByteString.copyFrom(assetProfile.getImage().getBytes(StandardCharsets.UTF_8))); } if (assetProfile.getDefaultEdgeRuleChainId() != null) { builder.setDefaultRuleChainIdMSB(assetProfile.getDefaultEdgeRuleChainId().getId().getMostSignificantBits()) @@ -90,4 +90,5 @@ public class AssetMsgConstructorV1 extends BaseAssetMsgConstructor { } return builder.build(); } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV1.java index f94b165e2d..ec2d216a03 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV1.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV1.java @@ -33,8 +33,8 @@ public class DashboardMsgConstructorV1 extends BaseDashboardMsgConstructor { @Override public DashboardUpdateMsg constructDashboardUpdatedMsg(UpdateMsgType msgType, Dashboard dashboard) { - Dashboard copy = JacksonUtil.clone(dashboard); - imageService.inlineImagesForEdge(copy); + dashboard = JacksonUtil.clone(dashboard); + imageService.inlineImagesForEdge(dashboard); DashboardUpdateMsg.Builder builder = DashboardUpdateMsg.newBuilder() .setMsgType(msgType) .setIdMSB(dashboard.getId().getId().getMostSignificantBits()) @@ -46,11 +46,12 @@ public class DashboardMsgConstructorV1 extends BaseDashboardMsgConstructor { builder.setAssignedCustomers(JacksonUtil.toString(dashboard.getAssignedCustomers())); } if (dashboard.getImage() != null) { - builder.setImage(copy.getImage()); + builder.setImage(dashboard.getImage()); } if (dashboard.getMobileOrder() != null) { builder.setMobileOrder(dashboard.getMobileOrder()); } return builder.build(); } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV1.java index 3c6552f9d3..0e8b7452de 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV1.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV1.java @@ -95,8 +95,8 @@ public class DeviceMsgConstructorV1 extends BaseDeviceMsgConstructor { @Override public DeviceProfileUpdateMsg constructDeviceProfileUpdatedMsg(UpdateMsgType msgType, DeviceProfile deviceProfile) { - DeviceProfile copy = JacksonUtil.clone(deviceProfile); - imageService.inlineImageForEdge(copy); + deviceProfile = JacksonUtil.clone(deviceProfile); + imageService.inlineImageForEdge(deviceProfile); DeviceProfileUpdateMsg.Builder builder = DeviceProfileUpdateMsg.newBuilder() .setMsgType(msgType) .setIdMSB(deviceProfile.getId().getId().getMostSignificantBits()) @@ -121,7 +121,7 @@ public class DeviceMsgConstructorV1 extends BaseDeviceMsgConstructor { builder.setProvisionDeviceKey(deviceProfile.getProvisionDeviceKey()); } if (deviceProfile.getImage() != null) { - builder.setImage(ByteString.copyFrom(copy.getImage().getBytes(StandardCharsets.UTF_8))); + builder.setImage(ByteString.copyFrom(deviceProfile.getImage().getBytes(StandardCharsets.UTF_8))); } if (deviceProfile.getFirmwareId() != null) { builder.setFirmwareIdMSB(deviceProfile.getFirmwareId().getId().getMostSignificantBits()) @@ -141,4 +141,5 @@ public class DeviceMsgConstructorV1 extends BaseDeviceMsgConstructor { } return builder.build(); } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorV1.java index 068378bea6..0201f6276b 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorV1.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorV1.java @@ -43,8 +43,8 @@ public class WidgetMsgConstructorV1 extends BaseWidgetMsgConstructor { @Override public WidgetsBundleUpdateMsg constructWidgetsBundleUpdateMsg(UpdateMsgType msgType, WidgetsBundle widgetsBundle, List widgets) { - WidgetsBundle copy = JacksonUtil.clone(widgetsBundle); - imageService.inlineImageForEdge(copy); + widgetsBundle = JacksonUtil.clone(widgetsBundle); + imageService.inlineImageForEdge(widgetsBundle); WidgetsBundleUpdateMsg.Builder builder = WidgetsBundleUpdateMsg.newBuilder() .setMsgType(msgType) .setIdMSB(widgetsBundle.getId().getId().getMostSignificantBits()) @@ -52,7 +52,7 @@ public class WidgetMsgConstructorV1 extends BaseWidgetMsgConstructor { .setTitle(widgetsBundle.getTitle()) .setAlias(widgetsBundle.getAlias()); if (widgetsBundle.getImage() != null) { - builder.setImage(ByteString.copyFrom(copy.getImage().getBytes(StandardCharsets.UTF_8))); + builder.setImage(ByteString.copyFrom(widgetsBundle.getImage().getBytes(StandardCharsets.UTF_8))); } if (widgetsBundle.getDescription() != null) { builder.setDescription(widgetsBundle.getDescription()); @@ -69,8 +69,8 @@ public class WidgetMsgConstructorV1 extends BaseWidgetMsgConstructor { @Override public WidgetTypeUpdateMsg constructWidgetTypeUpdateMsg(UpdateMsgType msgType, WidgetTypeDetails widgetTypeDetails, EdgeVersion edgeVersion) { - WidgetTypeDetails copy = JacksonUtil.clone(widgetTypeDetails); - imageService.inlineImagesForEdge(copy); + widgetTypeDetails = JacksonUtil.clone(widgetTypeDetails); + imageService.inlineImagesForEdge(widgetTypeDetails); WidgetTypeUpdateMsg.Builder builder = WidgetTypeUpdateMsg.newBuilder() .setMsgType(msgType) .setIdMSB(widgetTypeDetails.getId().getId().getMostSignificantBits()) @@ -95,7 +95,7 @@ public class WidgetMsgConstructorV1 extends BaseWidgetMsgConstructor { builder.setIsSystem(true); } if (widgetTypeDetails.getImage() != null) { - builder.setImage(copy.getImage()); + builder.setImage(widgetTypeDetails.getImage()); } if (widgetTypeDetails.getDescription() != null) { if (EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_6_0) && @@ -111,4 +111,5 @@ public class WidgetMsgConstructorV1 extends BaseWidgetMsgConstructor { } return builder.build(); } + } From ff18b8712a357f9af1545532a237e8ed00e9d6fe Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Wed, 6 Mar 2024 14:03:05 +0200 Subject: [PATCH 14/14] Fix null values in Firebase message data --- .../DefaultNotificationCenter.java | 12 ++++- .../MobileAppNotificationChannel.java | 5 +- .../AbstractNotificationApiTest.java | 13 +++-- .../notification/NotificationApiTest.java | 54 +++++++++++++++++++ .../notification/NotificationRuleApiTest.java | 4 +- .../NotificationRequestStats.java | 10 ++++ 6 files changed, 89 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java index 5a9e3fc3e1..25a344b775 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java @@ -21,6 +21,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.NotificationCenter; +import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.EntityId; @@ -62,7 +63,6 @@ import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.dao.notification.NotificationSettingsService; import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.notification.NotificationTemplateService; -import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.TopicService; @@ -204,6 +204,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple private void processNotificationRequestAsync(NotificationProcessingContext ctx, List targets, FutureCallback callback) { notificationExecutor.submit(() -> { + long startTs = System.currentTimeMillis(); NotificationRequestId requestId = ctx.getRequest().getId(); for (NotificationTarget target : targets) { try { @@ -219,9 +220,16 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple return; } } - log.debug("[{}] Notification request processing is finished", requestId); NotificationRequestStats stats = ctx.getStats(); + long time = System.currentTimeMillis() - startTs; + int sent = stats.getTotalSent().get(); + int errors = stats.getTotalErrors().get(); + if (errors > 0) { + log.info("[{}][{}] Notification request processing finished in {} ms (sent: {}, errors: {})", ctx.getTenantId(), requestId, time, sent, errors); + } else { + log.info("[{}][{}] Notification request processing finished in {} ms (sent: {})", ctx.getTenantId(), requestId, time, sent); + } updateRequestStats(ctx, requestId, stats); if (callback != null) { callback.onSuccess(stats); diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java index dc7f3f9d99..646556372a 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.service.notification.channels; +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.base.Strings; import com.google.firebase.messaging.FirebaseMessagingException; import com.google.firebase.messaging.MessagingErrorCode; import lombok.RequiredArgsConstructor; @@ -82,7 +84,7 @@ public class MobileAppNotificationChannel implements NotificationChannel getNotificationData(MobileAppDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) { Map data = Optional.ofNullable(processedTemplate.getAdditionalConfig()) - .map(JacksonUtil::toFlatMap).orElseGet(HashMap::new); + .filter(JsonNode::isObject).map(JacksonUtil::toFlatMap).orElseGet(HashMap::new); NotificationInfo info = ctx.getRequest().getInfo(); if (info == null) { return data; @@ -107,6 +109,7 @@ public class MobileAppNotificationChannel implements NotificationChannel Strings.nullToEmpty(value)); return data; } diff --git a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java index f54b5d9b7b..b945c162df 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java @@ -60,6 +60,7 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.notification.DefaultNotifications; import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationRuleService; import org.thingsboard.server.dao.notification.NotificationSettingsService; @@ -99,6 +100,8 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest protected NotificationSettingsService notificationSettingsService; @Autowired protected SqlPartitioningRepository partitioningRepository; + @Autowired + protected DefaultNotifications defaultNotifications; public static final String DEFAULT_NOTIFICATION_SUBJECT = "Just a test"; public static final NotificationType DEFAULT_NOTIFICATION_TYPE = NotificationType.GENERAL; @@ -249,10 +252,14 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest } protected NotificationRule createNotificationRule(NotificationRuleTriggerConfig triggerConfig, String subject, String text, NotificationTargetId... targets) { - NotificationTemplate template = createNotificationTemplate(NotificationType.valueOf(triggerConfig.getTriggerType().toString()), subject, text, NotificationDeliveryMethod.WEB); + return createNotificationRule(triggerConfig, subject, text, List.of(targets), NotificationDeliveryMethod.WEB); + } + + protected NotificationRule createNotificationRule(NotificationRuleTriggerConfig triggerConfig, String subject, String text, List targets, NotificationDeliveryMethod... deliveryMethods) { + NotificationTemplate template = createNotificationTemplate(NotificationType.valueOf(triggerConfig.getTriggerType().toString()), subject, text, deliveryMethods); NotificationRule rule = new NotificationRule(); - rule.setName(triggerConfig.getTriggerType() + " " + Arrays.toString(targets)); + rule.setName(triggerConfig.getTriggerType() + " " + targets); rule.setEnabled(true); rule.setTemplateId(template.getId()); rule.setTriggerType(triggerConfig.getTriggerType()); @@ -260,7 +267,7 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest DefaultNotificationRuleRecipientsConfig recipientsConfig = new DefaultNotificationRuleRecipientsConfig(); recipientsConfig.setTriggerType(triggerConfig.getTriggerType()); - recipientsConfig.setTargets(DaoUtil.toUUIDs(List.of(targets))); + recipientsConfig.setTargets(DaoUtil.toUUIDs(targets)); rule.setRecipientsConfig(recipientsConfig); return saveNotificationRule(rule); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java index 2d93573a25..d3b79029ba 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java @@ -30,8 +30,12 @@ import org.springframework.web.client.RestTemplate; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.NotificationCenter; import org.thingsboard.rule.engine.api.notification.FirebaseService; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmComment; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.NotificationRequestId; @@ -49,6 +53,7 @@ import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.info.EntityActionNotificationInfo; +import org.thingsboard.server.common.data.notification.rule.trigger.config.AlarmCommentNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.settings.MobileAppNotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; @@ -771,6 +776,55 @@ public class NotificationApiTest extends AbstractNotificationApiTest { verifyNoMoreInteractions(firebaseService); } + @Test + public void testMobileAppNotifications_ruleBased() throws Exception { + loginSysAdmin(); + MobileAppNotificationDeliveryMethodConfig config = new MobileAppNotificationDeliveryMethodConfig(); + config.setFirebaseServiceAccountCredentials("testCredentials"); + saveNotificationSettings(config); + + loginTenantAdmin(); + mobileToken = "tenantFcmToken"; + doPost("/api/user/mobile/session", new MobileSessionInfo()).andExpect(status().isOk()); + + createNotificationRule(AlarmCommentNotificationRuleTriggerConfig.builder().onlyUserComments(true).build(), + DefaultNotifications.alarmComment.getSubject(), DefaultNotifications.alarmComment.getText(), + List.of(createNotificationTarget(tenantAdminUserId).getId()), NotificationDeliveryMethod.MOBILE_APP); + + Device device = createDevice("test", "test"); + UUID alarmDashboardId = UUID.randomUUID(); + Alarm alarm = Alarm.builder() + .type("test") + .tenantId(tenantId) + .originator(device.getId()) + .severity(AlarmSeverity.MAJOR) + .details(JacksonUtil.newObjectNode() + .put("dashboardId", alarmDashboardId.toString())) + .build(); + alarm = doPost("/api/alarm", alarm, Alarm.class); + + AlarmComment comment = new AlarmComment(); + comment.setComment(JacksonUtil.newObjectNode() + .put("text", "text")); + doPost("/api/alarm/" + alarm.getId() + "/comment", comment, AlarmComment.class); + + ArgumentCaptor> msgCaptor = ArgumentCaptor.forClass(Map.class); + await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> { + verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"), + eq("tenantFcmToken"), eq("Comment on 'test' alarm"), + eq(TENANT_ADMIN_EMAIL + " added comment: text"), + msgCaptor.capture()); + }); + Map firebaseMessageData = msgCaptor.getValue(); + assertThat(firebaseMessageData.keySet()).doesNotContainNull().doesNotContain(""); + assertThat(firebaseMessageData.values()).doesNotContainNull(); + assertThat(firebaseMessageData.get("info.userEmail")).isEqualTo(TENANT_ADMIN_EMAIL); + assertThat(firebaseMessageData.get("info.alarmType")).isEqualTo("test"); + assertThat(firebaseMessageData.get("onClick.enabled")).isEqualTo("true"); + assertThat(firebaseMessageData.get("onClick.linkType")).isEqualTo("DASHBOARD"); + assertThat(firebaseMessageData.get("onClick.dashboardId")).isEqualTo(alarmDashboardId.toString()); + } + @Test public void testMobileSettings_tenantLevel() throws Exception { MobileAppNotificationDeliveryMethodConfig config = new MobileAppNotificationDeliveryMethodConfig(); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java index 1e95f9add3..9cf73894c2 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java @@ -26,6 +26,7 @@ import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.data.util.Pair; import org.springframework.test.context.TestPropertySource; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; @@ -93,7 +94,6 @@ import org.thingsboard.server.dao.notification.DefaultNotifications; import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.queue.notification.DefaultNotificationDeduplicationService; import org.thingsboard.server.service.notification.rule.cache.DefaultNotificationRulesCache; import org.thingsboard.server.service.state.DeviceStateService; @@ -140,8 +140,6 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { @Autowired private NotificationRuleProcessor notificationRuleProcessor; @Autowired - private DefaultNotifications defaultNotifications; - @Autowired private DefaultNotificationRulesCache notificationRulesCache; @Autowired private DeviceStateService deviceStateService; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java index f7cdbda6fc..2bd9c526fd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java @@ -31,14 +31,20 @@ import java.util.concurrent.atomic.AtomicInteger; public class NotificationRequestStats { private final Map sent; + @JsonIgnore + private final AtomicInteger totalSent; private final Map> errors; + @JsonIgnore + private final AtomicInteger totalErrors; private String error; @JsonIgnore private final Map> processedRecipients; public NotificationRequestStats() { this.sent = new ConcurrentHashMap<>(); + this.totalSent = new AtomicInteger(); this.errors = new ConcurrentHashMap<>(); + this.totalErrors = new AtomicInteger(); this.processedRecipients = new ConcurrentHashMap<>(); } @@ -47,13 +53,16 @@ public class NotificationRequestStats { @JsonProperty("errors") Map> errors, @JsonProperty("error") String error) { this.sent = sent; + this.totalSent = null; this.errors = errors; + this.totalErrors = null; this.error = error; this.processedRecipients = Collections.emptyMap(); } public void reportSent(NotificationDeliveryMethod deliveryMethod, NotificationRecipient recipient) { sent.computeIfAbsent(deliveryMethod, k -> new AtomicInteger()).incrementAndGet(); + totalSent.incrementAndGet(); } public void reportError(NotificationDeliveryMethod deliveryMethod, Throwable error, NotificationRecipient recipient) { @@ -65,6 +74,7 @@ public class NotificationRequestStats { errorMessage = error.getClass().getSimpleName(); } errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(recipient.getTitle(), errorMessage); + totalErrors.incrementAndGet(); } public void reportProcessed(NotificationDeliveryMethod deliveryMethod, Object recipientId) {