Browse Source

Merge branch 'master' into feature/time-series-chart

pull/10315/head
Igor Kulikov 3 years ago
parent
commit
4de8c6e595
  1. 2
      application/src/main/data/json/system/widget_types/persistent_rpc_table.json
  2. 2
      application/src/main/data/json/system/widget_types/rpc_debug_terminal.json
  3. 2
      application/src/main/data/json/system/widget_types/rpc_remote_shell.json
  4. 12
      application/src/main/java/org/thingsboard/server/controller/AdminController.java
  5. 4
      application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
  6. 43
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  7. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorV1.java
  8. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV1.java
  9. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV1.java
  10. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorV1.java
  11. 12
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
  12. 42
      application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java
  13. 1
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java
  14. 1
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java
  15. 3
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java
  16. 2
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  17. 7
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  18. 7
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
  19. 5
      application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java
  20. 6
      application/src/test/java/org/thingsboard/server/service/limits/RateLimitServiceTest.java
  21. 13
      application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java
  22. 54
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  23. 4
      application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java
  24. 1
      application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java
  25. 10
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
  26. 2
      common/data/src/main/java/org/thingsboard/server/common/data/limit/LimitedApi.java
  27. 10
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java
  28. 1
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java
  29. 7
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java
  30. 7
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java
  31. 7
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmNotificationInfo.java
  32. 5
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NotificationInfo.java
  33. 2
      common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java
  34. 8
      ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html
  35. 4
      ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts
  36. 8
      ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts
  37. 8
      ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts
  38. 10
      ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-action-button-configuration.component.html
  39. 3
      ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-action-button-configuration.component.ts
  40. 1
      ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html
  41. 14
      ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.ts
  42. 1
      ui-ngx/src/app/shared/components/image/embed-image-dialog.component.scss
  43. 4
      ui-ngx/src/app/shared/components/image/image-dialog.component.ts
  44. 6
      ui-ngx/src/app/shared/models/limited-api.models.ts
  45. 2
      ui-ngx/src/assets/help/en_US/notification/rate_limits.md
  46. 9
      ui-ngx/src/assets/locale/locale.constant-en_US.json

2
application/src/main/data/json/system/widget_types/persistent_rpc_table.json

