Introduce Current user alias
This commit is contained in:
parent
c6f7862cbf
commit
7446e012a6
@ -246,6 +246,28 @@ public class UserController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
|
||||
@RequestMapping(value = "/users", params = {"pageSize", "page"}, method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public PageData<User> getUsers(
|
||||
@RequestParam int pageSize,
|
||||
@RequestParam int page,
|
||||
@RequestParam(required = false) String textSearch,
|
||||
@RequestParam(required = false) String sortProperty,
|
||||
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
|
||||
try {
|
||||
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
|
||||
SecurityUser currentUser = getCurrentUser();
|
||||
if (Authority.TENANT_ADMIN.equals(currentUser.getAuthority())) {
|
||||
return checkNotNull(userService.findUsersByTenantId(currentUser.getTenantId(), pageLink));
|
||||
} else {
|
||||
return checkNotNull(userService.findCustomerUsers(currentUser.getTenantId(), currentUser.getCustomerId(), pageLink));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_ADMIN')")
|
||||
@RequestMapping(value = "/tenant/{tenantId}/users", params = {"pageSize", "page"}, method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
|
||||
@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.Customer;
|
||||
import org.thingsboard.server.common.data.Device;
|
||||
import org.thingsboard.server.common.data.EntityView;
|
||||
import org.thingsboard.server.common.data.Tenant;
|
||||
import org.thingsboard.server.common.data.User;
|
||||
import org.thingsboard.server.common.data.asset.Asset;
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardException;
|
||||
import org.thingsboard.server.common.data.id.AssetId;
|
||||
@ -40,6 +41,7 @@ import org.thingsboard.server.common.data.id.EntityViewId;
|
||||
import org.thingsboard.server.common.data.id.RuleChainId;
|
||||
import org.thingsboard.server.common.data.id.RuleNodeId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.id.UserId;
|
||||
import org.thingsboard.server.common.data.rule.RuleChain;
|
||||
import org.thingsboard.server.common.data.rule.RuleNode;
|
||||
import org.thingsboard.server.controller.HttpValidationCallback;
|
||||
@ -172,6 +174,9 @@ public class AccessValidator {
|
||||
case TENANT:
|
||||
validateTenant(currentUser, operation, entityId, callback);
|
||||
return;
|
||||
case USER:
|
||||
validateUser(currentUser, operation, entityId, callback);
|
||||
return;
|
||||
case ENTITY_VIEW:
|
||||
validateEntityView(currentUser, operation, entityId, callback);
|
||||
return;
|
||||
@ -308,6 +313,22 @@ public class AccessValidator {
|
||||
}
|
||||
}
|
||||
|
||||
private void validateUser(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
|
||||
ListenableFuture<User> userFuture = userService.findUserByIdAsync(currentUser.getTenantId(), new UserId(entityId.getId()));
|
||||
Futures.addCallback(userFuture, getCallback(callback, user -> {
|
||||
if (user == null) {
|
||||
return ValidationResult.entityNotFound("User with requested id wasn't found!");
|
||||
}
|
||||
try {
|
||||
accessControlService.checkPermission(currentUser, Resource.USER, operation, entityId, user);
|
||||
} catch (ThingsboardException e) {
|
||||
return ValidationResult.accessDenied(e.getMessage());
|
||||
}
|
||||
return ValidationResult.ok(user);
|
||||
|
||||
}), executor);
|
||||
}
|
||||
|
||||
private void validateEntityView(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
|
||||
if (currentUser.isSystemAdmin()) {
|
||||
callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
|
||||
|
||||
@ -53,7 +53,9 @@ public interface UserService {
|
||||
|
||||
void deleteUser(TenantId tenantId, UserId userId);
|
||||
|
||||
PageData<User> findTenantAdmins(TenantId tenantId, PageLink pageLink);
|
||||
PageData<User> findUsersByTenantId(TenantId tenantId, PageLink pageLink);
|
||||
|
||||
PageData<User> findTenantAdmins(TenantId tenantId, PageLink pageLink);
|
||||
|
||||
void deleteTenantAdmins(TenantId tenantId);
|
||||
|
||||
|
||||
@ -86,7 +86,7 @@ public class EntityKeyMapping {
|
||||
allowedEntityFieldMap.get(EntityType.TENANT).add(REGION);
|
||||
allowedEntityFieldMap.put(EntityType.CUSTOMER, new HashSet<>(contactBasedEntityFields));
|
||||
|
||||
allowedEntityFieldMap.put(EntityType.USER, new HashSet<>(Arrays.asList(FIRST_NAME, LAST_NAME, EMAIL)));
|
||||
allowedEntityFieldMap.put(EntityType.USER, new HashSet<>(Arrays.asList(CREATED_TIME, FIRST_NAME, LAST_NAME, EMAIL)));
|
||||
|
||||
allowedEntityFieldMap.put(EntityType.DASHBOARD, new HashSet<>(commonEntityFields));
|
||||
allowedEntityFieldMap.put(EntityType.RULE_CHAIN, new HashSet<>(commonEntityFields));
|
||||
@ -377,28 +377,30 @@ public class EntityKeyMapping {
|
||||
}
|
||||
|
||||
private String buildSimplePredicateQuery(EntityQueryContext ctx, String alias, EntityKey key, KeyFilterPredicate predicate) {
|
||||
if (predicate.getType().equals(FilterPredicateType.NUMERIC)) {
|
||||
if (key.getType().equals(EntityKeyType.ENTITY_FIELD)) {
|
||||
String column = entityFieldColumnMap.get(key.getKey());
|
||||
return this.buildNumericPredicateQuery(ctx, alias + "." + column, (NumericFilterPredicate) predicate);
|
||||
} else {
|
||||
String longQuery = this.buildNumericPredicateQuery(ctx, alias + ".long_v", (NumericFilterPredicate) predicate);
|
||||
String doubleQuery = this.buildNumericPredicateQuery(ctx, alias + ".dbl_v", (NumericFilterPredicate) predicate);
|
||||
return String.format("(%s or %s)", longQuery, doubleQuery);
|
||||
}
|
||||
} else {
|
||||
String column;
|
||||
if (key.getType().equals(EntityKeyType.ENTITY_FIELD)) {
|
||||
column = entityFieldColumnMap.get(key.getKey());
|
||||
} else {
|
||||
column = predicate.getType().equals(FilterPredicateType.STRING) ? "str_v" : "bool_v";
|
||||
}
|
||||
if (key.getType().equals(EntityKeyType.ENTITY_FIELD)) {
|
||||
String column = entityFieldColumnMap.get(key.getKey());
|
||||
String field = alias + "." + column;
|
||||
if (predicate.getType().equals(FilterPredicateType.STRING)) {
|
||||
if (predicate.getType().equals(FilterPredicateType.NUMERIC)) {
|
||||
return this.buildNumericPredicateQuery(ctx, field, (NumericFilterPredicate) predicate);
|
||||
} else if (predicate.getType().equals(FilterPredicateType.STRING)) {
|
||||
return this.buildStringPredicateQuery(ctx, field, (StringFilterPredicate) predicate);
|
||||
} else {
|
||||
return this.buildBooleanPredicateQuery(ctx, field, (BooleanFilterPredicate) predicate);
|
||||
}
|
||||
} else {
|
||||
if (predicate.getType().equals(FilterPredicateType.NUMERIC)) {
|
||||
String longQuery = this.buildNumericPredicateQuery(ctx, alias + ".long_v", (NumericFilterPredicate) predicate);
|
||||
String doubleQuery = this.buildNumericPredicateQuery(ctx, alias + ".dbl_v", (NumericFilterPredicate) predicate);
|
||||
return String.format("(%s or %s)", longQuery, doubleQuery);
|
||||
} else {
|
||||
String column = predicate.getType().equals(FilterPredicateType.STRING) ? "str_v" : "bool_v";
|
||||
String field = alias + "." + column;
|
||||
if (predicate.getType().equals(FilterPredicateType.STRING)) {
|
||||
return this.buildStringPredicateQuery(ctx, field, (StringFilterPredicate) predicate);
|
||||
} else {
|
||||
return this.buildBooleanPredicateQuery(ctx, field, (BooleanFilterPredicate) predicate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -59,6 +59,16 @@ public class JpaUserDao extends JpaAbstractSearchTextDao<UserEntity, User> imple
|
||||
return DaoUtil.getData(userRepository.findByEmail(email));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<User> findByTenantId(UUID tenantId, PageLink pageLink) {
|
||||
return DaoUtil.toPageData(
|
||||
userRepository
|
||||
.findByTenantId(
|
||||
tenantId,
|
||||
Objects.toString(pageLink.getTextSearch(), ""),
|
||||
DaoUtil.toPageable(pageLink)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<User> findTenantAdmins(UUID tenantId, PageLink pageLink) {
|
||||
return DaoUtil.toPageData(
|
||||
|
||||
@ -43,4 +43,10 @@ public interface UserRepository extends PagingAndSortingRepository<UserEntity, U
|
||||
@Param("authority") Authority authority,
|
||||
Pageable pageable);
|
||||
|
||||
@Query("SELECT u FROM UserEntity u WHERE u.tenantId = :tenantId " +
|
||||
"AND LOWER(u.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
|
||||
Page<UserEntity> findByTenantId(@Param("tenantId") UUID tenantId,
|
||||
@Param("searchText") String searchText,
|
||||
Pageable pageable);
|
||||
|
||||
}
|
||||
|
||||
@ -41,6 +41,15 @@ public interface UserDao extends Dao<User> {
|
||||
*/
|
||||
User findByEmail(TenantId tenantId, String email);
|
||||
|
||||
/**
|
||||
* Find users by tenantId and page link.
|
||||
*
|
||||
* @param tenantId the tenantId
|
||||
* @param pageLink the page link
|
||||
* @return the list of user entities
|
||||
*/
|
||||
PageData<User> findByTenantId(UUID tenantId, PageLink pageLink);
|
||||
|
||||
/**
|
||||
* Find tenant admin users by tenantId and page link.
|
||||
*
|
||||
|
||||
@ -219,6 +219,14 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
|
||||
userDao.removeById(tenantId, userId.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<User> findUsersByTenantId(TenantId tenantId, PageLink pageLink) {
|
||||
log.trace("Executing findUsersByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
|
||||
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
|
||||
validatePageLink(pageLink);
|
||||
return userDao.findByTenantId(tenantId.getId(), pageLink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<User> findTenantAdmins(TenantId tenantId, PageLink pageLink) {
|
||||
log.trace("Executing findTenantAdmins, tenantId [{}], pageLink [{}]", tenantId, pageLink);
|
||||
|
||||
@ -310,7 +310,8 @@ export class EntityService {
|
||||
}
|
||||
break;
|
||||
case EntityType.USER:
|
||||
console.error('Get User Entities is not implemented!');
|
||||
pageLink.sortOrder.property = 'email';
|
||||
entitiesObservable = this.userService.getUsers(pageLink);
|
||||
break;
|
||||
case EntityType.ALARM:
|
||||
console.error('Get Alarm Entities is not implemented!');
|
||||
@ -548,6 +549,7 @@ export class EntityService {
|
||||
entityTypes.push(EntityType.ENTITY_VIEW);
|
||||
entityTypes.push(EntityType.TENANT);
|
||||
entityTypes.push(EntityType.CUSTOMER);
|
||||
entityTypes.push(EntityType.USER);
|
||||
entityTypes.push(EntityType.DASHBOARD);
|
||||
if (useAliasEntityTypes) {
|
||||
entityTypes.push(AliasEntityType.CURRENT_CUSTOMER);
|
||||
@ -559,12 +561,16 @@ export class EntityService {
|
||||
entityTypes.push(EntityType.ASSET);
|
||||
entityTypes.push(EntityType.ENTITY_VIEW);
|
||||
entityTypes.push(EntityType.CUSTOMER);
|
||||
entityTypes.push(EntityType.USER);
|
||||
entityTypes.push(EntityType.DASHBOARD);
|
||||
if (useAliasEntityTypes) {
|
||||
entityTypes.push(AliasEntityType.CURRENT_CUSTOMER);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (useAliasEntityTypes) {
|
||||
entityTypes.push(AliasEntityType.CURRENT_USER);
|
||||
}
|
||||
if (allowedEntityTypes && allowedEntityTypes.length) {
|
||||
for (let index = entityTypes.length - 1; index >= 0; index--) {
|
||||
if (allowedEntityTypes.indexOf(entityTypes[index]) === -1) {
|
||||
@ -961,6 +967,10 @@ export class EntityService {
|
||||
const authUser = getCurrentAuthUser(this.store);
|
||||
entityId.entityType = EntityType.TENANT;
|
||||
entityId.id = authUser.tenantId;
|
||||
} else if (entityType === AliasEntityType.CURRENT_USER){
|
||||
const authUser = getCurrentAuthUser(this.store);
|
||||
entityId.entityType = EntityType.USER;
|
||||
entityId.id = authUser.userId;
|
||||
}
|
||||
return entityId;
|
||||
}
|
||||
|
||||
@ -32,6 +32,12 @@ export class UserService {
|
||||
private http: HttpClient
|
||||
) { }
|
||||
|
||||
public getUsers(pageLink: PageLink,
|
||||
config?: RequestConfig): Observable<PageData<User>> {
|
||||
return this.http.get<PageData<User>>(`/api/users${pageLink.toQuery()}`,
|
||||
defaultHttpOptionsFromConfig(config));
|
||||
}
|
||||
|
||||
public getTenantAdmins(tenantId: string, pageLink: PageLink,
|
||||
config?: RequestConfig): Observable<PageData<User>> {
|
||||
return this.http.get<PageData<User>>(`/api/tenant/${tenantId}/users${pageLink.toQuery()}`,
|
||||
|
||||
@ -28,6 +28,7 @@ import {
|
||||
AliasesEntitySelectPanelData
|
||||
} from './aliases-entity-select-panel.component';
|
||||
import { deepClone } from '@core/utils';
|
||||
import { AliasFilterType } from '@shared/models/alias.models';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-aliases-entity-select',
|
||||
@ -178,7 +179,7 @@ export class AliasesEntitySelectComponent implements OnInit, OnDestroy {
|
||||
for (const aliasId of Object.keys(allEntityAliases)) {
|
||||
const aliasInfo = this.aliasController.getInstantAliasInfo(aliasId);
|
||||
if (aliasInfo && !aliasInfo.resolveMultiple && aliasInfo.currentEntity
|
||||
&& aliasInfo.entityFilter) {
|
||||
&& aliasInfo.entityFilter && aliasInfo.entityFilter.type !== AliasFilterType.singleEntity) {
|
||||
this.entityAliasesInfo[aliasId] = deepClone(aliasInfo);
|
||||
this.hasSelectableAliasEntities = true;
|
||||
}
|
||||
|
||||
@ -84,6 +84,9 @@ import {
|
||||
KeyFilter
|
||||
} from '@shared/models/query/query.models';
|
||||
import { sortItems } from '@shared/models/page/page-link';
|
||||
import { entityFields } from '@shared/models/entity.models';
|
||||
import { alarmFields } from '@shared/models/alarm.models';
|
||||
import { DatePipe } from '@angular/common';
|
||||
|
||||
interface EntitiesTableWidgetSettings extends TableWidgetSettings {
|
||||
entitiesTitle: string;
|
||||
@ -153,6 +156,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
|
||||
private overlay: Overlay,
|
||||
private viewContainerRef: ViewContainerRef,
|
||||
private utils: UtilsService,
|
||||
private datePipe: DatePipe,
|
||||
private translate: TranslateService,
|
||||
private domSanitizer: DomSanitizer) {
|
||||
super(store);
|
||||
@ -511,9 +515,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
|
||||
content = '' + value;
|
||||
}
|
||||
} else {
|
||||
const decimals = (contentInfo.decimals || contentInfo.decimals === 0) ? contentInfo.decimals : this.ctx.widgetConfig.decimals;
|
||||
const units = contentInfo.units || this.ctx.widgetConfig.units;
|
||||
content = this.ctx.utils.formatValue(value, decimals, units, true);
|
||||
content = this.defaultContent(key, contentInfo, value);
|
||||
}
|
||||
return isDefined(content) ? this.domSanitizer.bypassSecurityTrustHtml(content) : '';
|
||||
} else {
|
||||
@ -521,6 +523,22 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
|
||||
}
|
||||
}
|
||||
|
||||
private defaultContent(key: EntityColumn, contentInfo: CellContentInfo, value: any): any {
|
||||
if (isDefined(value)) {
|
||||
const entityField = entityFields[key.name];
|
||||
if (entityField) {
|
||||
if (entityField.time) {
|
||||
return this.datePipe.transform(value, 'yyyy-MM-dd HH:mm:ss');
|
||||
}
|
||||
}
|
||||
const decimals = (contentInfo.decimals || contentInfo.decimals === 0) ? contentInfo.decimals : this.ctx.widgetConfig.decimals;
|
||||
const units = contentInfo.units || this.ctx.widgetConfig.units;
|
||||
return this.ctx.utils.formatValue(value, decimals, units, true);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
public onRowClick($event: Event, entity: EntityData, isDouble?: boolean) {
|
||||
if ($event) {
|
||||
$event.stopPropagation();
|
||||
|
||||
@ -15,6 +15,23 @@
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<mat-tab *ngIf="entity"
|
||||
label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab">
|
||||
<tb-attribute-table [defaultAttributeScope]="attributeScopes.SERVER_SCOPE"
|
||||
[active]="attributesTab.isActive"
|
||||
[entityId]="entity.id"
|
||||
[entityName]="entity.name">
|
||||
</tb-attribute-table>
|
||||
</mat-tab>
|
||||
<mat-tab *ngIf="entity"
|
||||
label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab">
|
||||
<tb-attribute-table [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY"
|
||||
disableAttributeScopeSelection
|
||||
[active]="telemetryTab.isActive"
|
||||
[entityId]="entity.id"
|
||||
[entityName]="entity.name">
|
||||
</tb-attribute-table>
|
||||
</mat-tab>
|
||||
<mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN"
|
||||
label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
|
||||
<tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.USER" [userId]="entity.id" detailsMode="true"></tb-audit-log-table>
|
||||
|
||||
@ -175,6 +175,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
|
||||
this.entityRequiredText = 'customer.customer-required';
|
||||
break;
|
||||
case EntityType.USER:
|
||||
case AliasEntityType.CURRENT_USER:
|
||||
this.entityText = 'user.user';
|
||||
this.noEntitiesMatchingText = 'user.no-users-matching';
|
||||
this.entityRequiredText = 'user.user-required';
|
||||
@ -324,6 +325,8 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
|
||||
return EntityType.CUSTOMER;
|
||||
} else if (entityType === AliasEntityType.CURRENT_TENANT) {
|
||||
return EntityType.TENANT;
|
||||
} else if (entityType === AliasEntityType.CURRENT_USER) {
|
||||
return EntityType.USER;
|
||||
}
|
||||
return entityType;
|
||||
}
|
||||
|
||||
@ -27,7 +27,8 @@
|
||||
</tb-entity-type-select>
|
||||
<tb-entity-autocomplete
|
||||
fxFlex
|
||||
*ngIf="modelValue.entityType && modelValue.entityType !== AliasEntityType.CURRENT_TENANT"
|
||||
*ngIf="modelValue.entityType && modelValue.entityType !== AliasEntityType.CURRENT_TENANT
|
||||
&& modelValue.entityType !== AliasEntityType.CURRENT_USER"
|
||||
[required]="required"
|
||||
[entityType]="modelValue.entityType"
|
||||
formControlName="entityId">
|
||||
|
||||
@ -97,7 +97,7 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte
|
||||
ngOnInit() {
|
||||
this.entitySelectFormGroup.get('entityType').valueChanges.subscribe(
|
||||
(value) => {
|
||||
if(value === AliasEntityType.CURRENT_TENANT){
|
||||
if(value === AliasEntityType.CURRENT_TENANT || value === AliasEntityType.CURRENT_USER) {
|
||||
this.modelValue.id = NULL_UUID;
|
||||
}
|
||||
this.updateView(value, this.modelValue.id);
|
||||
@ -145,7 +145,9 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte
|
||||
entityType,
|
||||
id: this.modelValue.entityType !== entityType ? null : entityId
|
||||
};
|
||||
if (this.modelValue.entityType && (this.modelValue.id || this.modelValue.entityType === AliasEntityType.CURRENT_TENANT)) {
|
||||
if (this.modelValue.entityType && (this.modelValue.id ||
|
||||
this.modelValue.entityType === AliasEntityType.CURRENT_TENANT ||
|
||||
this.modelValue.entityType === AliasEntityType.CURRENT_USER)) {
|
||||
this.propagateChange(this.modelValue);
|
||||
} else {
|
||||
this.propagateChange(null);
|
||||
|
||||
@ -50,7 +50,8 @@ export enum EntityType {
|
||||
|
||||
export enum AliasEntityType {
|
||||
CURRENT_CUSTOMER = 'CURRENT_CUSTOMER',
|
||||
CURRENT_TENANT = 'CURRENT_TENANT'
|
||||
CURRENT_TENANT = 'CURRENT_TENANT',
|
||||
CURRENT_USER = 'CURRENT_USER'
|
||||
}
|
||||
|
||||
export interface EntityTypeTranslation {
|
||||
@ -229,6 +230,13 @@ export const entityTypeTranslations = new Map<EntityType | AliasEntityType, Enti
|
||||
type: 'entity.type-current-tenant',
|
||||
list: 'entity.type-current-tenant'
|
||||
}
|
||||
],
|
||||
[
|
||||
AliasEntityType.CURRENT_USER,
|
||||
{
|
||||
type: 'entity.type-current-user',
|
||||
list: 'entity.type-current-user'
|
||||
}
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
@ -841,6 +841,7 @@
|
||||
"rulenode-name-starts-with": "Rule nodes whose names start with '{{prefix}}'",
|
||||
"type-current-customer": "Current Customer",
|
||||
"type-current-tenant": "Current Tenant",
|
||||
"type-current-user": "Current User",
|
||||
"search": "Search entities",
|
||||
"selected-entities": "{ count, plural, 1 {1 entity} other {# entities} } selected",
|
||||
"entity-name": "Entity name",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user