committed by
GitHub
28 changed files with 597 additions and 13 deletions
@ -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; |
|||
} |
|||
|
|||
} |
|||
@ -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.common.data.notification.info; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
|
|||
import java.util.Map; |
|||
|
|||
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf; |
|||
|
|||
@Data |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
@Builder |
|||
public class EntitiesLimitIncreaseRequestNotificationInfo implements NotificationInfo { |
|||
|
|||
private EntityType entityType; |
|||
private String userEmail; |
|||
private String increaseLimitActionLabel; |
|||
private String increaseLimitLink; |
|||
private String baseUrl; |
|||
|
|||
@Override |
|||
public Map<String, String> getTemplateData() { |
|||
return mapOf( |
|||
"entityType", entityType.getNormalName(), |
|||
"userEmail", userEmail, |
|||
"increaseLimitActionLabel", increaseLimitActionLabel, |
|||
"increaseLimitLink", increaseLimitLink, |
|||
"baseUrl", baseUrl |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
<!-- |
|||
|
|||
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. |
|||
|
|||
--> |
|||
<div class="entity-limit-exceeded-dialog"> |
|||
<button mat-icon-button class="close-btn" (click)="cancel()"> |
|||
<tb-icon>close</tb-icon> |
|||
</button> |
|||
<div class="entity-limit-exceeded-bg"></div> |
|||
<div class="entity-limit-exceeded-title">{{ 'entity.limit-reached' | translate }}</div> |
|||
<div class="entity-limit-exceeded-container"> |
|||
<div class="entity-limit-exceeded-text" [innerHTML]="limitReachedText | safe: 'html'"></div> |
|||
</div> |
|||
<div class="flex flex-row justify-center gap-2"> |
|||
<button mat-flat-button color="primary" (click)="requestLimitIncrease($event)"> |
|||
{{ 'entity.request-limit-increase' | translate }} |
|||
</button> |
|||
<button mat-button (click)="cancel()"> |
|||
{{ 'action.cancel' | translate }} |
|||
</button> |
|||
</div> |
|||
<div class="request-entity-limit-increase-sysadmin-prompt"> |
|||
{{ 'entity.request-sysadmin-text' | translate }} |
|||
<a href="" (click)="loginAsSysAdmin($event)"> {{ 'entity.login-here' | translate }} </a> |
|||
{{ 'entity.to-increase-limit' | translate }} |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,97 @@ |
|||
/** |
|||
* 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. |
|||
*/ |
|||
@import '../../../../scss/constants'; |
|||
|
|||
.entity-limit-exceeded-dialog { |
|||
max-width: 100%; |
|||
height: 100%; |
|||
|
|||
@media #{$mat-gt-xs} { |
|||
max-width: 500px; |
|||
max-height: 80vh; |
|||
} |
|||
|
|||
position: relative; |
|||
padding: 40px 40px 24px 40px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 20px; |
|||
|
|||
.close-btn { |
|||
position: absolute; |
|||
top: 8px; |
|||
right: 8px; |
|||
color: rgba(0, 0, 0, 0.54); |
|||
} |
|||
|
|||
.entity-limit-exceeded-bg { |
|||
position: relative; |
|||
width: 100%; |
|||
height: 140px; |
|||
&:before { |
|||
content: ""; |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background: $tb-primary-color; |
|||
mask-image: url(/assets/entity-limit-reached.svg); |
|||
-webkit-mask-image: url(/assets/entity-limit-reached.svg); |
|||
mask-repeat: no-repeat; |
|||
-webkit-mask-repeat: no-repeat; |
|||
mask-size: contain; |
|||
-webkit-mask-size: contain; |
|||
mask-position: center; |
|||
-webkit-mask-position: center; |
|||
} |
|||
} |
|||
|
|||
.entity-limit-exceeded-title { |
|||
font-size: 24px; |
|||
font-style: normal; |
|||
font-weight: 500; |
|||
line-height: 32px; |
|||
color: rgba(0, 0, 0, 0.87); |
|||
text-align: center; |
|||
} |
|||
|
|||
.entity-limit-exceeded-container { |
|||
flex: 1; |
|||
overflow-y: auto; |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
.entity-limit-exceeded-text { |
|||
font-size: 16px; |
|||
font-style: normal; |
|||
font-weight: 400; |
|||
line-height: 24px; |
|||
color: rgba(0, 0, 0, 0.76); |
|||
text-align: center; |
|||
} |
|||
} |
|||
|
|||
.request-entity-limit-increase-sysadmin-prompt { |
|||
text-align: center; |
|||
font-size: 14px; |
|||
font-style: normal; |
|||
font-weight: 400; |
|||
line-height: 20px; |
|||
color: rgba(0, 0, 0, 0.54); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,100 @@ |
|||
///
|
|||
/// 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.
|
|||
///
|
|||
|
|||
import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; |
|||
import { Component, Inject, ViewEncapsulation } from '@angular/core'; |
|||
import { DialogComponent } from '@shared/components/dialog.component'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { Router } from '@angular/router'; |
|||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { DialogService } from '@core/services/dialog.service'; |
|||
import { AuthService } from '@core/auth/auth.service'; |
|||
import { TenantService } from '@core/http/tenant.service'; |
|||
import { getCurrentAuthUser } from '@core/auth/auth.selectors'; |
|||
import { NotificationService } from '@core/http/notification.service'; |
|||
|
|||
export interface EntityLimitExceededDialogData { |
|||
entityType: EntityType; |
|||
limit: number; |
|||
} |
|||
|
|||
// @dynamic
|
|||
@Component({ |
|||
selector: 'tb-entity-limit-exceeded-dialog', |
|||
templateUrl: './entity-limit-exceeded-dialog.component.html', |
|||
styleUrls: ['./entity-limit-exceeded-dialog.component.scss'], |
|||
encapsulation: ViewEncapsulation.None |
|||
}) |
|||
export class EntityLimitExceededDialogComponent extends DialogComponent<EntityLimitExceededDialogComponent> { |
|||
|
|||
limitReachedText: string; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
protected router: Router, |
|||
@Inject(MAT_DIALOG_DATA) public data: EntityLimitExceededDialogData, |
|||
public dialogRef: MatDialogRef<EntityLimitExceededDialogComponent>, |
|||
private authService: AuthService, |
|||
private dialogs: DialogService, |
|||
private translate: TranslateService, |
|||
private tenantService: TenantService, |
|||
private notificationService: NotificationService) { |
|||
super(store, router, dialogRef); |
|||
|
|||
let entitiesText: string; |
|||
if (data.limit > 1) { |
|||
entitiesText = data.limit + ' ' + (this.translate.instant(entityTypeTranslations.get(data.entityType).typePlural) as string).toLowerCase(); |
|||
} else { |
|||
entitiesText = '1 ' + (this.translate.instant(entityTypeTranslations.get(data.entityType).type) as string).toLowerCase(); |
|||
} |
|||
this.limitReachedText = this.translate.instant('entity.limit-reached-text', { entities: entitiesText, entity: (this.translate.instant(entityTypeTranslations.get(data.entityType).type) as string).toLowerCase() }); |
|||
} |
|||
|
|||
cancel(): void { |
|||
this.dialogRef.close(); |
|||
} |
|||
|
|||
requestLimitIncrease($event: Event) { |
|||
if ($event) { |
|||
$event.stopPropagation(); |
|||
} |
|||
this.notificationService.sendEntitiesLimitIncreaseRequest(this.data.entityType).subscribe( |
|||
() => { |
|||
this.dialogRef.close(); |
|||
this.dialogs.alert( |
|||
this.translate.instant('entity.increase-limit-request-sent-title'), |
|||
this.translate.instant('entity.increase-limit-request-sent-text'), |
|||
this.translate.instant('action.close') |
|||
); |
|||
} |
|||
); |
|||
} |
|||
|
|||
loginAsSysAdmin($event: Event) { |
|||
if ($event) { |
|||
$event.preventDefault(); |
|||
$event.stopPropagation(); |
|||
} |
|||
this.tenantService.getTenant(getCurrentAuthUser(this.store).tenantId).subscribe( |
|||
(tenant) => { |
|||
this.authService.redirectUrl = `/tenantProfiles/${tenant.tenantProfileId.id}`; |
|||
this.authService.logout(); |
|||
} |
|||
); |
|||
} |
|||
|
|||
} |
|||
|
After Width: | Height: | Size: 4.5 KiB |
@ -0,0 +1,46 @@ |
|||
#### Entity count limit increase request notification templatization |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
Notification subject and message fields support templatization. |
|||
The list of available templatization parameters depends on the template type. |
|||
See the available types and parameters below: |
|||
|
|||
Available template parameters: |
|||
|
|||
* `entityType` - one of: 'Device', 'Asset', 'Customer', 'User', 'Dashboard', 'Rule chain', 'Edge'; |
|||
* `userEmail` - email of the user who sends the request; |
|||
* `increaseLimitActionLabel` - label of the button used to open Limits Management page, for ex: 'Set new limit'; |
|||
* `increaseLimitLink` - link to the Limits Management page; |
|||
* `baseUrl` - used to construct the full URL for the Limits Management page in email notifications; |
|||
|
|||
Parameter names must be wrapped using `${...}`. For example: `${userEmail}`. |
|||
You may also modify the value of the parameter with one of the suffixes: |
|||
|
|||
* `upperCase`, for example - `${userEmail:upperCase}` |
|||
* `lowerCase`, for example - `${userEmail:lowerCase}` |
|||
* `capitalize`, for example - `${userEmail:capitalize}` |
|||
|
|||
<div class="divider"></div> |
|||
|
|||
##### Examples |
|||
|
|||
Let's assume the notification about the increasing limit of the maximum allowed devices for the tenant. |
|||
The following template: |
|||
|
|||
```text |
|||
${userEmail} has reached the maximum number of ${entityType:lowerCase}s allowed and is requesting an increase to the ${entityType:lowerCase} limit. |
|||
{:copy-code} |
|||
``` |
|||
|
|||
will be transformed to: |
|||
|
|||
```text |
|||
johndoe@company.com has reached the maximum number of devices allowed and is requesting an increase to the device limit. |
|||
``` |
|||
|
|||
<br/> |
|||
|
|||
<br> |
|||
<br> |
|||
Loading…
Reference in new issue