diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
index a58ae911a2..83603f7ea5 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -151,7 +151,7 @@ import org.thingsboard.server.dao.domain.DomainService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.DataValidationException;
-import org.thingsboard.server.dao.exception.EntitiesLimitException;
+import org.thingsboard.server.dao.exception.EntitiesLimitExceededException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.job.JobService;
import org.thingsboard.server.dao.mobile.MobileAppBundleService;
@@ -453,7 +453,7 @@ public abstract class BaseController {
}
if (exception instanceof ThingsboardException) {
return (ThingsboardException) exception;
- } else if (exception instanceof EntitiesLimitException) {
+ } else if (exception instanceof EntitiesLimitExceededException) {
return new ThingsboardException(exception, ThingsboardErrorCode.ENTITIES_LIMIT_EXCEEDED);
} else if (exception instanceof IllegalArgumentException || exception instanceof IncorrectParameterException
|| exception instanceof DataValidationException || cause instanceof IncorrectParameterException) {
diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardEntitiesLimitExceededResponse.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardEntitiesLimitExceededResponse.java
new file mode 100644
index 0000000000..c1faabb569
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardEntitiesLimitExceededResponse.java
@@ -0,0 +1,50 @@
+/**
+ * Copyright © 2016-2025 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.exception;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.springframework.http.HttpStatus;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
+
+@Schema
+public class ThingsboardEntitiesLimitExceededResponse extends ThingsboardErrorResponse {
+
+ private final EntityType entityType;
+
+ private final Long limit;
+
+ protected ThingsboardEntitiesLimitExceededResponse(String message, EntityType entityType, Long limit) {
+ super(message, ThingsboardErrorCode.ENTITIES_LIMIT_EXCEEDED, HttpStatus.FORBIDDEN);
+ this.entityType = entityType;
+ this.limit = limit;
+ }
+
+ public static ThingsboardEntitiesLimitExceededResponse of(final String message, final EntityType entityType, final Long limit) {
+ return new ThingsboardEntitiesLimitExceededResponse(message, entityType, limit);
+ }
+
+ @Schema(description = "Entity type", accessMode = Schema.AccessMode.READ_ONLY)
+ public EntityType getEntityType() {
+ return entityType;
+ }
+
+ @Schema(description = "Limit", accessMode = Schema.AccessMode.READ_ONLY)
+ public Long getLimit() {
+ return limit;
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java
index f38b86e394..8a564dedca 100644
--- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java
+++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java
@@ -15,10 +15,8 @@
*/
package org.thingsboard.server.exception;
-import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.http.HttpStatus;
-import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
@Schema
@@ -34,19 +32,9 @@ public class ThingsboardErrorResponse {
private final long timestamp;
- private EntityType entityType;
-
- private Long limit;
-
protected ThingsboardErrorResponse(final String message, final ThingsboardErrorCode errorCode, HttpStatus status) {
- this(message, errorCode, null, null, status);
- }
-
- protected ThingsboardErrorResponse(final String message, final ThingsboardErrorCode errorCode, EntityType entityType, Long limit, HttpStatus status) {
this.message = message;
this.errorCode = errorCode;
- this.entityType = entityType;
- this.limit = limit;
this.status = status;
this.timestamp = System.currentTimeMillis();
}
@@ -55,14 +43,6 @@ public class ThingsboardErrorResponse {
return new ThingsboardErrorResponse(message, errorCode, status);
}
- public static ThingsboardErrorResponse ofEntityLimitExceeded(final String message,
- EntityType entityType,
- Long limit,
- HttpStatus status) {
- return new ThingsboardErrorResponse(message, ThingsboardErrorCode.ENTITIES_LIMIT_EXCEEDED,
- entityType, limit, status);
- }
-
@Schema(description = "HTTP Response Status Code", example = "401", accessMode = Schema.AccessMode.READ_ONLY)
public Integer getStatus() {
return status.value();
@@ -84,7 +64,8 @@ public class ThingsboardErrorResponse {
"\n\n* `32` - Item not found (HTTP: 404 - Not Found)" +
"\n\n* `33` - Too many requests (HTTP: 429 - Too Many Requests)" +
"\n\n* `34` - Too many updates (Too many updates over Websocket session)" +
- "\n\n* `40` - Subscription violation (HTTP: 403 - Forbidden)",
+ "\n\n* `40` - Subscription violation (HTTP: 403 - Forbidden)" +
+ "\n\n* `41` - Entities limit exceeded (HTTP: 403 - Forbidden)",
example = "10", type = "integer",
accessMode = Schema.AccessMode.READ_ONLY)
public ThingsboardErrorCode getErrorCode() {
@@ -95,12 +76,4 @@ public class ThingsboardErrorResponse {
public long getTimestamp() {
return timestamp;
}
-
- public EntityType getEntityType() {
- return entityType;
- }
-
- public Long getLimit() {
- return limit;
- }
}
diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java
index e6e27e6a75..3ef24e2db9 100644
--- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java
+++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java
@@ -52,7 +52,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.msg.tools.MaxPayloadSizeExceededException;
import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
-import org.thingsboard.server.dao.exception.EntitiesLimitException;
+import org.thingsboard.server.dao.exception.EntitiesLimitExceededException;
import org.thingsboard.server.service.security.exception.AuthMethodNotSupportedException;
import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
import org.thingsboard.server.service.security.exception.UserPasswordExpiredException;
@@ -147,10 +147,10 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand
} else if (thingsboardException.getErrorCode() == ThingsboardErrorCode.DATABASE) {
handleDatabaseException(thingsboardException.getCause(), response);
} else if (thingsboardException.getErrorCode() == ThingsboardErrorCode.ENTITIES_LIMIT_EXCEEDED) {
- if (thingsboardException.getCause() instanceof EntitiesLimitException entitiesLimitException) {
- handleEntitiesLimitException(entitiesLimitException, response);
+ if (thingsboardException.getCause() instanceof EntitiesLimitExceededException entitiesLimitExceededException) {
+ handleEntitiesLimitExceededException(entitiesLimitExceededException, response);
} else {
- handleEntitiesLimitException(thingsboardException, response);
+ handleEntitiesLimitExceededException(thingsboardException, response);
}
} else {
handleThingsboardException(thingsboardException, response);
@@ -227,19 +227,18 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand
writeResponse(errorResponse, response);
}
- private void handleEntitiesLimitException(ThingsboardException entitiesLimitException, HttpServletResponse response) throws IOException {
+ private void handleEntitiesLimitExceededException(ThingsboardException entitiesLimitExceededException, HttpServletResponse response) throws IOException {
response.setStatus(HttpStatus.FORBIDDEN.value());
JacksonUtil.writeValue(response.getWriter(),
- JacksonUtil.fromBytes(((HttpClientErrorException) entitiesLimitException.getCause()).getResponseBodyAsByteArray(), Object.class));
+ JacksonUtil.fromBytes(((HttpClientErrorException) entitiesLimitExceededException.getCause()).getResponseBodyAsByteArray(), Object.class));
}
- private void handleEntitiesLimitException(EntitiesLimitException entitiesLimitException, HttpServletResponse response) throws IOException {
- EntityType entityType = entitiesLimitException.getEntityType();
- Long limit = entitiesLimitException.getLimit();
- HttpStatus status = HttpStatus.FORBIDDEN;
- response.setStatus(status.value());
+ private void handleEntitiesLimitExceededException(EntitiesLimitExceededException entitiesLimitExceededException, HttpServletResponse response) throws IOException {
+ EntityType entityType = entitiesLimitExceededException.getEntityType();
+ Long limit = entitiesLimitExceededException.getLimit();
+ response.setStatus(HttpStatus.FORBIDDEN.value());
JacksonUtil.writeValue(response.getWriter(),
- ThingsboardErrorResponse.ofEntityLimitExceeded(entitiesLimitException.getMessage(), entityType, limit, status));
+ ThingsboardEntitiesLimitExceededResponse.of(entitiesLimitExceededException.getMessage(), entityType, limit));
}
private void handleAccessDeniedException(HttpServletResponse response) throws IOException {
diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
index 7fee411704..096c1a814a 100644
--- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
+++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
@@ -76,7 +76,7 @@ import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.device.provision.ProvisionResponse;
import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus;
-import org.thingsboard.server.dao.exception.EntitiesLimitException;
+import org.thingsboard.server.dao.exception.EntitiesLimitExceededException;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.relation.RelationService;
@@ -398,7 +398,7 @@ public class DefaultTransportApiService implements TransportApiService {
} catch (JsonProcessingException e) {
log.warn("[{}] Failed to lookup device by gateway id and name: [{}]", gatewayId, requestMsg.getDeviceName(), e);
throw new RuntimeException(e);
- } catch (EntitiesLimitException e) {
+ } catch (EntitiesLimitExceededException e) {
log.warn("[{}][{}] API limit exception: [{}]", e.getTenantId(), gatewayId, e.getMessage());
return TransportApiResponseMsg.newBuilder()
.setGetOrCreateDeviceResponseMsg(
diff --git a/dao/src/main/java/org/thingsboard/server/dao/exception/EntitiesLimitException.java b/dao/src/main/java/org/thingsboard/server/dao/exception/EntitiesLimitExceededException.java
similarity index 87%
rename from dao/src/main/java/org/thingsboard/server/dao/exception/EntitiesLimitException.java
rename to dao/src/main/java/org/thingsboard/server/dao/exception/EntitiesLimitExceededException.java
index 559ef94e45..b2907c9104 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/exception/EntitiesLimitException.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/exception/EntitiesLimitExceededException.java
@@ -19,7 +19,7 @@ import lombok.Getter;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
-public class EntitiesLimitException extends DataValidationException {
+public class EntitiesLimitExceededException extends DataValidationException {
private static final long serialVersionUID = -9211462514373279196L;
@Getter
@@ -30,7 +30,7 @@ public class EntitiesLimitException extends DataValidationException {
@Getter
private final long limit;
- public EntitiesLimitException(TenantId tenantId, EntityType entityType, long limit) {
+ public EntitiesLimitExceededException(TenantId tenantId, EntityType entityType, long limit) {
super(entityType.getNormalName() + "s limit reached");
this.tenantId = tenantId;
this.entityType = entityType;
diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java
index 920fd8c144..e305ca556c 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java
@@ -113,7 +113,30 @@ public class DefaultNotifications {
.button("${increaseLimitActionLabel}").link("${increaseLimitLink}")
.emailTemplate(DefaultEmailTemplate.builder()
.subject("${entityType} limit increase request")
- .body("${userEmail} has reached the maximum number of ${entityType:lowerCase}s allowed and is requesting an increase to the ${entityType:lowerCase} limit.
${increaseLimitActionLabel}")
+ .body("""
+
+
+
+
+
+
+
+ | ${entityType} limit increase request |
+
+
+ | ${userEmail} has reached the maximum number of ${entityType:lowerCase}s allowed and is requesting an increase to the ${entityType:lowerCase} limit. |
+
+
+ |
+ ${increaseLimitActionLabel}
+ |
+
+
+
+ |
+
+
+
""")
.build())
.build();
public static final DefaultNotification apiFeatureWarningForSysadmin = DefaultNotification.builder()
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
index ffa88fe39c..89ab237ed3 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
@@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.TenantEntityWithDataDao;
import org.thingsboard.server.dao.exception.DataValidationException;
-import org.thingsboard.server.dao.exception.EntitiesLimitException;
+import org.thingsboard.server.dao.exception.EntitiesLimitExceededException;
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import java.util.HashSet;
@@ -124,7 +124,7 @@ public abstract class DataValidator> {
EntityType entityType) {
if (!apiLimitService.checkEntitiesLimit(tenantId, entityType)) {
long limit = apiLimitService.getLimit(tenantId, profileConfiguration -> profileConfiguration.getEntitiesLimit(entityType));
- throw new EntitiesLimitException(tenantId, entityType, limit);
+ throw new EntitiesLimitExceededException(tenantId, entityType, limit);
}
}
diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts
index 72315e8ed5..2788aa52df 100644
--- a/ui-ngx/src/app/shared/models/constants.ts
+++ b/ui-ngx/src/app/shared/models/constants.ts
@@ -53,6 +53,7 @@ export const serverErrorCodesTranslations = new Map([
[Constants.serverErrorCode.itemNotFound, 'server-error.item-not-found'],
[Constants.serverErrorCode.tooManyRequests, 'server-error.too-many-requests'],
[Constants.serverErrorCode.tooManyUpdates, 'server-error.too-many-updates'],
+ [Constants.serverErrorCode.entitiesLimitExceeded, 'server-error.entities-limit-exceeded'],
]);
export const MediaBreakpoints = {
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 7a7b5bff62..27bd757078 100644
--- a/ui-ngx/src/assets/locale/locale.constant-en_US.json
+++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json
@@ -6140,7 +6140,8 @@
"bad-request-params": "Bad request params",
"item-not-found": "Item not found",
"too-many-requests": "Too many requests",
- "too-many-updates": "Too many updates"
+ "too-many-updates": "Too many updates",
+ "entities-limit-exceeded": "Entities limit exceeded"
},
"tenant": {
"tenant": "Tenant",