@ -15,7 +15,7 @@
"settingsSchema": "", "settingsSchema": "",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-persistent-table-widget-settings", "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, "externalId": null,
"tags": [ "tags": [

2
application/src/main/data/json/system/widget_types/rpc_debug_terminal.json

@ -11,7 +11,7 @@
"resources": [], "resources": [],
"templateHtml": "<div style=\"height: 100%; overflow-y: auto;\" id=\"device-terminal\"></div>", "templateHtml": "<div style=\"height: 100%; overflow-y: auto;\" id=\"device-terminal\"></div>",
"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", "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 += ' <method> [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 += ' <method> [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": "", "settingsSchema": "",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-rpc-terminal-widget-settings", "settingsDirective": "tb-rpc-terminal-widget-settings",

2
application/src/main/data/json/system/widget_types/rpc_remote_shell.json

File diff suppressed because one or more lines are too long

12
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 io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; 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.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize; 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.MailService;
import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.server.common.data.AdminSettings; 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.FeaturesInfo;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.SystemInfo; import org.thingsboard.server.common.data.SystemInfo;
import org.thingsboard.server.common.data.UpdateMessage; 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.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; 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.audit.AuditLogService;
import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.queue.util.TbCoreComponent; 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.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.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Operation;
@ -93,7 +90,6 @@ import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Optional; 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.SYSTEM_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.TENANT_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 SmsService smsService;
private final AdminSettingsService adminSettingsService; private final AdminSettingsService adminSettingsService;
private final SystemSecurityService systemSecurityService; private final SystemSecurityService systemSecurityService;
@Lazy
private final JwtSettingsService jwtSettingsService; private final JwtSettingsService jwtSettingsService;
@Lazy
private final JwtTokenFactory tokenFactory; private final JwtTokenFactory tokenFactory;
private final EntitiesVersionControlService versionControlService; private final EntitiesVersionControlService versionControlService;
private final TbAutoCommitSettingsService autoCommitSettingsService; private final TbAutoCommitSettingsService autoCommitSettingsService;

4
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.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.cache.limits.RateLimitService;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.asset.AssetProfileService;
@ -150,6 +151,9 @@ public class EdgeContextComponent {
@Autowired @Autowired
private ResourceService resourceService; private ResourceService resourceService;
@Autowired
private RateLimitService rateLimitService;
@Autowired @Autowired
private NotificationRuleProcessor notificationRuleProcessor; private NotificationRuleProcessor notificationRuleProcessor;

43
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java

@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry; 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.notification.rule.trigger.EdgeCommunicationFailureTrigger;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
@ -109,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_TS_ATTR_KEY = "queueStartTs";
private static final String QUEUE_START_SEQ_ID_ATTR_KEY = "queueStartSeqId"; 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 UUID sessionId;
private final BiConsumer<EdgeId, EdgeGrpcSession> sessionOpenListener; private final BiConsumer<EdgeId, EdgeGrpcSession> sessionOpenListener;
@ -264,36 +266,51 @@ public final class EdgeGrpcSession implements Closeable {
} }
private void onUplinkMsg(UplinkMsg uplinkMsg) { private void onUplinkMsg(UplinkMsg uplinkMsg) {
if (isRateLimitViolated(uplinkMsg)) {
return;
}
ListenableFuture<List<Void>> future = processUplinkMsg(uplinkMsg); ListenableFuture<List<Void>> future = processUplinkMsg(uplinkMsg);
Futures.addCallback(future, new FutureCallback<>() { Futures.addCallback(future, new FutureCallback<>() {
@Override @Override
public void onSuccess(@Nullable List<Void> result) { public void onSuccess(@Nullable List<Void> result) {
UplinkResponseMsg uplinkResponseMsg = UplinkResponseMsg.newBuilder() sendResponseMessage(uplinkMsg.getUplinkMsgId(), true, null);
.setUplinkMsgId(uplinkMsg.getUplinkMsgId())
.setSuccess(true).build();
sendDownlinkMsg(ResponseMsg.newBuilder()
.setUplinkResponseMsg(uplinkResponseMsg)
.build());
} }
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
String errorMsg = EdgeUtils.createErrorMsgFromRootCauseAndStackTrace(t); String errorMsg = EdgeUtils.createErrorMsgFromRootCauseAndStackTrace(t);
UplinkResponseMsg uplinkResponseMsg = UplinkResponseMsg.newBuilder() sendResponseMessage(uplinkMsg.getUplinkMsgId(), false, errorMsg);
.setUplinkMsgId(uplinkMsg.getUplinkMsgId())
.setSuccess(false).setErrorMsg(errorMsg).build();
sendDownlinkMsg(ResponseMsg.newBuilder()
.setUplinkResponseMsg(uplinkResponseMsg)
.build());
} }
}, ctx.getGrpcCallbackExecutorService()); }, 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) { private void onDownlinkResponse(DownlinkResponseMsg msg) {
try { try {
if (msg.getSuccess()) { if (msg.getSuccess()) {
sessionState.getPendingMsgsMap().remove(msg.getDownlinkMsgId()); 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 { } else {
log.error("[{}][{}] Msg processing failed! Msg Id: [{}], Error msg: {}", this.tenantId, edge.getRoutingKey(), msg.getDownlinkMsgId(), msg.getErrorMsg()); log.error("[{}][{}] Msg processing failed! Msg Id: [{}], Error msg: {}", this.tenantId, edge.getRoutingKey(), msg.getDownlinkMsgId(), msg.getErrorMsg());
} }

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorV1.java

@ -63,6 +63,7 @@ public class AssetMsgConstructorV1 extends BaseAssetMsgConstructor {
@Override @Override
public AssetProfileUpdateMsg constructAssetProfileUpdatedMsg(UpdateMsgType msgType, AssetProfile assetProfile) { public AssetProfileUpdateMsg constructAssetProfileUpdatedMsg(UpdateMsgType msgType, AssetProfile assetProfile) {
assetProfile = JacksonUtil.clone(assetProfile);
imageService.inlineImageForEdge(assetProfile); imageService.inlineImageForEdge(assetProfile);
AssetProfileUpdateMsg.Builder builder = AssetProfileUpdateMsg.newBuilder() AssetProfileUpdateMsg.Builder builder = AssetProfileUpdateMsg.newBuilder()
.setMsgType(msgType) .setMsgType(msgType)
@ -89,4 +90,5 @@ public class AssetMsgConstructorV1 extends BaseAssetMsgConstructor {
} }
return builder.build(); return builder.build();
} }
} }

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV1.java

@ -33,6 +33,7 @@ public class DashboardMsgConstructorV1 extends BaseDashboardMsgConstructor {
@Override @Override
public DashboardUpdateMsg constructDashboardUpdatedMsg(UpdateMsgType msgType, Dashboard dashboard) { public DashboardUpdateMsg constructDashboardUpdatedMsg(UpdateMsgType msgType, Dashboard dashboard) {
dashboard = JacksonUtil.clone(dashboard);
imageService.inlineImagesForEdge(dashboard); imageService.inlineImagesForEdge(dashboard);
DashboardUpdateMsg.Builder builder = DashboardUpdateMsg.newBuilder() DashboardUpdateMsg.Builder builder = DashboardUpdateMsg.newBuilder()
.setMsgType(msgType) .setMsgType(msgType)
@ -52,4 +53,5 @@ public class DashboardMsgConstructorV1 extends BaseDashboardMsgConstructor {
} }
return builder.build(); return builder.build();
} }
} }

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV1.java

@ -95,6 +95,7 @@ public class DeviceMsgConstructorV1 extends BaseDeviceMsgConstructor {
@Override @Override
public DeviceProfileUpdateMsg constructDeviceProfileUpdatedMsg(UpdateMsgType msgType, DeviceProfile deviceProfile) { public DeviceProfileUpdateMsg constructDeviceProfileUpdatedMsg(UpdateMsgType msgType, DeviceProfile deviceProfile) {
deviceProfile = JacksonUtil.clone(deviceProfile);
imageService.inlineImageForEdge(deviceProfile); imageService.inlineImageForEdge(deviceProfile);
DeviceProfileUpdateMsg.Builder builder = DeviceProfileUpdateMsg.newBuilder() DeviceProfileUpdateMsg.Builder builder = DeviceProfileUpdateMsg.newBuilder()
.setMsgType(msgType) .setMsgType(msgType)
@ -140,4 +141,5 @@ public class DeviceMsgConstructorV1 extends BaseDeviceMsgConstructor {
} }
return builder.build(); return builder.build();
} }
} }

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorV1.java

@ -43,6 +43,7 @@ public class WidgetMsgConstructorV1 extends BaseWidgetMsgConstructor {
@Override @Override
public WidgetsBundleUpdateMsg constructWidgetsBundleUpdateMsg(UpdateMsgType msgType, WidgetsBundle widgetsBundle, List<String> widgets) { public WidgetsBundleUpdateMsg constructWidgetsBundleUpdateMsg(UpdateMsgType msgType, WidgetsBundle widgetsBundle, List<String> widgets) {
widgetsBundle = JacksonUtil.clone(widgetsBundle);
imageService.inlineImageForEdge(widgetsBundle); imageService.inlineImageForEdge(widgetsBundle);
WidgetsBundleUpdateMsg.Builder builder = WidgetsBundleUpdateMsg.newBuilder() WidgetsBundleUpdateMsg.Builder builder = WidgetsBundleUpdateMsg.newBuilder()
.setMsgType(msgType) .setMsgType(msgType)
@ -68,6 +69,7 @@ public class WidgetMsgConstructorV1 extends BaseWidgetMsgConstructor {
@Override @Override
public WidgetTypeUpdateMsg constructWidgetTypeUpdateMsg(UpdateMsgType msgType, WidgetTypeDetails widgetTypeDetails, EdgeVersion edgeVersion) { public WidgetTypeUpdateMsg constructWidgetTypeUpdateMsg(UpdateMsgType msgType, WidgetTypeDetails widgetTypeDetails, EdgeVersion edgeVersion) {
widgetTypeDetails = JacksonUtil.clone(widgetTypeDetails);
imageService.inlineImagesForEdge(widgetTypeDetails); imageService.inlineImagesForEdge(widgetTypeDetails);
WidgetTypeUpdateMsg.Builder builder = WidgetTypeUpdateMsg.newBuilder() WidgetTypeUpdateMsg.Builder builder = WidgetTypeUpdateMsg.newBuilder()
.setMsgType(msgType) .setMsgType(msgType)
@ -109,4 +111,5 @@ public class WidgetMsgConstructorV1 extends BaseWidgetMsgConstructor {
} }
return builder.build(); return builder.build();
} }
} }

12
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.NotificationCenter; 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.EntityType;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.EntityId; 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.NotificationSettingsService;
import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.notification.NotificationTemplateService; 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.gen.transport.TransportProtos;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.TopicService; import org.thingsboard.server.queue.discovery.TopicService;
@ -204,6 +204,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
private void processNotificationRequestAsync(NotificationProcessingContext ctx, List<NotificationTarget> targets, FutureCallback<NotificationRequestStats> callback) { private void processNotificationRequestAsync(NotificationProcessingContext ctx, List<NotificationTarget> targets, FutureCallback<NotificationRequestStats> callback) {
notificationExecutor.submit(() -> { notificationExecutor.submit(() -> {
long startTs = System.currentTimeMillis();
NotificationRequestId requestId = ctx.getRequest().getId(); NotificationRequestId requestId = ctx.getRequest().getId();
for (NotificationTarget target : targets) { for (NotificationTarget target : targets) {
try { try {
@ -219,9 +220,16 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
return; return;
} }
} }
log.debug("[{}] Notification request processing is finished", requestId);
NotificationRequestStats stats = ctx.getStats(); 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); updateRequestStats(ctx, requestId, stats);
if (callback != null) { if (callback != null) {
callback.onSuccess(stats); callback.onSuccess(stats);

42
application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java

@ -15,6 +15,8 @@
*/ */
package org.thingsboard.server.service.notification.channels; 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.FirebaseMessagingException;
import com.google.firebase.messaging.MessagingErrorCode; import com.google.firebase.messaging.MessagingErrorCode;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -61,14 +63,7 @@ public class MobileAppNotificationChannel implements NotificationChannel<User, M
String subject = processedTemplate.getSubject(); String subject = processedTemplate.getSubject();
String body = processedTemplate.getBody(); String body = processedTemplate.getBody();
Map<String, String> data = Optional.ofNullable(processedTemplate.getAdditionalConfig()) Map<String, String> data = getNotificationData(processedTemplate, ctx);
.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());
});
for (String token : mobileSessions.keySet()) { for (String token : mobileSessions.keySet()) {
try { try {
firebaseService.sendMessage(ctx.getTenantId(), credentials, token, subject, body, data); firebaseService.sendMessage(ctx.getTenantId(), credentials, token, subject, body, data);
@ -87,6 +82,37 @@ public class MobileAppNotificationChannel implements NotificationChannel<User, M
} }
} }
private Map<String, String> getNotificationData(MobileAppDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) {
Map<String, String> data = Optional.ofNullable(processedTemplate.getAdditionalConfig())
.filter(JsonNode::isObject).map(JacksonUtil::toFlatMap).orElseGet(HashMap::new);
NotificationInfo info = ctx.getRequest().getInfo();
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) {
data.put("onClick.enabled", "true");
data.put("onClick.linkType", "DASHBOARD");
data.put("onClick.setEntityIdInState", "true");
data.put("onClick.dashboardId", info.getDashboardId().toString());
}
});
data.put("notificationType", ctx.getNotificationType().name());
switch (ctx.getNotificationType()) {
case ALARM:
case ALARM_ASSIGNMENT:
case ALARM_COMMENT:
info.getTemplateData().forEach((key, value) -> {
data.put("info." + key, value);
});
break;
}
data.replaceAll((key, value) -> Strings.nullToEmpty(value));
return data;
}
@Override @Override
public void check(TenantId tenantId) throws Exception { public void check(TenantId tenantId) throws Exception {
NotificationSettings systemSettings = notificationSettingsService.findNotificationSettings(TenantId.SYS_TENANT_ID); NotificationSettings systemSettings = notificationSettingsService.findNotificationSettings(TenantId.SYS_TENANT_ID);

1
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()) .alarmSeverity(alarmInfo.getSeverity())
.alarmStatus(alarmInfo.getStatus()) .alarmStatus(alarmInfo.getStatus())
.alarmCustomerId(alarmInfo.getCustomerId()) .alarmCustomerId(alarmInfo.getCustomerId())
.dashboardId(alarmInfo.getDashboardId())
.build(); .build();
} }

1
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()) .alarmSeverity(alarm.getSeverity())
.alarmStatus(alarm.getStatus()) .alarmStatus(alarm.getStatus())
.alarmCustomerId(alarm.getCustomerId()) .alarmCustomerId(alarm.getCustomerId())
.dashboardId(alarm.getDashboardId())
.build(); .build();
} }

