Showing
19 changed files
with
245 additions
and
65 deletions
... | ... | @@ -246,6 +246,28 @@ public class UserController extends BaseController { |
246 | 246 | } |
247 | 247 | } |
248 | 248 | |
249 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | |
250 | + @RequestMapping(value = "/users", params = {"pageSize", "page"}, method = RequestMethod.GET) | |
251 | + @ResponseBody | |
252 | + public PageData<User> getUsers( | |
253 | + @RequestParam int pageSize, | |
254 | + @RequestParam int page, | |
255 | + @RequestParam(required = false) String textSearch, | |
256 | + @RequestParam(required = false) String sortProperty, | |
257 | + @RequestParam(required = false) String sortOrder) throws ThingsboardException { | |
258 | + try { | |
259 | + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); | |
260 | + SecurityUser currentUser = getCurrentUser(); | |
261 | + if (Authority.TENANT_ADMIN.equals(currentUser.getAuthority())) { | |
262 | + return checkNotNull(userService.findUsersByTenantId(currentUser.getTenantId(), pageLink)); | |
263 | + } else { | |
264 | + return checkNotNull(userService.findCustomerUsers(currentUser.getTenantId(), currentUser.getCustomerId(), pageLink)); | |
265 | + } | |
266 | + } catch (Exception e) { | |
267 | + throw handleException(e); | |
268 | + } | |
269 | + } | |
270 | + | |
249 | 271 | @PreAuthorize("hasAuthority('SYS_ADMIN')") |
250 | 272 | @RequestMapping(value = "/tenant/{tenantId}/users", params = {"pageSize", "page"}, method = RequestMethod.GET) |
251 | 273 | @ResponseBody | ... | ... |
... | ... | @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.Customer; |
29 | 29 | import org.thingsboard.server.common.data.Device; |
30 | 30 | import org.thingsboard.server.common.data.EntityView; |
31 | 31 | import org.thingsboard.server.common.data.Tenant; |
32 | +import org.thingsboard.server.common.data.User; | |
32 | 33 | import org.thingsboard.server.common.data.asset.Asset; |
33 | 34 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
34 | 35 | import org.thingsboard.server.common.data.id.AssetId; |
... | ... | @@ -40,6 +41,7 @@ import org.thingsboard.server.common.data.id.EntityViewId; |
40 | 41 | import org.thingsboard.server.common.data.id.RuleChainId; |
41 | 42 | import org.thingsboard.server.common.data.id.RuleNodeId; |
42 | 43 | import org.thingsboard.server.common.data.id.TenantId; |
44 | +import org.thingsboard.server.common.data.id.UserId; | |
43 | 45 | import org.thingsboard.server.common.data.rule.RuleChain; |
44 | 46 | import org.thingsboard.server.common.data.rule.RuleNode; |
45 | 47 | import org.thingsboard.server.controller.HttpValidationCallback; |
... | ... | @@ -172,6 +174,9 @@ public class AccessValidator { |
172 | 174 | case TENANT: |
173 | 175 | validateTenant(currentUser, operation, entityId, callback); |
174 | 176 | return; |
177 | + case USER: | |
178 | + validateUser(currentUser, operation, entityId, callback); | |
179 | + return; | |
175 | 180 | case ENTITY_VIEW: |
176 | 181 | validateEntityView(currentUser, operation, entityId, callback); |
177 | 182 | return; |
... | ... | @@ -308,6 +313,22 @@ public class AccessValidator { |
308 | 313 | } |
309 | 314 | } |
310 | 315 | |
316 | + private void validateUser(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) { | |
317 | + ListenableFuture<User> userFuture = userService.findUserByIdAsync(currentUser.getTenantId(), new UserId(entityId.getId())); | |
318 | + Futures.addCallback(userFuture, getCallback(callback, user -> { | |
319 | + if (user == null) { | |
320 | + return ValidationResult.entityNotFound("User with requested id wasn't found!"); | |
321 | + } | |
322 | + try { | |
323 | + accessControlService.checkPermission(currentUser, Resource.USER, operation, entityId, user); | |
324 | + } catch (ThingsboardException e) { | |
325 | + return ValidationResult.accessDenied(e.getMessage()); | |
326 | + } | |
327 | + return ValidationResult.ok(user); | |
328 | + | |
329 | + }), executor); | |
330 | + } | |
331 | + | |
311 | 332 | private void validateEntityView(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) { |
312 | 333 | if (currentUser.isSystemAdmin()) { |
313 | 334 | callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); | ... | ... |
... | ... | @@ -52,8 +52,10 @@ public interface UserService { |
52 | 52 | UserCredentials replaceUserCredentials(TenantId tenantId, UserCredentials userCredentials); |
53 | 53 | |
54 | 54 | void deleteUser(TenantId tenantId, UserId userId); |
55 | - | |
56 | - PageData<User> findTenantAdmins(TenantId tenantId, PageLink pageLink); | |
55 | + | |
56 | + PageData<User> findUsersByTenantId(TenantId tenantId, PageLink pageLink); | |
57 | + | |
58 | + PageData<User> findTenantAdmins(TenantId tenantId, PageLink pageLink); | |
57 | 59 | |
58 | 60 | void deleteTenantAdmins(TenantId tenantId); |
59 | 61 | ... | ... |
... | ... | @@ -254,7 +254,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { |
254 | 254 | |
255 | 255 | String entityWhereClause = this.buildEntityWhere(ctx, tenantId, customerId, query.getEntityFilter(), entityFieldsFiltersMapping, entityType); |
256 | 256 | String latestJoins = EntityKeyMapping.buildLatestJoins(ctx, query.getEntityFilter(), entityType, allLatestMappings); |
257 | - String whereClause = this.buildWhere(ctx, latestFiltersMapping); | |
257 | + String whereClause = this.buildWhere(ctx, latestFiltersMapping, query.getEntityFilter().getType(), entityType); | |
258 | 258 | String textSearchQuery = this.buildTextSearchQuery(ctx, selectionMapping, pageLink.getTextSearch()); |
259 | 259 | String entityFieldsSelection = EntityKeyMapping.buildSelections(entityFieldsSelectionMapping, query.getEntityFilter().getType(), entityType); |
260 | 260 | String entityTypeStr; |
... | ... | @@ -316,7 +316,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { |
316 | 316 | EntityType entityType) { |
317 | 317 | String permissionQuery = this.buildPermissionQuery(ctx, entityFilter, tenantId, customerId, entityType); |
318 | 318 | String entityFilterQuery = this.buildEntityFilterQuery(ctx, entityFilter); |
319 | - String entityFieldsQuery = EntityKeyMapping.buildQuery(ctx, entityFieldsFilters); | |
319 | + String entityFieldsQuery = EntityKeyMapping.buildQuery(ctx, entityFieldsFilters, entityFilter.getType(), entityType); | |
320 | 320 | String result = permissionQuery; |
321 | 321 | if (!entityFilterQuery.isEmpty()) { |
322 | 322 | result += " and " + entityFilterQuery; |
... | ... | @@ -478,8 +478,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { |
478 | 478 | return from; |
479 | 479 | } |
480 | 480 | |
481 | - private String buildWhere(QueryContext ctx, List<EntityKeyMapping> latestFiltersMapping) { | |
482 | - String latestFilters = EntityKeyMapping.buildQuery(ctx, latestFiltersMapping); | |
481 | + private String buildWhere(QueryContext ctx, List<EntityKeyMapping> latestFiltersMapping, EntityFilterType filterType, EntityType entityType) { | |
482 | + String latestFilters = EntityKeyMapping.buildQuery(ctx, latestFiltersMapping, filterType, entityType); | |
483 | 483 | if (!StringUtils.isEmpty(latestFilters)) { |
484 | 484 | return String.format("where %s", latestFilters); |
485 | 485 | } else { | ... | ... |
... | ... | @@ -40,6 +40,7 @@ import java.util.HashMap; |
40 | 40 | import java.util.HashSet; |
41 | 41 | import java.util.List; |
42 | 42 | import java.util.Map; |
43 | +import java.util.Objects; | |
43 | 44 | import java.util.Optional; |
44 | 45 | import java.util.Set; |
45 | 46 | import java.util.stream.Collectors; |
... | ... | @@ -86,7 +87,7 @@ public class EntityKeyMapping { |
86 | 87 | allowedEntityFieldMap.get(EntityType.TENANT).add(REGION); |
87 | 88 | allowedEntityFieldMap.put(EntityType.CUSTOMER, new HashSet<>(contactBasedEntityFields)); |
88 | 89 | |
89 | - allowedEntityFieldMap.put(EntityType.USER, new HashSet<>(Arrays.asList(FIRST_NAME, LAST_NAME, EMAIL))); | |
90 | + allowedEntityFieldMap.put(EntityType.USER, new HashSet<>(Arrays.asList(CREATED_TIME, FIRST_NAME, LAST_NAME, EMAIL))); | |
90 | 91 | |
91 | 92 | allowedEntityFieldMap.put(EntityType.DASHBOARD, new HashSet<>(commonEntityFields)); |
92 | 93 | allowedEntityFieldMap.put(EntityType.RULE_CHAIN, new HashSet<>(commonEntityFields)); |
... | ... | @@ -160,27 +161,8 @@ public class EntityKeyMapping { |
160 | 161 | |
161 | 162 | public String toSelection(EntityFilterType filterType, EntityType entityType) { |
162 | 163 | if (entityKey.getType().equals(EntityKeyType.ENTITY_FIELD)) { |
163 | - Set<String> existingEntityFields; | |
164 | - String alias; | |
165 | - if (filterType.equals(EntityFilterType.RELATIONS_QUERY)) { | |
166 | - existingEntityFields = relationQueryEntityFieldsSet; | |
167 | - alias = entityKey.getKey(); | |
168 | - } else { | |
169 | - existingEntityFields = allowedEntityFieldMap.get(entityType); | |
170 | - if (existingEntityFields == null) { | |
171 | - existingEntityFields = commonEntityFieldsSet; | |
172 | - } | |
173 | - | |
174 | - Map<String, String> entityAliases = aliases.get(entityType); | |
175 | - if (entityAliases != null) { | |
176 | - alias = entityAliases.get(entityKey.getKey()); | |
177 | - } else { | |
178 | - alias = null; | |
179 | - } | |
180 | - if (alias == null) { | |
181 | - alias = entityKey.getKey(); | |
182 | - } | |
183 | - } | |
164 | + Set<String> existingEntityFields = getExistingEntityFields(filterType, entityType); | |
165 | + String alias = getEntityFieldAlias(filterType, entityType); | |
184 | 166 | if (existingEntityFields.contains(alias)) { |
185 | 167 | String column = entityFieldColumnMap.get(alias); |
186 | 168 | return String.format("e.%s as %s", column, getValueAlias()); |
... | ... | @@ -194,11 +176,48 @@ public class EntityKeyMapping { |
194 | 176 | } |
195 | 177 | } |
196 | 178 | |
197 | - public Stream<String> toQueries(QueryContext ctx) { | |
179 | + private String getEntityFieldAlias(EntityFilterType filterType, EntityType entityType) { | |
180 | + String alias; | |
181 | + if (filterType.equals(EntityFilterType.RELATIONS_QUERY)) { | |
182 | + alias = entityKey.getKey(); | |
183 | + } else { | |
184 | + alias = getAliasByEntityKeyAndType(entityKey.getKey(), entityType); | |
185 | + } | |
186 | + return alias; | |
187 | + } | |
188 | + | |
189 | + private Set<String> getExistingEntityFields(EntityFilterType filterType, EntityType entityType) { | |
190 | + Set<String> existingEntityFields; | |
191 | + if (filterType.equals(EntityFilterType.RELATIONS_QUERY)) { | |
192 | + existingEntityFields = relationQueryEntityFieldsSet; | |
193 | + } else { | |
194 | + existingEntityFields = allowedEntityFieldMap.get(entityType); | |
195 | + if (existingEntityFields == null) { | |
196 | + existingEntityFields = commonEntityFieldsSet; | |
197 | + } | |
198 | + } | |
199 | + return existingEntityFields; | |
200 | + } | |
201 | + | |
202 | + private String getAliasByEntityKeyAndType(String key, EntityType entityType) { | |
203 | + String alias; | |
204 | + Map<String, String> entityAliases = aliases.get(entityType); | |
205 | + if (entityAliases != null) { | |
206 | + alias = entityAliases.get(key); | |
207 | + } else { | |
208 | + alias = null; | |
209 | + } | |
210 | + if (alias == null) { | |
211 | + alias = key; | |
212 | + } | |
213 | + return alias; | |
214 | + } | |
215 | + | |
216 | + public Stream<String> toQueries(QueryContext ctx, EntityFilterType filterType, EntityType entityType) { | |
198 | 217 | if (hasFilter()) { |
199 | 218 | String keyAlias = entityKey.getType().equals(EntityKeyType.ENTITY_FIELD) ? "e" : alias; |
200 | 219 | return keyFilters.stream().map(keyFilter -> |
201 | - this.buildKeyQuery(ctx, keyAlias, keyFilter)); | |
220 | + this.buildKeyQuery(ctx, keyAlias, keyFilter, filterType, entityType)); | |
202 | 221 | } else { |
203 | 222 | return null; |
204 | 223 | } |
... | ... | @@ -244,8 +263,8 @@ public class EntityKeyMapping { |
244 | 263 | Collectors.joining(" ")); |
245 | 264 | } |
246 | 265 | |
247 | - public static String buildQuery(QueryContext ctx, List<EntityKeyMapping> mappings) { | |
248 | - return mappings.stream().flatMap(mapping -> mapping.toQueries(ctx)).collect( | |
266 | + public static String buildQuery(QueryContext ctx, List<EntityKeyMapping> mappings, EntityFilterType filterType, EntityType entityType) { | |
267 | + return mappings.stream().flatMap(mapping -> mapping.toQueries(ctx, filterType, entityType)).filter(Objects::nonNull).collect( | |
249 | 268 | Collectors.joining(" AND ")); |
250 | 269 | } |
251 | 270 | |
... | ... | @@ -357,47 +376,63 @@ public class EntityKeyMapping { |
357 | 376 | return String.join(", ", attrValSelection, attrTsSelection); |
358 | 377 | } |
359 | 378 | |
360 | - private String buildKeyQuery(QueryContext ctx, String alias, KeyFilter keyFilter) { | |
361 | - return this.buildPredicateQuery(ctx, alias, keyFilter.getKey(), keyFilter.getPredicate()); | |
379 | + private String buildKeyQuery(QueryContext ctx, String alias, KeyFilter keyFilter, | |
380 | + EntityFilterType filterType, EntityType entityType) { | |
381 | + return this.buildPredicateQuery(ctx, alias, keyFilter.getKey(), keyFilter.getPredicate(), filterType, entityType); | |
362 | 382 | } |
363 | 383 | |
364 | - private String buildPredicateQuery(QueryContext ctx, String alias, EntityKey key, KeyFilterPredicate predicate) { | |
384 | + private String buildPredicateQuery(QueryContext ctx, String alias, EntityKey key, | |
385 | + KeyFilterPredicate predicate, EntityFilterType filterType, EntityType entityType) { | |
365 | 386 | if (predicate.getType().equals(FilterPredicateType.COMPLEX)) { |
366 | - return this.buildComplexPredicateQuery(ctx, alias, key, (ComplexFilterPredicate) predicate); | |
387 | + return this.buildComplexPredicateQuery(ctx, alias, key, (ComplexFilterPredicate) predicate, filterType, entityType); | |
367 | 388 | } else { |
368 | - return this.buildSimplePredicateQuery(ctx, alias, key, predicate); | |
389 | + return this.buildSimplePredicateQuery(ctx, alias, key, predicate, filterType, entityType); | |
369 | 390 | } |
370 | 391 | } |
371 | 392 | |
372 | - private String buildComplexPredicateQuery(QueryContext ctx, String alias, EntityKey key, ComplexFilterPredicate predicate) { | |
393 | + private String buildComplexPredicateQuery(QueryContext ctx, String alias, EntityKey key, | |
394 | + ComplexFilterPredicate predicate, EntityFilterType filterType, EntityType entityType) { | |
373 | 395 | return predicate.getPredicates().stream() |
374 | - .map(keyFilterPredicate -> this.buildPredicateQuery(ctx, alias, key, keyFilterPredicate)).collect(Collectors.joining( | |
396 | + .map(keyFilterPredicate -> this.buildPredicateQuery(ctx, alias, key, keyFilterPredicate, filterType, entityType)) | |
397 | + .filter(Objects::nonNull).collect(Collectors.joining( | |
375 | 398 | " " + predicate.getOperation().name() + " " |
376 | 399 | )); |
377 | 400 | } |
378 | 401 | |
379 | - private String buildSimplePredicateQuery(QueryContext ctx, String alias, EntityKey key, KeyFilterPredicate predicate) { | |
380 | - if (predicate.getType().equals(FilterPredicateType.NUMERIC)) { | |
381 | - if (key.getType().equals(EntityKeyType.ENTITY_FIELD)) { | |
382 | - String column = entityFieldColumnMap.get(key.getKey()); | |
383 | - return this.buildNumericPredicateQuery(ctx, alias + "." + column, (NumericFilterPredicate) predicate); | |
402 | + private String buildSimplePredicateQuery(QueryContext ctx, String alias, EntityKey key, | |
403 | + KeyFilterPredicate predicate, EntityFilterType filterType, EntityType entityType) { | |
404 | + if (key.getType().equals(EntityKeyType.ENTITY_FIELD)) { | |
405 | + Set<String> existingEntityFields = getExistingEntityFields(filterType, entityType); | |
406 | + String entityFieldAlias = getEntityFieldAlias(filterType, entityType); | |
407 | + String column = null; | |
408 | + if (existingEntityFields.contains(entityFieldAlias)) { | |
409 | + column = entityFieldColumnMap.get(key.getKey()); | |
410 | + } | |
411 | + if (column != null) { | |
412 | + String field = alias + "." + column; | |
413 | + if (predicate.getType().equals(FilterPredicateType.NUMERIC)) { | |
414 | + return this.buildNumericPredicateQuery(ctx, field, (NumericFilterPredicate) predicate); | |
415 | + } else if (predicate.getType().equals(FilterPredicateType.STRING)) { | |
416 | + return this.buildStringPredicateQuery(ctx, field, (StringFilterPredicate) predicate); | |
417 | + } else { | |
418 | + return this.buildBooleanPredicateQuery(ctx, field, (BooleanFilterPredicate) predicate); | |
419 | + } | |
384 | 420 | } else { |
421 | + return null; | |
422 | + } | |
423 | + } else { | |
424 | + if (predicate.getType().equals(FilterPredicateType.NUMERIC)) { | |
385 | 425 | String longQuery = this.buildNumericPredicateQuery(ctx, alias + ".long_v", (NumericFilterPredicate) predicate); |
386 | 426 | String doubleQuery = this.buildNumericPredicateQuery(ctx, alias + ".dbl_v", (NumericFilterPredicate) predicate); |
387 | 427 | return String.format("(%s or %s)", longQuery, doubleQuery); |
388 | - } | |
389 | - } else { | |
390 | - String column; | |
391 | - if (key.getType().equals(EntityKeyType.ENTITY_FIELD)) { | |
392 | - column = entityFieldColumnMap.get(key.getKey()); | |
393 | 428 | } else { |
394 | - column = predicate.getType().equals(FilterPredicateType.STRING) ? "str_v" : "bool_v"; | |
395 | - } | |
396 | - String field = alias + "." + column; | |
397 | - if (predicate.getType().equals(FilterPredicateType.STRING)) { | |
398 | - return this.buildStringPredicateQuery(ctx, field, (StringFilterPredicate) predicate); | |
399 | - } else { | |
400 | - return this.buildBooleanPredicateQuery(ctx, field, (BooleanFilterPredicate) predicate); | |
429 | + String column = predicate.getType().equals(FilterPredicateType.STRING) ? "str_v" : "bool_v"; | |
430 | + String field = alias + "." + column; | |
431 | + if (predicate.getType().equals(FilterPredicateType.STRING)) { | |
432 | + return this.buildStringPredicateQuery(ctx, field, (StringFilterPredicate) predicate); | |
433 | + } else { | |
434 | + return this.buildBooleanPredicateQuery(ctx, field, (BooleanFilterPredicate) predicate); | |
435 | + } | |
401 | 436 | } |
402 | 437 | } |
403 | 438 | } | ... | ... |
... | ... | @@ -60,6 +60,16 @@ public class JpaUserDao extends JpaAbstractSearchTextDao<UserEntity, User> imple |
60 | 60 | } |
61 | 61 | |
62 | 62 | @Override |
63 | + public PageData<User> findByTenantId(UUID tenantId, PageLink pageLink) { | |
64 | + return DaoUtil.toPageData( | |
65 | + userRepository | |
66 | + .findByTenantId( | |
67 | + tenantId, | |
68 | + Objects.toString(pageLink.getTextSearch(), ""), | |
69 | + DaoUtil.toPageable(pageLink))); | |
70 | + } | |
71 | + | |
72 | + @Override | |
63 | 73 | public PageData<User> findTenantAdmins(UUID tenantId, PageLink pageLink) { |
64 | 74 | return DaoUtil.toPageData( |
65 | 75 | userRepository | ... | ... |
... | ... | @@ -43,4 +43,10 @@ public interface UserRepository extends PagingAndSortingRepository<UserEntity, U |
43 | 43 | @Param("authority") Authority authority, |
44 | 44 | Pageable pageable); |
45 | 45 | |
46 | + @Query("SELECT u FROM UserEntity u WHERE u.tenantId = :tenantId " + | |
47 | + "AND LOWER(u.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") | |
48 | + Page<UserEntity> findByTenantId(@Param("tenantId") UUID tenantId, | |
49 | + @Param("searchText") String searchText, | |
50 | + Pageable pageable); | |
51 | + | |
46 | 52 | } | ... | ... |
... | ... | @@ -40,6 +40,15 @@ public interface UserDao extends Dao<User> { |
40 | 40 | * @return the user entity |
41 | 41 | */ |
42 | 42 | User findByEmail(TenantId tenantId, String email); |
43 | + | |
44 | + /** | |
45 | + * Find users by tenantId and page link. | |
46 | + * | |
47 | + * @param tenantId the tenantId | |
48 | + * @param pageLink the page link | |
49 | + * @return the list of user entities | |
50 | + */ | |
51 | + PageData<User> findByTenantId(UUID tenantId, PageLink pageLink); | |
43 | 52 | |
44 | 53 | /** |
45 | 54 | * Find tenant admin users by tenantId and page link. | ... | ... |
... | ... | @@ -220,6 +220,14 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic |
220 | 220 | } |
221 | 221 | |
222 | 222 | @Override |
223 | + public PageData<User> findUsersByTenantId(TenantId tenantId, PageLink pageLink) { | |
224 | + log.trace("Executing findUsersByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); | |
225 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | |
226 | + validatePageLink(pageLink); | |
227 | + return userDao.findByTenantId(tenantId.getId(), pageLink); | |
228 | + } | |
229 | + | |
230 | + @Override | |
223 | 231 | public PageData<User> findTenantAdmins(TenantId tenantId, PageLink pageLink) { |
224 | 232 | log.trace("Executing findTenantAdmins, tenantId [{}], pageLink [{}]", tenantId, pageLink); |
225 | 233 | validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | ... | ... |
... | ... | @@ -310,7 +310,8 @@ export class EntityService { |
310 | 310 | } |
311 | 311 | break; |
312 | 312 | case EntityType.USER: |
313 | - console.error('Get User Entities is not implemented!'); | |
313 | + pageLink.sortOrder.property = 'email'; | |
314 | + entitiesObservable = this.userService.getUsers(pageLink); | |
314 | 315 | break; |
315 | 316 | case EntityType.ALARM: |
316 | 317 | console.error('Get Alarm Entities is not implemented!'); |
... | ... | @@ -548,6 +549,7 @@ export class EntityService { |
548 | 549 | entityTypes.push(EntityType.ENTITY_VIEW); |
549 | 550 | entityTypes.push(EntityType.TENANT); |
550 | 551 | entityTypes.push(EntityType.CUSTOMER); |
552 | + entityTypes.push(EntityType.USER); | |
551 | 553 | entityTypes.push(EntityType.DASHBOARD); |
552 | 554 | if (useAliasEntityTypes) { |
553 | 555 | entityTypes.push(AliasEntityType.CURRENT_CUSTOMER); |
... | ... | @@ -559,12 +561,16 @@ export class EntityService { |
559 | 561 | entityTypes.push(EntityType.ASSET); |
560 | 562 | entityTypes.push(EntityType.ENTITY_VIEW); |
561 | 563 | entityTypes.push(EntityType.CUSTOMER); |
564 | + entityTypes.push(EntityType.USER); | |
562 | 565 | entityTypes.push(EntityType.DASHBOARD); |
563 | 566 | if (useAliasEntityTypes) { |
564 | 567 | entityTypes.push(AliasEntityType.CURRENT_CUSTOMER); |
565 | 568 | } |
566 | 569 | break; |
567 | 570 | } |
571 | + if (useAliasEntityTypes) { | |
572 | + entityTypes.push(AliasEntityType.CURRENT_USER); | |
573 | + } | |
568 | 574 | if (allowedEntityTypes && allowedEntityTypes.length) { |
569 | 575 | for (let index = entityTypes.length - 1; index >= 0; index--) { |
570 | 576 | if (allowedEntityTypes.indexOf(entityTypes[index]) === -1) { |
... | ... | @@ -961,6 +967,10 @@ export class EntityService { |
961 | 967 | const authUser = getCurrentAuthUser(this.store); |
962 | 968 | entityId.entityType = EntityType.TENANT; |
963 | 969 | entityId.id = authUser.tenantId; |
970 | + } else if (entityType === AliasEntityType.CURRENT_USER){ | |
971 | + const authUser = getCurrentAuthUser(this.store); | |
972 | + entityId.entityType = EntityType.USER; | |
973 | + entityId.id = authUser.userId; | |
964 | 974 | } |
965 | 975 | return entityId; |
966 | 976 | } | ... | ... |
... | ... | @@ -32,6 +32,12 @@ export class UserService { |
32 | 32 | private http: HttpClient |
33 | 33 | ) { } |
34 | 34 | |
35 | + public getUsers(pageLink: PageLink, | |
36 | + config?: RequestConfig): Observable<PageData<User>> { | |
37 | + return this.http.get<PageData<User>>(`/api/users${pageLink.toQuery()}`, | |
38 | + defaultHttpOptionsFromConfig(config)); | |
39 | + } | |
40 | + | |
35 | 41 | public getTenantAdmins(tenantId: string, pageLink: PageLink, |
36 | 42 | config?: RequestConfig): Observable<PageData<User>> { |
37 | 43 | return this.http.get<PageData<User>>(`/api/tenant/${tenantId}/users${pageLink.toQuery()}`, | ... | ... |
... | ... | @@ -28,6 +28,7 @@ import { |
28 | 28 | AliasesEntitySelectPanelData |
29 | 29 | } from './aliases-entity-select-panel.component'; |
30 | 30 | import { deepClone } from '@core/utils'; |
31 | +import { AliasFilterType } from '@shared/models/alias.models'; | |
31 | 32 | |
32 | 33 | @Component({ |
33 | 34 | selector: 'tb-aliases-entity-select', |
... | ... | @@ -178,7 +179,7 @@ export class AliasesEntitySelectComponent implements OnInit, OnDestroy { |
178 | 179 | for (const aliasId of Object.keys(allEntityAliases)) { |
179 | 180 | const aliasInfo = this.aliasController.getInstantAliasInfo(aliasId); |
180 | 181 | if (aliasInfo && !aliasInfo.resolveMultiple && aliasInfo.currentEntity |
181 | - && aliasInfo.entityFilter) { | |
182 | + && aliasInfo.entityFilter && aliasInfo.entityFilter.type !== AliasFilterType.singleEntity) { | |
182 | 183 | this.entityAliasesInfo[aliasId] = deepClone(aliasInfo); |
183 | 184 | this.hasSelectableAliasEntities = true; |
184 | 185 | } | ... | ... |
... | ... | @@ -84,6 +84,9 @@ import { |
84 | 84 | KeyFilter |
85 | 85 | } from '@shared/models/query/query.models'; |
86 | 86 | import { sortItems } from '@shared/models/page/page-link'; |
87 | +import { entityFields } from '@shared/models/entity.models'; | |
88 | +import { alarmFields } from '@shared/models/alarm.models'; | |
89 | +import { DatePipe } from '@angular/common'; | |
87 | 90 | |
88 | 91 | interface EntitiesTableWidgetSettings extends TableWidgetSettings { |
89 | 92 | entitiesTitle: string; |
... | ... | @@ -153,6 +156,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni |
153 | 156 | private overlay: Overlay, |
154 | 157 | private viewContainerRef: ViewContainerRef, |
155 | 158 | private utils: UtilsService, |
159 | + private datePipe: DatePipe, | |
156 | 160 | private translate: TranslateService, |
157 | 161 | private domSanitizer: DomSanitizer) { |
158 | 162 | super(store); |
... | ... | @@ -511,9 +515,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni |
511 | 515 | content = '' + value; |
512 | 516 | } |
513 | 517 | } else { |
514 | - const decimals = (contentInfo.decimals || contentInfo.decimals === 0) ? contentInfo.decimals : this.ctx.widgetConfig.decimals; | |
515 | - const units = contentInfo.units || this.ctx.widgetConfig.units; | |
516 | - content = this.ctx.utils.formatValue(value, decimals, units, true); | |
518 | + content = this.defaultContent(key, contentInfo, value); | |
517 | 519 | } |
518 | 520 | return isDefined(content) ? this.domSanitizer.bypassSecurityTrustHtml(content) : ''; |
519 | 521 | } else { |
... | ... | @@ -521,6 +523,22 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni |
521 | 523 | } |
522 | 524 | } |
523 | 525 | |
526 | + private defaultContent(key: EntityColumn, contentInfo: CellContentInfo, value: any): any { | |
527 | + if (isDefined(value)) { | |
528 | + const entityField = entityFields[key.name]; | |
529 | + if (entityField) { | |
530 | + if (entityField.time) { | |
531 | + return this.datePipe.transform(value, 'yyyy-MM-dd HH:mm:ss'); | |
532 | + } | |
533 | + } | |
534 | + const decimals = (contentInfo.decimals || contentInfo.decimals === 0) ? contentInfo.decimals : this.ctx.widgetConfig.decimals; | |
535 | + const units = contentInfo.units || this.ctx.widgetConfig.units; | |
536 | + return this.ctx.utils.formatValue(value, decimals, units, true); | |
537 | + } else { | |
538 | + return ''; | |
539 | + } | |
540 | + } | |
541 | + | |
524 | 542 | public onRowClick($event: Event, entity: EntityData, isDouble?: boolean) { |
525 | 543 | if ($event) { |
526 | 544 | $event.stopPropagation(); | ... | ... |
... | ... | @@ -15,6 +15,23 @@ |
15 | 15 | limitations under the License. |
16 | 16 | |
17 | 17 | --> |
18 | +<mat-tab *ngIf="entity" | |
19 | + label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> | |
20 | + <tb-attribute-table [defaultAttributeScope]="attributeScopes.SERVER_SCOPE" | |
21 | + [active]="attributesTab.isActive" | |
22 | + [entityId]="entity.id" | |
23 | + [entityName]="entity.name"> | |
24 | + </tb-attribute-table> | |
25 | +</mat-tab> | |
26 | +<mat-tab *ngIf="entity" | |
27 | + label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> | |
28 | + <tb-attribute-table [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY" | |
29 | + disableAttributeScopeSelection | |
30 | + [active]="telemetryTab.isActive" | |
31 | + [entityId]="entity.id" | |
32 | + [entityName]="entity.name"> | |
33 | + </tb-attribute-table> | |
34 | +</mat-tab> | |
18 | 35 | <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" |
19 | 36 | label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> |
20 | 37 | <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 |
175 | 175 | this.entityRequiredText = 'customer.customer-required'; |
176 | 176 | break; |
177 | 177 | case EntityType.USER: |
178 | + case AliasEntityType.CURRENT_USER: | |
178 | 179 | this.entityText = 'user.user'; |
179 | 180 | this.noEntitiesMatchingText = 'user.no-users-matching'; |
180 | 181 | this.entityRequiredText = 'user.user-required'; |
... | ... | @@ -324,6 +325,8 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit |
324 | 325 | return EntityType.CUSTOMER; |
325 | 326 | } else if (entityType === AliasEntityType.CURRENT_TENANT) { |
326 | 327 | return EntityType.TENANT; |
328 | + } else if (entityType === AliasEntityType.CURRENT_USER) { | |
329 | + return EntityType.USER; | |
327 | 330 | } |
328 | 331 | return entityType; |
329 | 332 | } | ... | ... |
... | ... | @@ -27,7 +27,8 @@ |
27 | 27 | </tb-entity-type-select> |
28 | 28 | <tb-entity-autocomplete |
29 | 29 | fxFlex |
30 | - *ngIf="modelValue.entityType && modelValue.entityType !== AliasEntityType.CURRENT_TENANT" | |
30 | + *ngIf="modelValue.entityType && modelValue.entityType !== AliasEntityType.CURRENT_TENANT | |
31 | + && modelValue.entityType !== AliasEntityType.CURRENT_USER" | |
31 | 32 | [required]="required" |
32 | 33 | [entityType]="modelValue.entityType" |
33 | 34 | formControlName="entityId"> | ... | ... |
... | ... | @@ -97,7 +97,7 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte |
97 | 97 | ngOnInit() { |
98 | 98 | this.entitySelectFormGroup.get('entityType').valueChanges.subscribe( |
99 | 99 | (value) => { |
100 | - if(value === AliasEntityType.CURRENT_TENANT){ | |
100 | + if(value === AliasEntityType.CURRENT_TENANT || value === AliasEntityType.CURRENT_USER) { | |
101 | 101 | this.modelValue.id = NULL_UUID; |
102 | 102 | } |
103 | 103 | this.updateView(value, this.modelValue.id); |
... | ... | @@ -145,7 +145,9 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte |
145 | 145 | entityType, |
146 | 146 | id: this.modelValue.entityType !== entityType ? null : entityId |
147 | 147 | }; |
148 | - if (this.modelValue.entityType && (this.modelValue.id || this.modelValue.entityType === AliasEntityType.CURRENT_TENANT)) { | |
148 | + if (this.modelValue.entityType && (this.modelValue.id || | |
149 | + this.modelValue.entityType === AliasEntityType.CURRENT_TENANT || | |
150 | + this.modelValue.entityType === AliasEntityType.CURRENT_USER)) { | |
149 | 151 | this.propagateChange(this.modelValue); |
150 | 152 | } else { |
151 | 153 | this.propagateChange(null); | ... | ... |
... | ... | @@ -50,7 +50,8 @@ export enum EntityType { |
50 | 50 | |
51 | 51 | export enum AliasEntityType { |
52 | 52 | CURRENT_CUSTOMER = 'CURRENT_CUSTOMER', |
53 | - CURRENT_TENANT = 'CURRENT_TENANT' | |
53 | + CURRENT_TENANT = 'CURRENT_TENANT', | |
54 | + CURRENT_USER = 'CURRENT_USER' | |
54 | 55 | } |
55 | 56 | |
56 | 57 | export interface EntityTypeTranslation { |
... | ... | @@ -229,6 +230,13 @@ export const entityTypeTranslations = new Map<EntityType | AliasEntityType, Enti |
229 | 230 | type: 'entity.type-current-tenant', |
230 | 231 | list: 'entity.type-current-tenant' |
231 | 232 | } |
233 | + ], | |
234 | + [ | |
235 | + AliasEntityType.CURRENT_USER, | |
236 | + { | |
237 | + type: 'entity.type-current-user', | |
238 | + list: 'entity.type-current-user' | |
239 | + } | |
232 | 240 | ] |
233 | 241 | ] |
234 | 242 | ); | ... | ... |
... | ... | @@ -841,6 +841,7 @@ |
841 | 841 | "rulenode-name-starts-with": "Rule nodes whose names start with '{{prefix}}'", |
842 | 842 | "type-current-customer": "Current Customer", |
843 | 843 | "type-current-tenant": "Current Tenant", |
844 | + "type-current-user": "Current User", | |
844 | 845 | "search": "Search entities", |
845 | 846 | "selected-entities": "{ count, plural, 1 {1 entity} other {# entities} } selected", |
846 | 847 | "entity-name": "Entity name", | ... | ... |