3
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.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.notification.info.AlarmNotificationInfo; 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.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;
import org.thingsboard.server.common.data.notification.rule.trigger.config.AlarmNotificationRuleTriggerConfig.AlarmAction; 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.AlarmNotificationRuleTriggerConfig.ClearRule;
import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; 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.apache.commons.collections.CollectionUtils.isNotEmpty;
import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains; import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains;
@ -111,6 +111,7 @@ public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<A
.acknowledged(alarmInfo.isAcknowledged()) .acknowledged(alarmInfo.isAcknowledged())
.cleared(alarmInfo.isCleared()) .cleared(alarmInfo.isCleared())
.alarmCustomerId(alarmInfo.getCustomerId()) .alarmCustomerId(alarmInfo.getCustomerId())
.dashboardId(alarmInfo.getDashboardId())
.build(); .build();
} }

2
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -171,7 +171,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
GitVersionControlQueueService vcQueueService, GitVersionControlQueueService vcQueueService,
PartitionService partitionService, PartitionService partitionService,
ApplicationEventPublisher eventPublisher, ApplicationEventPublisher eventPublisher,
Optional<JwtSettingsService> jwtSettingsService, JwtSettingsService jwtSettingsService,
NotificationSchedulerService notificationSchedulerService, NotificationSchedulerService notificationSchedulerService,
NotificationRuleProcessor notificationRuleProcessor, NotificationRuleProcessor notificationRuleProcessor,
TbImageService imageService) { TbImageService imageService) {

7
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.TbRuleEngineConsumerContext;
import org.thingsboard.server.service.queue.ruleengine.TbRuleEngineQueueConsumerManager; import org.thingsboard.server.service.queue.ruleengine.TbRuleEngineQueueConsumerManager;
import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService;
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.util.ArrayList; import java.util.ArrayList;
@ -85,9 +86,11 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
TbAssetProfileCache assetProfileCache, TbAssetProfileCache assetProfileCache,
TbTenantProfileCache tenantProfileCache, TbTenantProfileCache tenantProfileCache,
TbApiUsageStateService apiUsageStateService, TbApiUsageStateService apiUsageStateService,
PartitionService partitionService, ApplicationEventPublisher eventPublisher) { PartitionService partitionService,
ApplicationEventPublisher eventPublisher,
JwtSettingsService jwtSettingsService) {
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService,
eventPublisher, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer(), Optional.empty()); eventPublisher, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer(), jwtSettingsService);
this.ctx = ctx; this.ctx = ctx;
this.tbDeviceRpcService = tbDeviceRpcService; this.tbDeviceRpcService = tbDeviceRpcService;
this.queueService = queueService; this.queueService = queueService;

7
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 javax.annotation.PreDestroy;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
@ -75,14 +74,14 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
protected final ApplicationEventPublisher eventPublisher; protected final ApplicationEventPublisher eventPublisher;
protected final TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer; protected final TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer;
protected final Optional<JwtSettingsService> jwtSettingsService; protected final JwtSettingsService jwtSettingsService;
public AbstractConsumerService(ActorSystemContext actorContext, DataDecodingEncodingService encodingService, public AbstractConsumerService(ActorSystemContext actorContext, DataDecodingEncodingService encodingService,
TbTenantProfileCache tenantProfileCache, TbDeviceProfileCache deviceProfileCache, TbTenantProfileCache tenantProfileCache, TbDeviceProfileCache deviceProfileCache,
TbAssetProfileCache assetProfileCache, TbApiUsageStateService apiUsageStateService, TbAssetProfileCache assetProfileCache, TbApiUsageStateService apiUsageStateService,
PartitionService partitionService, ApplicationEventPublisher eventPublisher, PartitionService partitionService, ApplicationEventPublisher eventPublisher,
TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer, Optional<JwtSettingsService> jwtSettingsService) { TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer, JwtSettingsService jwtSettingsService) {
this.actorContext = actorContext; this.actorContext = actorContext;
this.encodingService = encodingService; this.encodingService = encodingService;
this.tenantProfileCache = tenantProfileCache; this.tenantProfileCache = tenantProfileCache;
@ -181,7 +180,7 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
} }
} else if (EntityType.TENANT.equals(componentLifecycleMsg.getEntityId().getEntityType())) { } else if (EntityType.TENANT.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
if (TenantId.SYS_TENANT_ID.equals(tenantId)) { if (TenantId.SYS_TENANT_ID.equals(tenantId)) {
jwtSettingsService.ifPresent(JwtSettingsService::reloadJwtSettings); jwtSettingsService.reloadJwtSettings();
return; return;
} else { } else {
tenantProfileCache.evict(tenantId); tenantProfileCache.evict(tenantId);

5
application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java

@ -19,7 +19,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.NotificationCenter; import org.thingsboard.rule.engine.api.NotificationCenter;
@ -42,9 +41,7 @@ import java.util.Optional;
@Slf4j @Slf4j
public class DefaultJwtSettingsService implements JwtSettingsService { public class DefaultJwtSettingsService implements JwtSettingsService {
@Lazy
private final AdminSettingsService adminSettingsService; private final AdminSettingsService adminSettingsService;
@Lazy
private final Optional<TbClusterService> tbClusterService; private final Optional<TbClusterService> tbClusterService;
private final Optional<NotificationCenter> notificationCenter; private final Optional<NotificationCenter> notificationCenter;
private final JwtSettingsValidator jwtSettingsValidator; private final JwtSettingsValidator jwtSettingsValidator;
@ -107,11 +104,13 @@ public class DefaultJwtSettingsService implements JwtSettingsService {
@Override @Override
public JwtSettings reloadJwtSettings() { public JwtSettings reloadJwtSettings() {
log.trace("Executing reloadJwtSettings");
return getJwtSettings(true); return getJwtSettings(true);
} }
@Override @Override
public JwtSettings getJwtSettings() { public JwtSettings getJwtSettings() {
log.trace("Executing getJwtSettings");
return getJwtSettings(false); return getJwtSettings(false);
} }

6
application/src/test/java/org/thingsboard/server/service/limits/RateLimitServiceTest.java

@ -71,6 +71,8 @@ public class RateLimitServiceTest {
profileConfiguration.setCassandraQueryTenantRateLimitsConfiguration(rateLimit); profileConfiguration.setCassandraQueryTenantRateLimitsConfiguration(rateLimit);
profileConfiguration.setEdgeEventRateLimits(rateLimit); profileConfiguration.setEdgeEventRateLimits(rateLimit);
profileConfiguration.setEdgeEventRateLimitsPerEdge(rateLimit); profileConfiguration.setEdgeEventRateLimitsPerEdge(rateLimit);
profileConfiguration.setEdgeUplinkMessagesRateLimits(rateLimit);
profileConfiguration.setEdgeUplinkMessagesRateLimitsPerEdge(rateLimit);
updateTenantProfileConfiguration(profileConfiguration); updateTenantProfileConfiguration(profileConfiguration);
for (LimitedApi limitedApi : List.of( for (LimitedApi limitedApi : List.of(
@ -80,7 +82,9 @@ public class RateLimitServiceTest {
LimitedApi.REST_REQUESTS_PER_CUSTOMER, LimitedApi.REST_REQUESTS_PER_CUSTOMER,
LimitedApi.CASSANDRA_QUERIES, LimitedApi.CASSANDRA_QUERIES,
LimitedApi.EDGE_EVENTS, 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); testRateLimits(limitedApi, max, tenantId);
} }

13
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.common.data.security.Authority;
import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.DaoUtil; 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.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationRuleService; import org.thingsboard.server.dao.notification.NotificationRuleService;
import org.thingsboard.server.dao.notification.NotificationSettingsService; import org.thingsboard.server.dao.notification.NotificationSettingsService;
@ -99,6 +100,8 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
protected NotificationSettingsService notificationSettingsService; protected NotificationSettingsService notificationSettingsService;
@Autowired @Autowired
protected SqlPartitioningRepository partitioningRepository; protected SqlPartitioningRepository partitioningRepository;
@Autowired
protected DefaultNotifications defaultNotifications;
public static final String DEFAULT_NOTIFICATION_SUBJECT = "Just a test"; public static final String DEFAULT_NOTIFICATION_SUBJECT = "Just a test";
public static final NotificationType DEFAULT_NOTIFICATION_TYPE = NotificationType.GENERAL; 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) { 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<NotificationTargetId> targets, NotificationDeliveryMethod... deliveryMethods) {
NotificationTemplate template = createNotificationTemplate(NotificationType.valueOf(triggerConfig.getTriggerType().toString()), subject, text, deliveryMethods);
NotificationRule rule = new NotificationRule(); NotificationRule rule = new NotificationRule();
rule.setName(triggerConfig.getTriggerType() + " " + Arrays.toString(targets)); rule.setName(triggerConfig.getTriggerType() + " " + targets);
rule.setEnabled(true); rule.setEnabled(true);
rule.setTemplateId(template.getId()); rule.setTemplateId(template.getId());
rule.setTriggerType(triggerConfig.getTriggerType()); rule.setTriggerType(triggerConfig.getTriggerType());
@ -260,7 +267,7 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
DefaultNotificationRuleRecipientsConfig recipientsConfig = new DefaultNotificationRuleRecipientsConfig(); DefaultNotificationRuleRecipientsConfig recipientsConfig = new DefaultNotificationRuleRecipientsConfig();
recipientsConfig.setTriggerType(triggerConfig.getTriggerType()); recipientsConfig.setTriggerType(triggerConfig.getTriggerType());
recipientsConfig.setTargets(DaoUtil.toUUIDs(List.of(targets))); recipientsConfig.setTargets(DaoUtil.toUUIDs(targets));
rule.setRecipientsConfig(recipientsConfig); rule.setRecipientsConfig(recipientsConfig);
return saveNotificationRule(rule); return saveNotificationRule(rule);

54
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.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.NotificationCenter; import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.rule.engine.api.notification.FirebaseService; 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.EntityType;
import org.thingsboard.server.common.data.User; 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.audit.ActionType;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.NotificationRequestId; 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.NotificationRequestStatus;
import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.info.EntityActionNotificationInfo; 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.MobileAppNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
@ -771,6 +776,55 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
verifyNoMoreInteractions(firebaseService); 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<Map<String, String>> 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<String, String> 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 @Test
public void testMobileSettings_tenantLevel() throws Exception { public void testMobileSettings_tenantLevel() throws Exception {
MobileAppNotificationDeliveryMethodConfig config = new MobileAppNotificationDeliveryMethodConfig(); MobileAppNotificationDeliveryMethodConfig config = new MobileAppNotificationDeliveryMethodConfig();

4
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.data.util.Pair;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil; 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.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; 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.notification.NotificationRequestService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DaoSqlTest; 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.queue.notification.DefaultNotificationDeduplicationService;
import org.thingsboard.server.service.notification.rule.cache.DefaultNotificationRulesCache; import org.thingsboard.server.service.notification.rule.cache.DefaultNotificationRulesCache;
import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.state.DeviceStateService;
@ -140,8 +140,6 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
@Autowired @Autowired
private NotificationRuleProcessor notificationRuleProcessor; private NotificationRuleProcessor notificationRuleProcessor;
@Autowired @Autowired
private DefaultNotifications defaultNotifications;
@Autowired
private DefaultNotificationRulesCache notificationRulesCache; private DefaultNotificationRulesCache notificationRulesCache;
@Autowired @Autowired
private DeviceStateService deviceStateService; private DeviceStateService deviceStateService;

1
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.UserPrincipal;
import org.thingsboard.server.service.security.model.token.AccessJwtToken; 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.JwtTokenFactory;
import org.thingsboard.server.service.security.model.token.RawAccessJwtToken;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;

10
common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.common.data.alarm; package org.thingsboard.server.common.data.alarm;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
@ -30,6 +31,7 @@ import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId; 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.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
@ -37,6 +39,8 @@ import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.common.data.validation.NoXss;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.UUID;
/** /**
* Created by ashvayka on 11.05.17. * Created by ashvayka on 11.05.17.
@ -160,4 +164,10 @@ public class Alarm extends BaseData<AlarmId> 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);
}
} }

2
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), CASSANDRA_QUERIES(DefaultTenantProfileConfiguration::getCassandraQueryTenantRateLimitsConfiguration, "Cassandra queries", true),
EDGE_EVENTS(DefaultTenantProfileConfiguration::getEdgeEventRateLimits, "Edge events", true), EDGE_EVENTS(DefaultTenantProfileConfiguration::getEdgeEventRateLimits, "Edge events", true),
EDGE_EVENTS_PER_EDGE(DefaultTenantProfileConfiguration::getEdgeEventRateLimitsPerEdge, "Edge events per edge", false), 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), PASSWORD_RESET(false, true),
TWO_FA_VERIFICATION_CODE_SEND(false, true), TWO_FA_VERIFICATION_CODE_SEND(false, true),
TWO_FA_VERIFICATION_CODE_CHECK(false, true), TWO_FA_VERIFICATION_CODE_CHECK(false, true),

10
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 { public class NotificationRequestStats {
private final Map<NotificationDeliveryMethod, AtomicInteger> sent; private final Map<NotificationDeliveryMethod, AtomicInteger> sent;
@JsonIgnore
private final AtomicInteger totalSent;
private final Map<NotificationDeliveryMethod, Map<String, String>> errors; private final Map<NotificationDeliveryMethod, Map<String, String>> errors;
@JsonIgnore
private final AtomicInteger totalErrors;
private String error; private String error;
@JsonIgnore @JsonIgnore
private final Map<NotificationDeliveryMethod, Set<Object>> processedRecipients; private final Map<NotificationDeliveryMethod, Set<Object>> processedRecipients;
public NotificationRequestStats() { public NotificationRequestStats() {
this.sent = new ConcurrentHashMap<>(); this.sent = new ConcurrentHashMap<>();
this.totalSent = new AtomicInteger();
this.errors = new ConcurrentHashMap<>(); this.errors = new ConcurrentHashMap<>();
this.totalErrors = new AtomicInteger();
this.processedRecipients = new ConcurrentHashMap<>(); this.processedRecipients = new ConcurrentHashMap<>();
} }
@ -47,13 +53,16 @@ public class NotificationRequestStats {
@JsonProperty("errors") Map<NotificationDeliveryMethod, Map<String, String>> errors, @JsonProperty("errors") Map<NotificationDeliveryMethod, Map<String, String>> errors,
@JsonProperty("error") String error) { @JsonProperty("error") String error) {
this.sent = sent; this.sent = sent;
this.totalSent = null;
this.errors = errors; this.errors = errors;
this.totalErrors = null;
this.error = error; this.error = error;
this.processedRecipients = Collections.emptyMap(); this.processedRecipients = Collections.emptyMap();
} }
public void reportSent(NotificationDeliveryMethod deliveryMethod, NotificationRecipient recipient) { public void reportSent(NotificationDeliveryMethod deliveryMethod, NotificationRecipient recipient) {
sent.computeIfAbsent(deliveryMethod, k -> new AtomicInteger()).incrementAndGet(); sent.computeIfAbsent(deliveryMethod, k -> new AtomicInteger()).incrementAndGet();
totalSent.incrementAndGet();
} }
public void reportError(NotificationDeliveryMethod deliveryMethod, Throwable error, NotificationRecipient recipient) { public void reportError(NotificationDeliveryMethod deliveryMethod, Throwable error, NotificationRecipient recipient) {
@ -65,6 +74,7 @@ public class NotificationRequestStats {
errorMessage = error.getClass().getSimpleName(); errorMessage = error.getClass().getSimpleName();
} }
errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(recipient.getTitle(), errorMessage); errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(recipient.getTitle(), errorMessage);
totalErrors.incrementAndGet();
} }
public void reportProcessed(NotificationDeliveryMethod deliveryMethod, Object recipientId) { public void reportProcessed(NotificationDeliveryMethod deliveryMethod, Object recipientId) {

1
common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java

@ -31,4 +31,5 @@ public enum NotificationType {
RATE_LIMITS, RATE_LIMITS,
EDGE_CONNECTION, EDGE_CONNECTION,
EDGE_COMMUNICATION_FAILURE EDGE_COMMUNICATION_FAILURE
} }

7
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.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.CustomerId; 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.EntityId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
@ -55,6 +56,7 @@ public class AlarmAssignmentNotificationInfo implements RuleOriginatedNotificati
private AlarmSeverity alarmSeverity; private AlarmSeverity alarmSeverity;
private AlarmStatus alarmStatus; private AlarmStatus alarmStatus;
private CustomerId alarmCustomerId; private CustomerId alarmCustomerId;
private DashboardId dashboardId;
@Override @Override
public Map<String, String> getTemplateData() { public Map<String, String> getTemplateData() {
@ -94,4 +96,9 @@ public class AlarmAssignmentNotificationInfo implements RuleOriginatedNotificati
return alarmOriginator; return alarmOriginator;
} }
@Override
public DashboardId getDashboardId() {
return dashboardId;
}
} }

7
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.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.CustomerId; 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.EntityId;
import java.util.Map; import java.util.Map;
@ -50,6 +51,7 @@ public class AlarmCommentNotificationInfo implements RuleOriginatedNotificationI
private AlarmSeverity alarmSeverity; private AlarmSeverity alarmSeverity;
private AlarmStatus alarmStatus; private AlarmStatus alarmStatus;
private CustomerId alarmCustomerId; private CustomerId alarmCustomerId;
private DashboardId dashboardId;
@Override @Override
public Map<String, String> getTemplateData() { public Map<String, String> getTemplateData() {
@ -80,4 +82,9 @@ public class AlarmCommentNotificationInfo implements RuleOriginatedNotificationI
return alarmOriginator; return alarmOriginator;
} }
@Override
public DashboardId getDashboardId() {
return dashboardId;
}
} }

7
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.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.CustomerId; 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.EntityId;
import java.util.Map; import java.util.Map;
@ -45,6 +46,7 @@ public class AlarmNotificationInfo implements RuleOriginatedNotificationInfo {
private boolean acknowledged; private boolean acknowledged;
private boolean cleared; private boolean cleared;
private CustomerId alarmCustomerId; private CustomerId alarmCustomerId;
private DashboardId dashboardId;
@Override @Override
public Map<String, String> getTemplateData() { public Map<String, String> getTemplateData() {
@ -70,4 +72,9 @@ public class AlarmNotificationInfo implements RuleOriginatedNotificationInfo {
return alarmOriginator; return alarmOriginator;
} }
@Override
public DashboardId getDashboardId() {
return dashboardId;
}
} }

5
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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import java.util.Map; import java.util.Map;
@ -33,4 +34,8 @@ public interface NotificationInfo {
return null; return null;
} }
default DashboardId getDashboardId() {
return null;
}
} }

2
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 edgeEventRateLimits;
private String edgeEventRateLimitsPerEdge; private String edgeEventRateLimitsPerEdge;
private String edgeUplinkMessagesRateLimits;
private String edgeUplinkMessagesRateLimitsPerEdge;
private int defaultStorageTtlDays; private int defaultStorageTtlDays;
private int alarmsTtlDays; private int alarmsTtlDays;

8
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"> [type]="rateLimitsType.EDGE_EVENTS_PER_EDGE_RATE_LIMIT">
</tb-rate-limits> </tb-rate-limits>
</div> </div>
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="16px">
<tb-rate-limits fxFlex="50" formControlName="edgeUplinkMessagesRateLimits"
[type]="rateLimitsType.EDGE_UPLINK_MESSAGES_RATE_LIMIT">
</tb-rate-limits>
<tb-rate-limits fxFlex="50" formControlName="edgeUplinkMessagesRateLimitsPerEdge"
[type]="rateLimitsType.EDGE_UPLINK_MESSAGES_PER_EDGE_RATE_LIMIT">
</tb-rate-limits>
</div>
</ng-template> </ng-template>
</mat-expansion-panel> </mat-expansion-panel>
</fieldset> </fieldset>

4
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, []], wsUpdatesPerSessionRateLimit: [null, []],
cassandraQueryTenantRateLimitsConfiguration: [null, []], cassandraQueryTenantRateLimitsConfiguration: [null, []],
edgeEventRateLimits: [null, []], edgeEventRateLimits: [null, []],
edgeEventRateLimitsPerEdge: [null, []] edgeEventRateLimitsPerEdge: [null, []],
edgeUplinkMessagesRateLimits: [null, []],
edgeUplinkMessagesRateLimitsPerEdge: [null, []]
}); });
this.defaultTenantProfileConfigurationFormGroup.get('smsEnabled').valueChanges.pipe( this.defaultTenantProfileConfigurationFormGroup.get('smsEnabled').valueChanges.pipe(

8
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_REQUEST_RATE_LIMIT = 'TENANT_NOTIFICATION_REQUEST_RATE_LIMIT',
TENANT_NOTIFICATION_REQUESTS_PER_RULE_RATE_LIMIT = 'TENANT_NOTIFICATION_REQUESTS_PER_RULE_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_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<RateLimitsType, string>( export const rateLimitsLabelTranslationMap = new Map<RateLimitsType, string>(
@ -58,6 +60,8 @@ export const rateLimitsLabelTranslationMap = new Map<RateLimitsType, string>(
[RateLimitsType.TENANT_NOTIFICATION_REQUESTS_PER_RULE_RATE_LIMIT, 'tenant-profile.tenant-notification-requests-per-rule-rate-limit'], [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_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_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<RateLimitsType, strin
[RateLimitsType.TENANT_NOTIFICATION_REQUESTS_PER_RULE_RATE_LIMIT, 'tenant-profile.rate-limits.edit-tenant-notification-requests-per-rule-rate-limit-title'], [RateLimitsType.TENANT_NOTIFICATION_REQUESTS_PER_RULE_RATE_LIMIT, 'tenant-profile.rate-limits.edit-tenant-notification-requests-per-rule-rate-limit-title'],
[RateLimitsType.EDGE_EVENTS_RATE_LIMIT, 'tenant-profile.rate-limits.edit-edge-events-rate-limit'], [RateLimitsType.EDGE_EVENTS_RATE_LIMIT, 'tenant-profile.rate-limits.edit-edge-events-rate-limit'],
[RateLimitsType.EDGE_EVENTS_PER_EDGE_RATE_LIMIT, 'tenant-profile.rate-limits.edit-edge-events-per-edge-rate-limit'], [RateLimitsType.EDGE_EVENTS_PER_EDGE_RATE_LIMIT, 'tenant-profile.rate-limits.edit-edge-events-per-edge-rate-limit'],
[RateLimitsType.EDGE_UPLINK_MESSAGES_RATE_LIMIT, 'tenant-profile.rate-limits.edit-edge-uplink-messages-rate-limit'],
[RateLimitsType.EDGE_UPLINK_MESSAGES_PER_EDGE_RATE_LIMIT, 'tenant-profile.rate-limits.edit-edge-uplink-messages-per-edge-rate-limit'],
] ]
); );

8
ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts

@ -206,8 +206,8 @@ export class TbFlot {
if (this.settings.yaxis) { if (this.settings.yaxis) {
this.yaxis.font.color = this.settings.yaxis.color || this.yaxis.font.color; 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.min = isNumber(this.settings.yaxis.min) ? this.settings.yaxis.min : null;
this.yaxis.max = isDefined(this.settings.yaxis.max) ? this.settings.yaxis.max : 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.label = this.utils.customTranslation(this.settings.yaxis.title, this.settings.yaxis.title) || null;
this.yaxis.labelFont.color = this.yaxis.font.color; this.yaxis.labelFont.color = this.yaxis.font.color;
this.yaxis.labelFont.size = this.yaxis.font.size + 2; this.yaxis.labelFont.size = this.yaxis.font.size + 2;
@ -895,8 +895,8 @@ export class TbFlot {
tickSize = yaxis.tickSize; tickSize = yaxis.tickSize;
} }
const position = keySettings.axisPosition && keySettings.axisPosition.length ? keySettings.axisPosition : 'left'; const position = keySettings.axisPosition && keySettings.axisPosition.length ? keySettings.axisPosition : 'left';
const min = isDefined(keySettings.axisMin) ? keySettings.axisMin : yaxis.min; const min = isNumber(keySettings.axisMin) ? keySettings.axisMin : yaxis.min;
const max = isDefined(keySettings.axisMax) ? keySettings.axisMax : yaxis.max; const max = isNumber(keySettings.axisMax) ? keySettings.axisMax : yaxis.max;
yaxis.label = label; yaxis.label = label;
yaxis.min = min; yaxis.min = min;
yaxis.max = max; yaxis.max = max;

10
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"> [expanded]="actionButtonConfigForm.get('enabled').value">
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width"> <mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60"> <mat-panel-title fxFlex="60">
<mat-slide-toggle class="mat-slide" formControlName="enabled" (click)="$event.stopPropagation()" <div tb-hint-tooltip-icon="{{ sliderHint }}" class="tb-form-row no-border no-padding">
fxLayoutAlign="center"> <mat-slide-toggle class="mat-slide" formControlName="enabled" (click)="$event.stopPropagation()"
{{ actionTitle }} fxLayoutAlign="center">
</mat-slide-toggle> {{ actionTitle }}
</mat-slide-toggle>
</div>
</mat-panel-title> </mat-panel-title>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<ng-template matExpansionPanelContent class="tb-extension-panel"> <ng-template matExpansionPanelContent class="tb-extension-panel">

3
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() @Input()
actionTitle: string; actionTitle: string;
@Input()
sliderHint: string;
private hideButtonTextValue = false; private hideButtonTextValue = false;
get hideButtonText(): boolean { get hideButtonText(): boolean {

1
ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html

@ -124,6 +124,7 @@
<tb-notification-action-button-configuration <tb-notification-action-button-configuration
formControlName="onClick" formControlName="onClick"
hideButtonText hideButtonText
[sliderHint]="hotificationTapActionHint"
actionTitle="{{ 'notification.notification-tap-action' | translate }}"> actionTitle="{{ 'notification.notification-tap-action' | translate }}">
</tb-notification-action-button-configuration> </tb-notification-action-button-configuration>
</div> </div>

14
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 { Subject } from 'rxjs';
import { isDefinedAndNotNull } from '@core/utils'; import { isDefinedAndNotNull } from '@core/utils';
import { coerceBoolean } from '@shared/decorators/coercion'; import { coerceBoolean } from '@shared/decorators/coercion';
import { TranslateService } from '@ngx-translate/core';
@Component({ @Component({
selector: 'tb-template-configuration', selector: 'tb-template-configuration',
@ -96,7 +97,8 @@ export class NotificationTemplateConfigurationComponent implements OnDestroy, Co
private propagateChange = (v: any) => { }; private propagateChange = (v: any) => { };
private readonly destroy$ = new Subject<void>(); private readonly destroy$ = new Subject<void>();
constructor(private fb: FormBuilder) { constructor(private fb: FormBuilder,
private translate: TranslateService) {
this.templateConfigurationForm = this.buildForm(); this.templateConfigurationForm = this.buildForm();
this.templateConfigurationForm.valueChanges.pipe( this.templateConfigurationForm.valueChanges.pipe(
takeUntil(this.destroy$) 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(){ private updateDisabledForms(){
Object.values(NotificationDeliveryMethod).forEach((method) => { Object.values(NotificationDeliveryMethod).forEach((method) => {
const form = this.templateConfigurationForm.get(method); const form = this.templateConfigurationForm.get(method);

1
ui-ngx/src/app/shared/components/image/embed-image-dialog.component.scss

@ -16,6 +16,7 @@
:host { :host {
.mat-mdc-dialog-content { .mat-mdc-dialog-content {
display: flex; display: flex;
width: 780px;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 16px;
.tb-embed-image-text { .tb-embed-image-text {

4
ui-ngx/src/app/shared/components/image/image-dialog.component.ts

@ -66,7 +66,7 @@ export class ImageDialogComponent extends
this.image = data.image; this.image = data.image;
this.readonly = data.readonly; this.readonly = data.readonly;
this.imagePreviewData = { 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; let url;
if (result.base64) { if (result.base64) {
url = result.base64; url = result.base64;
} else if (this.image.public) {
url = `${this.image.publicLink}?ts=${new Date().getTime()}`;
} else { } else {
url = this.image.link; url = this.image.link;
} }

6
ui-ngx/src/app/shared/models/limited-api.models.ts

@ -26,7 +26,9 @@ export enum LimitedApi {
TRANSPORT_MESSAGES_PER_TENANT = 'TRANSPORT_MESSAGES_PER_TENANT', TRANSPORT_MESSAGES_PER_TENANT = 'TRANSPORT_MESSAGES_PER_TENANT',
TRANSPORT_MESSAGES_PER_DEVICE = 'TRANSPORT_MESSAGES_PER_DEVICE', TRANSPORT_MESSAGES_PER_DEVICE = 'TRANSPORT_MESSAGES_PER_DEVICE',
EDGE_EVENTS = 'EDGE_EVENTS', 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'
} }
export const LimitedApiTranslationMap = new Map<LimitedApi, string>( export const LimitedApiTranslationMap = new Map<LimitedApi, string>(
@ -43,5 +45,7 @@ export const LimitedApiTranslationMap = new Map<LimitedApi, string>(
[LimitedApi.TRANSPORT_MESSAGES_PER_DEVICE, 'api-limit.transport-messages-per-device'], [LimitedApi.TRANSPORT_MESSAGES_PER_DEVICE, 'api-limit.transport-messages-per-device'],
[LimitedApi.EDGE_EVENTS, 'api-limit.edge-events'], [LimitedApi.EDGE_EVENTS, 'api-limit.edge-events'],
[LimitedApi.EDGE_EVENTS_PER_EDGE, 'api-limit.edge-events-per-edge'], [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']
] ]
); );

2
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', * `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', '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.; * `limitLevelEntityType` - entity type of the limit level entity, e.g. 'Tenant', 'Device', 'Notification rule', 'Customer', etc.;
* `limitLevelEntityId` - id of the limit level entity; * `limitLevelEntityId` - id of the limit level entity;
* `limitLevelEntityName` - name of the limit level entity; * `limitLevelEntityName` - name of the limit level entity;

9
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -861,7 +861,9 @@
"transport-messages-per-device": "Transport messages per device", "transport-messages-per-device": "Transport messages per device",
"ws-updates-per-session": "WS updates per session", "ws-updates-per-session": "WS updates per session",
"edge-events": "Edge events", "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-log": {
"audit": "Audit", "audit": "Audit",
@ -3354,6 +3356,7 @@
"notification": "Notification", "notification": "Notification",
"notification-center": "Notification center", "notification-center": "Notification center",
"notification-tap-action": "Notification tap action", "notification-tap-action": "Notification tap action",
"notification-tap-action-hint": "If not enabled, the default alarm dashboard will be used",
"notify": "notify", "notify": "notify",
"notify-again": "Notify again", "notify-again": "Notify again",
"notify-alarm-action": { "notify-alarm-action": {
@ -4214,6 +4217,10 @@
"edit-edge-events-per-edge-rate-limit": "Edit edge events per edge rate limits", "edit-edge-events-per-edge-rate-limit": "Edit edge events per edge rate limits",
"edge-events-rate-limit": "Edge events", "edge-events-rate-limit": "Edge events",
"edge-events-per-edge-rate-limit": "Edge events per edge", "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", "messages-per": "messages per",
"not-set": "Not set", "not-set": "Not set",
"number-of-messages": "Number of messages", "number-of-messages": "Number of messages",

Loading…
Cancel
Save