Commit 9cdfe4ef54e38e4a0b962665abae1bef431a548d
1 parent
a90f16f4
TB-61: Implemented new alias filters.
Showing
44 changed files
with
1399 additions
and
126 deletions
... | ... | @@ -250,6 +250,21 @@ public class EntityRelationController extends BaseController { |
250 | 250 | } |
251 | 251 | } |
252 | 252 | |
253 | + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") | |
254 | + @RequestMapping(value = "/relations/info", method = RequestMethod.POST) | |
255 | + @ResponseBody | |
256 | + public List<EntityRelationInfo> findInfoByQuery(@RequestBody EntityRelationsQuery query) throws ThingsboardException { | |
257 | + checkNotNull(query); | |
258 | + checkNotNull(query.getParameters()); | |
259 | + checkNotNull(query.getFilters()); | |
260 | + checkEntityId(query.getParameters().getEntityId()); | |
261 | + try { | |
262 | + return checkNotNull(relationService.findInfoByQuery(query).get()); | |
263 | + } catch (Exception e) { | |
264 | + throw handleException(e); | |
265 | + } | |
266 | + } | |
267 | + | |
253 | 268 | private RelationTypeGroup parseRelationTypeGroup(String strRelationTypeGroup, RelationTypeGroup defaultValue) { |
254 | 269 | RelationTypeGroup result = defaultValue; |
255 | 270 | if (strRelationTypeGroup != null && strRelationTypeGroup.trim().length()>0) { | ... | ... |
... | ... | @@ -191,7 +191,7 @@ public class BaseRelationService implements RelationService { |
191 | 191 | |
192 | 192 | @Override |
193 | 193 | public ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query) { |
194 | - log.trace("Executing findByQuery [{}][{}]", query); | |
194 | + log.trace("Executing findByQuery [{}]", query); | |
195 | 195 | RelationsSearchParameters params = query.getParameters(); |
196 | 196 | final List<EntityTypeFilter> filters = query.getFilters(); |
197 | 197 | if (filters == null || filters.isEmpty()) { |
... | ... | @@ -224,6 +224,30 @@ public class BaseRelationService implements RelationService { |
224 | 224 | } |
225 | 225 | } |
226 | 226 | |
227 | + @Override | |
228 | + public ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(EntityRelationsQuery query) { | |
229 | + log.trace("Executing findInfoByQuery [{}]", query); | |
230 | + ListenableFuture<List<EntityRelation>> relations = findByQuery(query); | |
231 | + EntitySearchDirection direction = query.getParameters().getDirection(); | |
232 | + ListenableFuture<List<EntityRelationInfo>> relationsInfo = Futures.transform(relations, | |
233 | + (AsyncFunction<List<EntityRelation>, List<EntityRelationInfo>>) relations1 -> { | |
234 | + List<ListenableFuture<EntityRelationInfo>> futures = new ArrayList<>(); | |
235 | + relations1.stream().forEach(relation -> | |
236 | + futures.add(fetchRelationInfoAsync(relation, | |
237 | + relation2 -> direction == EntitySearchDirection.FROM ? relation2.getTo() : relation2.getFrom(), | |
238 | + (EntityRelationInfo relationInfo, String entityName) -> { | |
239 | + if (direction == EntitySearchDirection.FROM) { | |
240 | + relationInfo.setToName(entityName); | |
241 | + } else { | |
242 | + relationInfo.setFromName(entityName); | |
243 | + } | |
244 | + })) | |
245 | + ); | |
246 | + return Futures.successfulAsList(futures); | |
247 | + }); | |
248 | + return relationsInfo; | |
249 | + } | |
250 | + | |
227 | 251 | protected void validate(EntityRelation relation) { |
228 | 252 | if (relation == null) { |
229 | 253 | throw new DataValidationException("Relation type should be specified!"); | ... | ... |
... | ... | @@ -52,6 +52,8 @@ public interface RelationService { |
52 | 52 | |
53 | 53 | ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query); |
54 | 54 | |
55 | + ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(EntityRelationsQuery query); | |
56 | + | |
55 | 57 | // TODO: This method may be useful for some validations in the future |
56 | 58 | // ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to); |
57 | 59 | ... | ... |
... | ... | @@ -30,7 +30,8 @@ function EntityRelationService($http, $q) { |
30 | 30 | findByTo: findByTo, |
31 | 31 | findInfoByTo: findInfoByTo, |
32 | 32 | findByToAndType: findByToAndType, |
33 | - findByQuery: findByQuery | |
33 | + findByQuery: findByQuery, | |
34 | + findInfoByQuery: findInfoByQuery | |
34 | 35 | } |
35 | 36 | |
36 | 37 | return service; |
... | ... | @@ -159,4 +160,15 @@ function EntityRelationService($http, $q) { |
159 | 160 | return deferred.promise; |
160 | 161 | } |
161 | 162 | |
163 | + function findInfoByQuery(query) { | |
164 | + var deferred = $q.defer(); | |
165 | + var url = '/api/relations/info'; | |
166 | + $http.post(url, query).then(function success(response) { | |
167 | + deferred.resolve(response.data); | |
168 | + }, function fail() { | |
169 | + deferred.reject(); | |
170 | + }); | |
171 | + return deferred.promise; | |
172 | + } | |
173 | + | |
162 | 174 | } | ... | ... |
... | ... | @@ -32,6 +32,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
32 | 32 | checkEntityAlias: checkEntityAlias, |
33 | 33 | filterAliasByEntityTypes: filterAliasByEntityTypes, |
34 | 34 | getAliasFilterTypesByEntityTypes: getAliasFilterTypesByEntityTypes, |
35 | + prepareAllowedEntityTypesList: prepareAllowedEntityTypesList, | |
35 | 36 | getEntityKeys: getEntityKeys, |
36 | 37 | createDatasourcesFromSubscriptionsInfo: createDatasourcesFromSubscriptionsInfo, |
37 | 38 | getRelatedEntities: getRelatedEntities, |
... | ... | @@ -176,6 +177,54 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
176 | 177 | return deferred.promise; |
177 | 178 | } |
178 | 179 | |
180 | + function getSingleTenantByPageLinkPromise(pageLink) { | |
181 | + var user = userService.getCurrentUser(); | |
182 | + var tenantId = user.tenantId; | |
183 | + var deferred = $q.defer(); | |
184 | + tenantService.getTenant(tenantId).then( | |
185 | + function success(tenant) { | |
186 | + var tenantName = tenant.name; | |
187 | + var result = { | |
188 | + data: [], | |
189 | + nextPageLink: pageLink, | |
190 | + hasNext: false | |
191 | + }; | |
192 | + if (tenantName.toLowerCase().startsWith(pageLink.textSearch)) { | |
193 | + result.data.push(tenant); | |
194 | + } | |
195 | + deferred.resolve(result); | |
196 | + }, | |
197 | + function fail() { | |
198 | + deferred.reject(); | |
199 | + } | |
200 | + ); | |
201 | + return deferred.promise; | |
202 | + } | |
203 | + | |
204 | + function getSingleCustomerByPageLinkPromise(pageLink) { | |
205 | + var user = userService.getCurrentUser(); | |
206 | + var customerId = user.customerId; | |
207 | + var deferred = $q.defer(); | |
208 | + customerService.getCustomer(customerId).then( | |
209 | + function success(customer) { | |
210 | + var customerName = customer.name; | |
211 | + var result = { | |
212 | + data: [], | |
213 | + nextPageLink: pageLink, | |
214 | + hasNext: false | |
215 | + }; | |
216 | + if (customerName.toLowerCase().startsWith(pageLink.textSearch)) { | |
217 | + result.data.push(customer); | |
218 | + } | |
219 | + deferred.resolve(result); | |
220 | + }, | |
221 | + function fail() { | |
222 | + deferred.reject(); | |
223 | + } | |
224 | + ); | |
225 | + return deferred.promise; | |
226 | + } | |
227 | + | |
179 | 228 | function getEntitiesByPageLinkPromise(entityType, pageLink, config, subType) { |
180 | 229 | var promise; |
181 | 230 | var user = userService.getCurrentUser(); |
... | ... | @@ -196,10 +245,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
196 | 245 | } |
197 | 246 | break; |
198 | 247 | case types.entityType.tenant: |
199 | - promise = tenantService.getTenants(pageLink); | |
248 | + if (user.authority === 'TENANT_ADMIN') { | |
249 | + promise = getSingleTenantByPageLinkPromise(pageLink); | |
250 | + } else { | |
251 | + promise = tenantService.getTenants(pageLink); | |
252 | + } | |
200 | 253 | break; |
201 | 254 | case types.entityType.customer: |
202 | - promise = customerService.getCustomers(pageLink); | |
255 | + if (user.authority === 'CUSTOMER_USER') { | |
256 | + promise = getSingleCustomerByPageLinkPromise(pageLink); | |
257 | + } else { | |
258 | + promise = customerService.getCustomers(pageLink); | |
259 | + } | |
203 | 260 | break; |
204 | 261 | case types.entityType.rule: |
205 | 262 | promise = ruleService.getAllRules(pageLink); |
... | ... | @@ -283,6 +340,16 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
283 | 340 | return { name: entity.name, entityType: entity.id.entityType, id: entity.id.id }; |
284 | 341 | } |
285 | 342 | |
343 | + function entityRelationInfoToEntityInfo(entityRelationInfo, direction) { | |
344 | + var entityId = direction == types.entitySearchDirection.from ? entityRelationInfo.to : entityRelationInfo.from; | |
345 | + var name = direction == types.entitySearchDirection.from ? entityRelationInfo.toName : entityRelationInfo.fromName; | |
346 | + return { | |
347 | + name: name, | |
348 | + entityType: entityId.entityType, | |
349 | + id: entityId.id | |
350 | + }; | |
351 | + } | |
352 | + | |
286 | 353 | function entitiesToEntitiesInfo(entities) { |
287 | 354 | var entitiesInfo = []; |
288 | 355 | for (var d = 0; d < entities.length; d++) { |
... | ... | @@ -291,19 +358,26 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
291 | 358 | return entitiesInfo; |
292 | 359 | } |
293 | 360 | |
361 | + function entityRelationInfosToEntitiesInfo(entityRelations, direction) { | |
362 | + var entitiesInfo = []; | |
363 | + for (var d = 0; d < entityRelations.length; d++) { | |
364 | + entitiesInfo.push(entityRelationInfoToEntityInfo(entityRelations[d], direction)); | |
365 | + } | |
366 | + return entitiesInfo; | |
367 | + } | |
368 | + | |
369 | + | |
294 | 370 | function resolveAlias(entityAlias, stateParams) { |
295 | 371 | var deferred = $q.defer(); |
296 | 372 | var filter = entityAlias.filter; |
297 | 373 | resolveAliasFilter(filter, stateParams, -1).then( |
298 | 374 | function (result) { |
299 | - var entities = result.entities; | |
300 | 375 | var aliasInfo = { |
301 | 376 | alias: entityAlias.alias, |
302 | 377 | stateEntity: result.stateEntity, |
303 | 378 | resolveMultiple: filter.resolveMultiple |
304 | 379 | }; |
305 | - var resolvedEntities = entitiesToEntitiesInfo(entities); | |
306 | - aliasInfo.resolvedEntities = resolvedEntities; | |
380 | + aliasInfo.resolvedEntities = result.entities; | |
307 | 381 | aliasInfo.currentEntity = null; |
308 | 382 | if (aliasInfo.resolvedEntities.length) { |
309 | 383 | aliasInfo.currentEntity = aliasInfo.resolvedEntities[0]; |
... | ... | @@ -328,7 +402,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
328 | 402 | getEntities(filter.entityType, filter.entityList).then( |
329 | 403 | function success(entities) { |
330 | 404 | if (entities && entities.length) { |
331 | - result.entities = entities; | |
405 | + result.entities = entitiesToEntitiesInfo(entities); | |
332 | 406 | deferred.resolve(result); |
333 | 407 | } else { |
334 | 408 | deferred.reject(); |
... | ... | @@ -343,7 +417,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
343 | 417 | getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems).then( |
344 | 418 | function success(entities) { |
345 | 419 | if (entities && entities.length) { |
346 | - result.entities = entities; | |
420 | + result.entities = entitiesToEntitiesInfo(entities); | |
347 | 421 | deferred.resolve(result); |
348 | 422 | } else { |
349 | 423 | deferred.reject(); |
... | ... | @@ -359,7 +433,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
359 | 433 | if (stateParams && stateParams.entityId) { |
360 | 434 | getEntity(stateParams.entityId.entityType, stateParams.entityId.id).then( |
361 | 435 | function success(entity) { |
362 | - result.entities = [entity]; | |
436 | + result.entities = entitiesToEntitiesInfo([entity]); | |
363 | 437 | deferred.resolve(result); |
364 | 438 | }, |
365 | 439 | function fail() { |
... | ... | @@ -374,7 +448,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
374 | 448 | getEntitiesByNameFilter(types.entityType.asset, filter.assetNameFilter, maxItems, null, filter.assetType).then( |
375 | 449 | function success(entities) { |
376 | 450 | if (entities && entities.length) { |
377 | - result.entities = entities; | |
451 | + result.entities = entitiesToEntitiesInfo(entities); | |
378 | 452 | deferred.resolve(result); |
379 | 453 | } else { |
380 | 454 | deferred.reject(); |
... | ... | @@ -389,7 +463,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
389 | 463 | getEntitiesByNameFilter(types.entityType.device, filter.deviceNameFilter, maxItems, null, filter.deviceType).then( |
390 | 464 | function success(entities) { |
391 | 465 | if (entities && entities.length) { |
392 | - result.entities = entities; | |
466 | + result.entities = entitiesToEntitiesInfo(entities); | |
393 | 467 | deferred.resolve(result); |
394 | 468 | } else { |
395 | 469 | deferred.reject(); |
... | ... | @@ -400,8 +474,97 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
400 | 474 | } |
401 | 475 | ); |
402 | 476 | break; |
403 | - | |
404 | - //TODO: Alias filter | |
477 | + case types.aliasFilterType.relationsQuery.value: | |
478 | + result.stateEntity = filter.rootStateEntity; | |
479 | + var rootEntityType; | |
480 | + var rootEntityId; | |
481 | + if (result.stateEntity && stateParams && stateParams.entityId) { | |
482 | + rootEntityType = stateParams.entityId.entityType; | |
483 | + rootEntityId = stateParams.entityId.id; | |
484 | + } else if (!result.stateEntity) { | |
485 | + rootEntityType = filter.rootEntity.entityType; | |
486 | + rootEntityId = filter.rootEntity.id; | |
487 | + } | |
488 | + if (rootEntityType && rootEntityId) { | |
489 | + var searchQuery = { | |
490 | + parameters: { | |
491 | + rootId: rootEntityId, | |
492 | + rootType: rootEntityType, | |
493 | + direction: filter.direction | |
494 | + }, | |
495 | + filters: filter.filters | |
496 | + }; | |
497 | + searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1; | |
498 | + entityRelationService.findInfoByQuery(searchQuery).then( | |
499 | + function success(allRelations) { | |
500 | + if (allRelations && allRelations.length) { | |
501 | + if (angular.isDefined(maxItems) && maxItems > 0) { | |
502 | + var limit = Math.min(allRelations.length, maxItems); | |
503 | + allRelations.length = limit; | |
504 | + } | |
505 | + result.entities = entityRelationInfosToEntitiesInfo(allRelations, filter.direction); | |
506 | + deferred.resolve(result); | |
507 | + } else { | |
508 | + deferred.reject(); | |
509 | + } | |
510 | + }, | |
511 | + function fail() { | |
512 | + deferred.reject(); | |
513 | + } | |
514 | + ); | |
515 | + } else { | |
516 | + deferred.resolve(result); | |
517 | + } | |
518 | + break; | |
519 | + case types.aliasFilterType.assetSearchQuery.value: | |
520 | + case types.aliasFilterType.deviceSearchQuery.value: | |
521 | + result.stateEntity = filter.rootStateEntity; | |
522 | + if (result.stateEntity && stateParams && stateParams.entityId) { | |
523 | + rootEntityType = stateParams.entityId.entityType; | |
524 | + rootEntityId = stateParams.entityId.id; | |
525 | + } else if (!result.stateEntity) { | |
526 | + rootEntityType = filter.rootEntity.entityType; | |
527 | + rootEntityId = filter.rootEntity.id; | |
528 | + } | |
529 | + if (rootEntityType && rootEntityId) { | |
530 | + searchQuery = { | |
531 | + parameters: { | |
532 | + rootId: rootEntityId, | |
533 | + rootType: rootEntityType, | |
534 | + direction: filter.direction | |
535 | + }, | |
536 | + relationType: filter.relationType | |
537 | + }; | |
538 | + searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1; | |
539 | + var findByQueryPromise; | |
540 | + if (filter.type == types.aliasFilterType.assetSearchQuery.value) { | |
541 | + searchQuery.assetTypes = filter.assetTypes; | |
542 | + findByQueryPromise = assetService.findByQuery(searchQuery, false); | |
543 | + } else if (filter.type == types.aliasFilterType.deviceSearchQuery.value) { | |
544 | + searchQuery.deviceTypes = filter.deviceTypes; | |
545 | + findByQueryPromise = deviceService.findByQuery(searchQuery, false); | |
546 | + } | |
547 | + findByQueryPromise.then( | |
548 | + function success(entities) { | |
549 | + if (entities && entities.length) { | |
550 | + if (angular.isDefined(maxItems) && maxItems > 0) { | |
551 | + var limit = Math.min(entities.length, maxItems); | |
552 | + entities.length = limit; | |
553 | + } | |
554 | + result.entities = entitiesToEntitiesInfo(entities); | |
555 | + deferred.resolve(result); | |
556 | + } else { | |
557 | + deferred.reject(); | |
558 | + } | |
559 | + }, | |
560 | + function fail() { | |
561 | + deferred.reject(); | |
562 | + } | |
563 | + ); | |
564 | + } else { | |
565 | + deferred.resolve(result); | |
566 | + } | |
567 | + break; | |
405 | 568 | } |
406 | 569 | return deferred.promise; |
407 | 570 | } |
... | ... | @@ -420,9 +583,33 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
420 | 583 | return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false; |
421 | 584 | case types.aliasFilterType.deviceType.value: |
422 | 585 | return entityTypes.indexOf(types.entityType.device) > -1 ? true : false; |
586 | + case types.aliasFilterType.relationsQuery.value: | |
587 | + if (filter.filters && filter.filters.length) { | |
588 | + var match = false; | |
589 | + for (var f=0;f<filter.filters.length;f++) { | |
590 | + var relationFilter = filter.filters[f]; | |
591 | + if (relationFilter.entityTypes && relationFilter.entityTypes.length) { | |
592 | + for (var et=0;et<relationFilter.entityTypes.length;et++) { | |
593 | + if (entityTypes.indexOf(relationFilter.entityTypes[et]) > -1) { | |
594 | + match = true; | |
595 | + break; | |
596 | + } | |
597 | + } | |
598 | + } else { | |
599 | + match = true; | |
600 | + break; | |
601 | + } | |
602 | + } | |
603 | + return match; | |
604 | + } else { | |
605 | + return true; | |
606 | + } | |
607 | + case types.aliasFilterType.assetSearchQuery.value: | |
608 | + return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false; | |
609 | + case types.aliasFilterType.deviceSearchQuery.value: | |
610 | + return entityTypes.indexOf(types.entityType.device) > -1 ? true : false; | |
423 | 611 | } |
424 | 612 | } |
425 | - //TODO: Alias filter | |
426 | 613 | return false; |
427 | 614 | } |
428 | 615 | |
... | ... | @@ -474,6 +661,42 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
474 | 661 | return result; |
475 | 662 | } |
476 | 663 | |
664 | + function prepareAllowedEntityTypesList(allowedEntityTypes) { | |
665 | + var authority = userService.getAuthority(); | |
666 | + var entityTypes = {}; | |
667 | + switch(authority) { | |
668 | + case 'SYS_ADMIN': | |
669 | + entityTypes.tenant = types.entityType.tenant; | |
670 | + entityTypes.rule = types.entityType.rule; | |
671 | + entityTypes.plugin = types.entityType.plugin; | |
672 | + break; | |
673 | + case 'TENANT_ADMIN': | |
674 | + entityTypes.device = types.entityType.device; | |
675 | + entityTypes.asset = types.entityType.asset; | |
676 | + entityTypes.tenant = types.entityType.tenant; | |
677 | + entityTypes.customer = types.entityType.customer; | |
678 | + entityTypes.rule = types.entityType.rule; | |
679 | + entityTypes.plugin = types.entityType.plugin; | |
680 | + entityTypes.dashboard = types.entityType.dashboard; | |
681 | + break; | |
682 | + case 'CUSTOMER_USER': | |
683 | + entityTypes.device = types.entityType.device; | |
684 | + entityTypes.asset = types.entityType.asset; | |
685 | + entityTypes.customer = types.entityType.customer; | |
686 | + entityTypes.dashboard = types.entityType.dashboard; | |
687 | + break; | |
688 | + } | |
689 | + | |
690 | + if (allowedEntityTypes) { | |
691 | + for (var entityType in entityTypes) { | |
692 | + if (allowedEntityTypes.indexOf(entityTypes[entityType]) === -1) { | |
693 | + delete entityTypes[entityType]; | |
694 | + } | |
695 | + } | |
696 | + } | |
697 | + return entityTypes; | |
698 | + } | |
699 | + | |
477 | 700 | |
478 | 701 | function checkEntityAlias(entityAlias) { |
479 | 702 | var deferred = $q.defer(); | ... | ... |
... | ... | @@ -146,46 +146,55 @@ export default angular.module('thingsboard.types', []) |
146 | 146 | entityTypeTranslations: { |
147 | 147 | "DEVICE": { |
148 | 148 | type: 'entity.type-device', |
149 | + typePlural: 'entity.type-devices', | |
149 | 150 | list: 'entity.list-of-devices', |
150 | 151 | nameStartsWith: 'entity.device-name-starts-with' |
151 | 152 | }, |
152 | 153 | "ASSET": { |
153 | 154 | type: 'entity.type-asset', |
155 | + typePlural: 'entity.type-assets', | |
154 | 156 | list: 'entity.list-of-assets', |
155 | 157 | nameStartsWith: 'entity.asset-name-starts-with' |
156 | 158 | }, |
157 | 159 | "RULE": { |
158 | 160 | type: 'entity.type-rule', |
161 | + typePlural: 'entity.type-rules', | |
159 | 162 | list: 'entity.list-of-rules', |
160 | 163 | nameStartsWith: 'entity.rule-name-starts-with' |
161 | 164 | }, |
162 | 165 | "PLUGIN": { |
163 | 166 | type: 'entity.type-plugin', |
167 | + typePlural: 'entity.type-plugins', | |
164 | 168 | list: 'entity.list-of-plugins', |
165 | 169 | nameStartsWith: 'entity.plugin-name-starts-with' |
166 | 170 | }, |
167 | 171 | "TENANT": { |
168 | 172 | type: 'entity.type-tenant', |
173 | + typePlural: 'entity.type-tenants', | |
169 | 174 | list: 'entity.list-of-tenants', |
170 | 175 | nameStartsWith: 'entity.tenant-name-starts-with' |
171 | 176 | }, |
172 | 177 | "CUSTOMER": { |
173 | 178 | type: 'entity.type-customer', |
179 | + typePlural: 'entity.type-customers', | |
174 | 180 | list: 'entity.list-of-customers', |
175 | 181 | nameStartsWith: 'entity.customer-name-starts-with' |
176 | 182 | }, |
177 | 183 | "USER": { |
178 | 184 | type: 'entity.type-user', |
185 | + typePlural: 'entity.type-users', | |
179 | 186 | list: 'entity.list-of-users', |
180 | 187 | nameStartsWith: 'entity.user-name-starts-with' |
181 | 188 | }, |
182 | 189 | "DASHBOARD": { |
183 | 190 | type: 'entity.type-dashboard', |
191 | + typePlural: 'entity.type-dashboards', | |
184 | 192 | list: 'entity.list-of-dashboards', |
185 | 193 | nameStartsWith: 'entity.dashboard-name-starts-with' |
186 | 194 | }, |
187 | 195 | "ALARM": { |
188 | 196 | type: 'entity.type-alarm', |
197 | + typePlural: 'entity.type-alarms', | |
189 | 198 | list: 'entity.list-of-alarms', |
190 | 199 | nameStartsWith: 'entity.alarm-name-starts-with' |
191 | 200 | } | ... | ... |
... | ... | @@ -71,6 +71,13 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, |
71 | 71 | } |
72 | 72 | }, true); |
73 | 73 | |
74 | + scope.$watch('datasourceName', function () { | |
75 | + if (ngModelCtrl.$viewValue) { | |
76 | + ngModelCtrl.$viewValue.name = scope.datasourceName; | |
77 | + scope.updateValidity(); | |
78 | + } | |
79 | + }); | |
80 | + | |
74 | 81 | ngModelCtrl.$render = function () { |
75 | 82 | if (ngModelCtrl.$viewValue) { |
76 | 83 | var funcDataKeys = []; |
... | ... | @@ -78,6 +85,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, |
78 | 85 | funcDataKeys = funcDataKeys.concat(ngModelCtrl.$viewValue.dataKeys); |
79 | 86 | } |
80 | 87 | scope.funcDataKeys = funcDataKeys; |
88 | + scope.datasourceName = ngModelCtrl.$viewValue.name; | |
81 | 89 | } |
82 | 90 | }; |
83 | 91 | ... | ... |
... | ... | @@ -15,23 +15,29 @@ |
15 | 15 | */ |
16 | 16 | @import '../../scss/constants'; |
17 | 17 | |
18 | -.tb-func-datakey-autocomplete { | |
19 | - .tb-not-found { | |
20 | - display: block; | |
21 | - line-height: 1.5; | |
22 | - height: 48px; | |
23 | - .tb-no-entries { | |
24 | - line-height: 48px; | |
25 | - } | |
18 | +.tb-datasource-func { | |
19 | + @media (min-width: $layout-breakpoint-gt-sm) { | |
20 | + padding-left: 8px; | |
26 | 21 | } |
27 | - li { | |
28 | - height: auto !important; | |
29 | - white-space: normal !important; | |
22 | + | |
23 | + md-input-container.tb-datasource-name { | |
24 | + .md-errors-spacer { | |
25 | + display: none; | |
26 | + } | |
30 | 27 | } |
31 | -} | |
32 | 28 | |
33 | -tb-datasource-func { | |
34 | - @media (min-width: $layout-breakpoint-gt-sm) { | |
35 | - padding-left: 8px; | |
29 | + .tb-func-datakey-autocomplete { | |
30 | + .tb-not-found { | |
31 | + display: block; | |
32 | + line-height: 1.5; | |
33 | + height: 48px; | |
34 | + .tb-no-entries { | |
35 | + line-height: 48px; | |
36 | + } | |
37 | + } | |
38 | + li { | |
39 | + height: auto !important; | |
40 | + white-space: normal !important; | |
41 | + } | |
36 | 42 | } |
37 | -} | |
\ No newline at end of file | ||
43 | +} | ... | ... |
... | ... | @@ -15,59 +15,68 @@ |
15 | 15 | limitations under the License. |
16 | 16 | |
17 | 17 | --> |
18 | -<section flex layout='column' style="padding-left: 4px;"> | |
19 | - <md-chips flex | |
20 | - id="function_datakey_chips" | |
21 | - ng-required="true" | |
22 | - ng-model="funcDataKeys" md-autocomplete-snap | |
23 | - md-transform-chip="transformDataKeyChip($chip)" | |
24 | - md-require-match="false"> | |
25 | - <md-autocomplete | |
26 | - md-no-cache="false" | |
27 | - id="dataKey" | |
28 | - md-selected-item="selectedDataKey" | |
29 | - md-search-text="dataKeySearchText" | |
30 | - md-items="item in dataKeysSearch(dataKeySearchText)" | |
31 | - md-item-text="item.name" | |
32 | - md-min-length="0" | |
33 | - placeholder="{{ 'datakey.function-types' | translate }}" | |
34 | - md-menu-class="tb-func-datakey-autocomplete"> | |
35 | - <span md-highlight-text="dataKeySearchText" md-highlight-flags="^i">{{item}}</span> | |
36 | - <md-not-found> | |
37 | - <div class="tb-not-found"> | |
38 | - <div class="tb-no-entries" ng-if="!textIsNotEmpty(dataKeySearchText)"> | |
39 | - <span translate>device.no-keys-found</span> | |
18 | +<section class="tb-datasource-func" flex layout='column' | |
19 | + layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center"> | |
20 | + <md-input-container class="tb-datasource-name" md-no-float style="min-width: 200px;"> | |
21 | + <input name="datasourceName" | |
22 | + placeholder="{{ 'datasource.name' | translate }}" | |
23 | + ng-model="datasourceName" | |
24 | + aria-label="{{ 'datasource.name' | translate }}"> | |
25 | + </md-input-container> | |
26 | + <section flex layout='column' style="padding-left: 4px;"> | |
27 | + <md-chips flex | |
28 | + id="function_datakey_chips" | |
29 | + ng-required="true" | |
30 | + ng-model="funcDataKeys" md-autocomplete-snap | |
31 | + md-transform-chip="transformDataKeyChip($chip)" | |
32 | + md-require-match="false"> | |
33 | + <md-autocomplete | |
34 | + md-no-cache="false" | |
35 | + id="dataKey" | |
36 | + md-selected-item="selectedDataKey" | |
37 | + md-search-text="dataKeySearchText" | |
38 | + md-items="item in dataKeysSearch(dataKeySearchText)" | |
39 | + md-item-text="item.name" | |
40 | + md-min-length="0" | |
41 | + placeholder="{{ 'datakey.function-types' | translate }}" | |
42 | + md-menu-class="tb-func-datakey-autocomplete"> | |
43 | + <span md-highlight-text="dataKeySearchText" md-highlight-flags="^i">{{item}}</span> | |
44 | + <md-not-found> | |
45 | + <div class="tb-not-found"> | |
46 | + <div class="tb-no-entries" ng-if="!textIsNotEmpty(dataKeySearchText)"> | |
47 | + <span translate>device.no-keys-found</span> | |
48 | + </div> | |
49 | + <div ng-if="textIsNotEmpty(dataKeySearchText)"> | |
50 | + <span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:'...'}}" }'>device.no-key-matching</span> | |
51 | + <span> | |
52 | + <a translate ng-click="createKey($event, '#function_datakey_chips')">device.create-new-key</a> | |
53 | + </span> | |
54 | + </div> | |
40 | 55 | </div> |
41 | - <div ng-if="textIsNotEmpty(dataKeySearchText)"> | |
42 | - <span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:'...'}}" }'>device.no-key-matching</span> | |
43 | - <span> | |
44 | - <a translate ng-click="createKey($event, '#function_datakey_chips')">device.create-new-key</a> | |
45 | - </span> | |
46 | - </div> | |
47 | - </div> | |
48 | - </md-not-found> | |
49 | - </md-autocomplete> | |
50 | - <md-chip-template> | |
51 | - <div layout="row" layout-align="start center" class="tb-attribute-chip"> | |
52 | - <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;"> | |
53 | - <div class="tb-color-result" ng-style="{background: $chip.color}"></div> | |
54 | - </div> | |
55 | - <div layout="row" flex> | |
56 | - <div class="tb-chip-label"> | |
57 | - {{$chip.label}} | |
56 | + </md-not-found> | |
57 | + </md-autocomplete> | |
58 | + <md-chip-template> | |
59 | + <div layout="row" layout-align="start center" class="tb-attribute-chip"> | |
60 | + <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;"> | |
61 | + <div class="tb-color-result" ng-style="{background: $chip.color}"></div> | |
58 | 62 | </div> |
59 | - <div class="tb-chip-separator">: </div> | |
60 | - <div class="tb-chip-label"> | |
61 | - <strong>{{$chip.name}}</strong> | |
63 | + <div layout="row" flex> | |
64 | + <div class="tb-chip-label"> | |
65 | + {{$chip.label}} | |
66 | + </div> | |
67 | + <div class="tb-chip-separator">: </div> | |
68 | + <div class="tb-chip-label"> | |
69 | + <strong>{{$chip.name}}</strong> | |
70 | + </div> | |
62 | 71 | </div> |
72 | + <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32"> | |
73 | + <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon> | |
74 | + </md-button> | |
63 | 75 | </div> |
64 | - <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32"> | |
65 | - <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon> | |
66 | - </md-button> | |
67 | - </div> | |
68 | - </md-chip-template> | |
69 | - </md-chips> | |
70 | - <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert"> | |
71 | - <div translate ng-message="funcTypes" class="tb-error-message">datakey.function-types-required</div> | |
72 | - </div> | |
76 | + </md-chip-template> | |
77 | + </md-chips> | |
78 | + <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert"> | |
79 | + <div translate ng-message="funcTypes" class="tb-error-message">datakey.function-types-required</div> | |
80 | + </div> | |
81 | + </section> | |
73 | 82 | </section> | ... | ... |
... | ... | @@ -15,7 +15,7 @@ |
15 | 15 | */ |
16 | 16 | /* eslint-disable import/no-unresolved, import/default */ |
17 | 17 | |
18 | -import entityAliasDialogTemplate from '../entity/entity-alias-dialog.tpl.html'; | |
18 | +import entityAliasDialogTemplate from '../entity/alias/entity-alias-dialog.tpl.html'; | |
19 | 19 | |
20 | 20 | /* eslint-enable import/no-unresolved, import/default */ |
21 | 21 | ... | ... |
... | ... | @@ -15,7 +15,7 @@ |
15 | 15 | */ |
16 | 16 | /* eslint-disable import/no-unresolved, import/default */ |
17 | 17 | |
18 | -import entityAliasesTemplate from '../entity/entity-aliases.tpl.html'; | |
18 | +import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html'; | |
19 | 19 | import dashboardSettingsTemplate from './dashboard-settings.tpl.html'; |
20 | 20 | import manageDashboardLayoutsTemplate from './layouts/manage-dashboard-layouts.tpl.html'; |
21 | 21 | import manageDashboardStatesTemplate from './states/manage-dashboard-states.tpl.html'; | ... | ... |
... | ... | @@ -15,7 +15,7 @@ |
15 | 15 | */ |
16 | 16 | /* eslint-disable import/no-unresolved, import/default */ |
17 | 17 | |
18 | -import entityAliasDialogTemplate from '../entity/entity-alias-dialog.tpl.html'; | |
18 | +import entityAliasDialogTemplate from '../entity/alias/entity-alias-dialog.tpl.html'; | |
19 | 19 | import editWidgetTemplate from './edit-widget.tpl.html'; |
20 | 20 | |
21 | 21 | /* eslint-enable import/no-unresolved, import/default */ | ... | ... |
ui/src/app/entity/alias/aliases-entity-select-button.tpl.html
renamed from
ui/src/app/entity/aliases-entity-select-button.tpl.html
ui/src/app/entity/alias/aliases-entity-select-panel.controller.js
renamed from
ui/src/app/entity/aliases-entity-select-panel.controller.js
ui/src/app/entity/alias/aliases-entity-select-panel.tpl.html
renamed from
ui/src/app/entity/aliases-entity-select-panel.tpl.html
ui/src/app/entity/alias/aliases-entity-select.directive.js
renamed from
ui/src/app/entity/aliases-entity-select.directive.js
ui/src/app/entity/alias/aliases-entity-select.scss
renamed from
ui/src/app/entity/aliases-entity-select.scss
ui/src/app/entity/alias/entity-alias-dialog.controller.js
renamed from
ui/src/app/entity/entity-alias-dialog.controller.js
ui/src/app/entity/alias/entity-alias-dialog.scss
renamed from
ui/src/app/entity/entity-alias-dialog.scss
ui/src/app/entity/alias/entity-alias-dialog.tpl.html
renamed from
ui/src/app/entity/entity-alias-dialog.tpl.html
ui/src/app/entity/alias/entity-aliases.controller.js
renamed from
ui/src/app/entity/entity-aliases.controller.js
ui/src/app/entity/alias/entity-aliases.scss
renamed from
ui/src/app/entity/entity-aliases.scss
ui/src/app/entity/alias/entity-aliases.tpl.html
renamed from
ui/src/app/entity/entity-aliases.tpl.html
... | ... | @@ -74,7 +74,106 @@ export default function EntityFilterViewDirective($compile, $templateCache, $q, |
74 | 74 | scope.filterDisplayValue = $translate.instant('alias.filter-type-device-type-description', {deviceType: deviceType}); |
75 | 75 | } |
76 | 76 | break; |
77 | - //TODO: Alias filter | |
77 | + case types.aliasFilterType.relationsQuery.value: | |
78 | + var rootEntityText; | |
79 | + var directionText; | |
80 | + var allEntitiesText = $translate.instant('alias.all-entities'); | |
81 | + var anyRelationText = $translate.instant('alias.any-relation'); | |
82 | + if (scope.filter.rootStateEntity) { | |
83 | + rootEntityText = $translate.instant('alias.state-entity'); | |
84 | + } else { | |
85 | + rootEntityText = $translate.instant(types.entityTypeTranslations[scope.filter.rootEntity.entityType].type); | |
86 | + } | |
87 | + directionText = $translate.instant('relation.direction-type.' + scope.filter.direction); | |
88 | + var relationFilters = scope.filter.filters; | |
89 | + if (relationFilters && relationFilters.length) { | |
90 | + var relationFiltersDisplayValues = []; | |
91 | + relationFilters.forEach(function(relationFilter) { | |
92 | + var entitiesText; | |
93 | + if (relationFilter.entityTypes && relationFilter.entityTypes.length) { | |
94 | + var entitiesNamesList = []; | |
95 | + relationFilter.entityTypes.forEach(function(entityType) { | |
96 | + entitiesNamesList.push( | |
97 | + $translate.instant(types.entityTypeTranslations[entityType].typePlural) | |
98 | + ); | |
99 | + }); | |
100 | + entitiesText = entitiesNamesList.join(', '); | |
101 | + } else { | |
102 | + entitiesText = allEntitiesText; | |
103 | + } | |
104 | + var relationTypeText; | |
105 | + if (relationFilter.relationType && relationFilter.relationType.length) { | |
106 | + relationTypeText = "'" + relationFilter.relationType + "'"; | |
107 | + } else { | |
108 | + relationTypeText = anyRelationText; | |
109 | + } | |
110 | + var relationFilterDisplayValue = $translate.instant('alias.filter-type-relations-query-description', | |
111 | + { | |
112 | + entities: entitiesText, | |
113 | + relationType: relationTypeText, | |
114 | + direction: directionText, | |
115 | + rootEntity: rootEntityText | |
116 | + } | |
117 | + ); | |
118 | + relationFiltersDisplayValues.push(relationFilterDisplayValue); | |
119 | + }); | |
120 | + scope.filterDisplayValue = relationFiltersDisplayValues.join(', '); | |
121 | + } else { | |
122 | + scope.filterDisplayValue = $translate.instant('alias.filter-type-relations-query-description', | |
123 | + { | |
124 | + entities: allEntitiesText, | |
125 | + relationType: anyRelationText, | |
126 | + direction: directionText, | |
127 | + rootEntity: rootEntityText | |
128 | + } | |
129 | + ); | |
130 | + } | |
131 | + break; | |
132 | + case types.aliasFilterType.assetSearchQuery.value: | |
133 | + case types.aliasFilterType.deviceSearchQuery.value: | |
134 | + allEntitiesText = $translate.instant('alias.all-entities'); | |
135 | + anyRelationText = $translate.instant('alias.any-relation'); | |
136 | + if (scope.filter.rootStateEntity) { | |
137 | + rootEntityText = $translate.instant('alias.state-entity'); | |
138 | + } else { | |
139 | + rootEntityText = $translate.instant(types.entityTypeTranslations[scope.filter.rootEntity.entityType].type); | |
140 | + } | |
141 | + directionText = $translate.instant('relation.direction-type.' + scope.filter.direction); | |
142 | + var relationTypeText; | |
143 | + if (scope.filter.relationType && scope.filter.relationType.length) { | |
144 | + relationTypeText = "'" + scope.filter.relationType + "'"; | |
145 | + } else { | |
146 | + relationTypeText = anyRelationText; | |
147 | + } | |
148 | + | |
149 | + var translationValues = { | |
150 | + relationType: relationTypeText, | |
151 | + direction: directionText, | |
152 | + rootEntity: rootEntityText | |
153 | + } | |
154 | + | |
155 | + if (scope.filter.type == types.aliasFilterType.assetSearchQuery.value) { | |
156 | + var assetTypesQuoted = []; | |
157 | + scope.filter.assetTypes.forEach(function(assetType) { | |
158 | + assetTypesQuoted.push("'"+assetType+"'"); | |
159 | + }); | |
160 | + var assetTypesText = assetTypesQuoted.join(', '); | |
161 | + translationValues.assetTypes = assetTypesText; | |
162 | + scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-search-query-description', | |
163 | + translationValues | |
164 | + ); | |
165 | + } else { | |
166 | + var deviceTypesQuoted = []; | |
167 | + scope.filter.deviceTypes.forEach(function(deviceType) { | |
168 | + deviceTypesQuoted.push("'"+deviceType+"'"); | |
169 | + }); | |
170 | + var deviceTypesText = deviceTypesQuoted.join(', '); | |
171 | + translationValues.deviceTypes = deviceTypesText; | |
172 | + scope.filterDisplayValue = $translate.instant('alias.filter-type-device-search-query-description', | |
173 | + translationValues | |
174 | + ); | |
175 | + } | |
176 | + break; | |
78 | 177 | default: |
79 | 178 | scope.filterDisplayValue = scope.filter.type; |
80 | 179 | break; | ... | ... |
... | ... | @@ -63,7 +63,23 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc |
63 | 63 | filter.deviceType = null; |
64 | 64 | filter.deviceNameFilter = ''; |
65 | 65 | break; |
66 | - //TODO: Alias filter | |
66 | + case types.aliasFilterType.relationsQuery.value: | |
67 | + case types.aliasFilterType.assetSearchQuery.value: | |
68 | + case types.aliasFilterType.deviceSearchQuery.value: | |
69 | + filter.rootStateEntity = false; | |
70 | + filter.rootEntity = null; | |
71 | + filter.direction = types.entitySearchDirection.from; | |
72 | + filter.maxLevel = 1; | |
73 | + if (filter.type === types.aliasFilterType.relationsQuery.value) { | |
74 | + filter.filters = []; | |
75 | + } else if (filter.type === types.aliasFilterType.assetSearchQuery.value) { | |
76 | + filter.relationType = null; | |
77 | + filter.assetTypes = []; | |
78 | + } else if (filter.type === types.aliasFilterType.deviceSearchQuery.value) { | |
79 | + filter.relationType = null; | |
80 | + filter.deviceTypes = []; | |
81 | + } | |
82 | + break; | |
67 | 83 | } |
68 | 84 | scope.filter = filter; |
69 | 85 | } | ... | ... |
... | ... | @@ -16,4 +16,21 @@ |
16 | 16 | |
17 | 17 | .tb-entity-filter { |
18 | 18 | |
19 | + #relationsQueryFilter { | |
20 | + padding-top: 20px; | |
21 | + tb-entity-select { | |
22 | + min-height: 92px; | |
23 | + } | |
24 | + } | |
25 | + | |
26 | + .tb-root-state-entity-switch { | |
27 | + padding-left: 10px; | |
28 | + .root-state-entity-switch { | |
29 | + margin: 0; | |
30 | + } | |
31 | + .root-state-entity-label { | |
32 | + margin: 5px 0; | |
33 | + } | |
34 | + } | |
35 | + | |
19 | 36 | } |
\ No newline at end of file | ... | ... |
... | ... | @@ -88,4 +88,146 @@ |
88 | 88 | aria-label="{{ 'device.name-starts-with' | translate }}"> |
89 | 89 | </md-input-container> |
90 | 90 | </section> |
91 | + <section layout="column" ng-if="filter.type == types.aliasFilterType.relationsQuery.value" id="relationsQueryFilter"> | |
92 | + <label class="tb-small">{{ 'alias.root-entity' | translate }}</label> | |
93 | + <div flex layout="row"> | |
94 | + <tb-entity-select flex | |
95 | + the-form="theForm" | |
96 | + tb-required="!filter.rootStateEntity" | |
97 | + ng-disabled="filter.rootStateEntity" | |
98 | + ng-model="filter.rootEntity"> | |
99 | + </tb-entity-select> | |
100 | + <section class="tb-root-state-entity-switch" layout="column" layout-align="start center"> | |
101 | + <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label> | |
102 | + <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity" | |
103 | + aria-label="{{ 'alias.root-state-entity' | translate }}"> | |
104 | + </md-switch> | |
105 | + </section> | |
106 | + </div> | |
107 | + <div flex layout="row"> | |
108 | + <md-input-container class="md-block" style="min-width: 100px;"> | |
109 | + <label translate>relation.direction</label> | |
110 | + <md-select required ng-model="filter.direction"> | |
111 | + <md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction"> | |
112 | + {{ ('relation.search-direction.' + direction) | translate}} | |
113 | + </md-option> | |
114 | + </md-select> | |
115 | + </md-input-container> | |
116 | + <md-input-container flex class="md-block"> | |
117 | + <label translate>alias.max-relation-level</label> | |
118 | + <input name="maxRelationLevel" | |
119 | + type="number" | |
120 | + min="1" | |
121 | + step="1" | |
122 | + placeholder="{{ 'alias.unlimited-level' | translate }}" | |
123 | + ng-model="filter.maxLevel" | |
124 | + aria-label="{{ 'alias.max-relation-level' | translate }}"> | |
125 | + </md-input-container> | |
126 | + </div> | |
127 | + <div class="md-caption" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>relation.relation-filters</div> | |
128 | + <tb-relation-filters | |
129 | + ng-model="filter.filters" | |
130 | + allowed-entity-types="allowedEntityTypes"> | |
131 | + </tb-relation-filters> | |
132 | + </section> | |
133 | + <section layout="column" ng-if="filter.type == types.aliasFilterType.assetSearchQuery.value" id="assetSearchQueryFilter"> | |
134 | + <label class="tb-small">{{ 'alias.root-entity' | translate }}</label> | |
135 | + <div flex layout="row"> | |
136 | + <tb-entity-select flex | |
137 | + the-form="theForm" | |
138 | + tb-required="!filter.rootStateEntity" | |
139 | + ng-disabled="filter.rootStateEntity" | |
140 | + ng-model="filter.rootEntity"> | |
141 | + </tb-entity-select> | |
142 | + <section class="tb-root-state-entity-switch" layout="column" layout-align="start center"> | |
143 | + <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label> | |
144 | + <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity" | |
145 | + aria-label="{{ 'alias.root-state-entity' | translate }}"> | |
146 | + </md-switch> | |
147 | + </section> | |
148 | + </div> | |
149 | + <div flex layout="row"> | |
150 | + <md-input-container class="md-block" style="min-width: 100px;"> | |
151 | + <label translate>relation.direction</label> | |
152 | + <md-select required ng-model="filter.direction"> | |
153 | + <md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction"> | |
154 | + {{ ('relation.search-direction.' + direction) | translate}} | |
155 | + </md-option> | |
156 | + </md-select> | |
157 | + </md-input-container> | |
158 | + <md-input-container flex class="md-block"> | |
159 | + <label translate>alias.max-relation-level</label> | |
160 | + <input name="maxRelationLevel" | |
161 | + type="number" | |
162 | + min="1" | |
163 | + step="1" | |
164 | + placeholder="{{ 'alias.unlimited-level' | translate }}" | |
165 | + ng-model="filter.maxLevel" | |
166 | + aria-label="{{ 'alias.max-relation-level' | translate }}"> | |
167 | + </md-input-container> | |
168 | + </div> | |
169 | + <div class="md-caption" style="color: rgba(0,0,0,0.57);" translate>relation.relation-type</div> | |
170 | + <tb-relation-type-autocomplete flex | |
171 | + hide-label | |
172 | + the-form="theForm" | |
173 | + ng-model="filter.relationType" | |
174 | + tb-required="false"> | |
175 | + </tb-relation-type-autocomplete> | |
176 | + <div class="md-caption tb-required" style="color: rgba(0,0,0,0.57);" translate>asset.asset-types</div> | |
177 | + <tb-entity-subtype-list | |
178 | + tb-required="true" | |
179 | + entity-type="types.entityType.asset" | |
180 | + ng-model="filter.assetTypes"> | |
181 | + </tb-entity-subtype-list> | |
182 | + </section> | |
183 | + <section layout="column" ng-if="filter.type == types.aliasFilterType.deviceSearchQuery.value" id="deviceSearchQueryFilter"> | |
184 | + <label class="tb-small">{{ 'alias.root-entity' | translate }}</label> | |
185 | + <div flex layout="row"> | |
186 | + <tb-entity-select flex | |
187 | + the-form="theForm" | |
188 | + tb-required="!filter.rootStateEntity" | |
189 | + ng-disabled="filter.rootStateEntity" | |
190 | + ng-model="filter.rootEntity"> | |
191 | + </tb-entity-select> | |
192 | + <section class="tb-root-state-entity-switch" layout="column" layout-align="start center"> | |
193 | + <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label> | |
194 | + <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity" | |
195 | + aria-label="{{ 'alias.root-state-entity' | translate }}"> | |
196 | + </md-switch> | |
197 | + </section> | |
198 | + </div> | |
199 | + <div flex layout="row"> | |
200 | + <md-input-container class="md-block" style="min-width: 100px;"> | |
201 | + <label translate>relation.direction</label> | |
202 | + <md-select required ng-model="filter.direction"> | |
203 | + <md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction"> | |
204 | + {{ ('relation.search-direction.' + direction) | translate}} | |
205 | + </md-option> | |
206 | + </md-select> | |
207 | + </md-input-container> | |
208 | + <md-input-container flex class="md-block"> | |
209 | + <label translate>alias.max-relation-level</label> | |
210 | + <input name="maxRelationLevel" | |
211 | + type="number" | |
212 | + min="1" | |
213 | + step="1" | |
214 | + placeholder="{{ 'alias.unlimited-level' | translate }}" | |
215 | + ng-model="filter.maxLevel" | |
216 | + aria-label="{{ 'alias.max-relation-level' | translate }}"> | |
217 | + </md-input-container> | |
218 | + </div> | |
219 | + <div class="md-caption" style="color: rgba(0,0,0,0.57);" translate>relation.relation-type</div> | |
220 | + <tb-relation-type-autocomplete flex | |
221 | + hide-label | |
222 | + the-form="theForm" | |
223 | + ng-model="filter.relationType" | |
224 | + tb-required="false"> | |
225 | + </tb-relation-type-autocomplete> | |
226 | + <div class="md-caption tb-required" style="color: rgba(0,0,0,0.57);" translate>device.device-types</div> | |
227 | + <tb-entity-subtype-list | |
228 | + tb-required="true" | |
229 | + entity-type="types.entityType.device" | |
230 | + ng-model="filter.deviceTypes"> | |
231 | + </tb-entity-subtype-list> | |
232 | + </section> | |
91 | 233 | </div> | ... | ... |
... | ... | @@ -114,6 +114,9 @@ export default function EntitySubtypeAutocomplete($compile, $templateCache, $q, |
114 | 114 | scope.selectEntitySubtypeText = 'asset.select-asset-type'; |
115 | 115 | scope.entitySubtypeText = 'asset.asset-type'; |
116 | 116 | scope.entitySubtypeRequiredText = 'asset.asset-type-required'; |
117 | + scope.$on('assetSaved', function() { | |
118 | + scope.entitySubtypes = null; | |
119 | + }); | |
117 | 120 | } else if (scope.entityType == types.entityType.device) { |
118 | 121 | scope.selectEntitySubtypeText = 'device.select-device-type'; |
119 | 122 | scope.entitySubtypeText = 'device.device-type'; | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +/* eslint-disable import/no-unresolved, import/default */ | |
18 | + | |
19 | +import entitySubtypeListTemplate from './entity-subtype-list.tpl.html'; | |
20 | + | |
21 | +/* eslint-enable import/no-unresolved, import/default */ | |
22 | + | |
23 | +import './entity-subtype-list.scss'; | |
24 | + | |
25 | +/*@ngInject*/ | |
26 | +export default function EntitySubtypeListDirective($compile, $templateCache, $q, $mdUtil, $translate, $filter, types, assetService, deviceService) { | |
27 | + | |
28 | + var linker = function (scope, element, attrs, ngModelCtrl) { | |
29 | + | |
30 | + var template = $templateCache.get(entitySubtypeListTemplate); | |
31 | + element.html(template); | |
32 | + | |
33 | + scope.ngModelCtrl = ngModelCtrl; | |
34 | + | |
35 | + | |
36 | + scope.entitySubtypesList = []; | |
37 | + scope.entitySubtypes = null; | |
38 | + | |
39 | + if (scope.entityType == types.entityType.asset) { | |
40 | + scope.placeholder = scope.tbRequired ? $translate.instant('asset.enter-asset-type') | |
41 | + : $translate.instant('asset.any-asset'); | |
42 | + scope.secondaryPlaceholder = '+' + $translate.instant('asset.asset-type'); | |
43 | + scope.noSubtypesMathingText = 'asset.no-asset-types-matching'; | |
44 | + scope.subtypeListEmptyText = 'asset.asset-type-list-empty'; | |
45 | + } else if (scope.entityType == types.entityType.device) { | |
46 | + scope.placeholder = scope.tbRequired ? $translate.instant('device.enter-device-type') | |
47 | + : $translate.instant('device.any-device'); | |
48 | + scope.secondaryPlaceholder = '+' + $translate.instant('device.device-type'); | |
49 | + scope.noSubtypesMathingText = 'device.no-device-types-matching'; | |
50 | + scope.subtypeListEmptyText = 'device.device-type-list-empty'; | |
51 | + } | |
52 | + | |
53 | + scope.$watch('tbRequired', function () { | |
54 | + scope.updateValidity(); | |
55 | + }); | |
56 | + | |
57 | + scope.fetchEntitySubtypes = function(searchText) { | |
58 | + var deferred = $q.defer(); | |
59 | + loadSubTypes().then( | |
60 | + function success(subTypes) { | |
61 | + var result = $filter('filter')(subTypes, {'$': searchText}); | |
62 | + if (result && result.length) { | |
63 | + deferred.resolve(result); | |
64 | + } else { | |
65 | + deferred.resolve([searchText]); | |
66 | + } | |
67 | + }, | |
68 | + function fail() { | |
69 | + deferred.reject(); | |
70 | + } | |
71 | + ); | |
72 | + return deferred.promise; | |
73 | + } | |
74 | + | |
75 | + scope.updateValidity = function() { | |
76 | + var value = ngModelCtrl.$viewValue; | |
77 | + var valid = !scope.tbRequired || value && value.length > 0; | |
78 | + ngModelCtrl.$setValidity('entitySubtypesList', valid); | |
79 | + } | |
80 | + | |
81 | + ngModelCtrl.$render = function () { | |
82 | + scope.entitySubtypesList = ngModelCtrl.$viewValue; | |
83 | + if (!scope.entitySubtypesList) { | |
84 | + scope.entitySubtypesList = []; | |
85 | + } | |
86 | + } | |
87 | + | |
88 | + scope.$watch('entitySubtypesList', function () { | |
89 | + ngModelCtrl.$setViewValue(scope.entitySubtypesList); | |
90 | + scope.updateValidity(); | |
91 | + }, true); | |
92 | + | |
93 | + function loadSubTypes() { | |
94 | + var deferred = $q.defer(); | |
95 | + if (!scope.entitySubtypes) { | |
96 | + var entitySubtypesPromise; | |
97 | + if (scope.entityType == types.entityType.asset) { | |
98 | + entitySubtypesPromise = assetService.getAssetTypes(); | |
99 | + } else if (scope.entityType == types.entityType.device) { | |
100 | + entitySubtypesPromise = deviceService.getDeviceTypes(); | |
101 | + } | |
102 | + if (entitySubtypesPromise) { | |
103 | + entitySubtypesPromise.then( | |
104 | + function success(types) { | |
105 | + scope.entitySubtypes = []; | |
106 | + types.forEach(function (type) { | |
107 | + scope.entitySubtypes.push(type.type); | |
108 | + }); | |
109 | + deferred.resolve(scope.entitySubtypes); | |
110 | + }, | |
111 | + function fail() { | |
112 | + deferred.reject(); | |
113 | + } | |
114 | + ); | |
115 | + } else { | |
116 | + deferred.reject(); | |
117 | + } | |
118 | + } else { | |
119 | + deferred.resolve(scope.entitySubtypes); | |
120 | + } | |
121 | + return deferred.promise; | |
122 | + } | |
123 | + | |
124 | + $compile(element.contents())(scope); | |
125 | + | |
126 | + $mdUtil.nextTick(function(){ | |
127 | + var inputElement = angular.element('input', element); | |
128 | + inputElement.on('blur', function() { | |
129 | + scope.inputTouched = true; | |
130 | + } ); | |
131 | + }); | |
132 | + | |
133 | + } | |
134 | + | |
135 | + return { | |
136 | + restrict: "E", | |
137 | + require: "^ngModel", | |
138 | + link: linker, | |
139 | + scope: { | |
140 | + disabled:'=ngDisabled', | |
141 | + tbRequired: '=?', | |
142 | + entityType: "=" | |
143 | + } | |
144 | + }; | |
145 | + | |
146 | +} | ... | ... |
ui/src/app/entity/entity-subtype-list.scss
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +/*.tb-entity-subtype-list { | |
18 | + #entity_subtype_list_chips { | |
19 | + .md-chips { | |
20 | + padding-bottom: 1px; | |
21 | + } | |
22 | + } | |
23 | + .tb-error-messages { | |
24 | + margin-top: -11px; | |
25 | + height: 35px; | |
26 | + .tb-error-message { | |
27 | + padding-left: 1px; | |
28 | + } | |
29 | + } | |
30 | +}*/ | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2017 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | + | |
19 | +<section flex layout='column' class="tb-entity-subtype-list"> | |
20 | + <md-chips flex | |
21 | + readonly="disabled" | |
22 | + id="entity_subtype_list_chips" | |
23 | + ng-required="tbRequired" | |
24 | + ng-model="entitySubtypesList" | |
25 | + placeholder="{{placeholder}}" | |
26 | + secondary-placeholder="{{secondaryPlaceholder}}" | |
27 | + md-autocomplete-snap | |
28 | + md-require-match="false"> | |
29 | + <md-autocomplete | |
30 | + md-no-cache="true" | |
31 | + id="entitySubtype" | |
32 | + md-selected-item="selectedEntitySubtype" | |
33 | + md-search-text="entitySubtypeSearchText" | |
34 | + md-items="item in fetchEntitySubtypes(entitySubtypeSearchText)" | |
35 | + md-item-text="item" | |
36 | + md-min-length="0" | |
37 | + placeholder="{{ (!entitySubtypesList || !entitySubtypesList.length) ? placeholder : secondaryPlaceholder }}"> | |
38 | + <md-item-template> | |
39 | + <span md-highlight-text="entitySubtypeSearchText" md-highlight-flags="^i">{{item}}</span> | |
40 | + </md-item-template> | |
41 | + <md-not-found> | |
42 | + <span translate translate-values='{ entitySubtype: entitySubtypeSearchText }'>{{noSubtypesMathingText}}</span> | |
43 | + </md-not-found> | |
44 | + </md-autocomplete> | |
45 | + <md-chip-template> | |
46 | + <span> | |
47 | + <strong>{{$chip}}</strong> | |
48 | + </span> | |
49 | + </md-chip-template> | |
50 | + </md-chips> | |
51 | + <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" ng-if="inputTouched && tbRequired" role="alert"> | |
52 | + <div translate ng-message="entitySubtypesList" class="tb-error-message">{{subtypeListEmptyText}}</div> | |
53 | + </div> | |
54 | +</section> | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +/* eslint-disable import/no-unresolved, import/default */ | |
18 | + | |
19 | +import entityTypeListTemplate from './entity-type-list.tpl.html'; | |
20 | + | |
21 | +/* eslint-enable import/no-unresolved, import/default */ | |
22 | + | |
23 | +import './entity-type-list.scss'; | |
24 | + | |
25 | +/*@ngInject*/ | |
26 | +export default function EntityTypeListDirective($compile, $templateCache, $q, $mdUtil, $translate, $filter, types, entityService) { | |
27 | + | |
28 | + var linker = function (scope, element, attrs, ngModelCtrl) { | |
29 | + | |
30 | + var template = $templateCache.get(entityTypeListTemplate); | |
31 | + element.html(template); | |
32 | + | |
33 | + scope.ngModelCtrl = ngModelCtrl; | |
34 | + | |
35 | + scope.placeholder = scope.tbRequired ? $translate.instant('entity.enter-entity-type') | |
36 | + : $translate.instant('entity.any-entity'); | |
37 | + scope.secondaryPlaceholder = '+' + $translate.instant('entity.entity-type'); | |
38 | + | |
39 | + var entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes); | |
40 | + scope.entityTypesList = []; | |
41 | + for (var type in entityTypes) { | |
42 | + var entityTypeInfo = {}; | |
43 | + entityTypeInfo.value = entityTypes[type]; | |
44 | + entityTypeInfo.name = $translate.instant(types.entityTypeTranslations[entityTypeInfo.value].type) + ''; | |
45 | + scope.entityTypesList.push(entityTypeInfo); | |
46 | + } | |
47 | + | |
48 | + scope.$watch('tbRequired', function () { | |
49 | + scope.updateValidity(); | |
50 | + }); | |
51 | + | |
52 | + scope.fetchEntityTypes = function(searchText) { | |
53 | + var deferred = $q.defer(); | |
54 | + var entityTypes = $filter('filter')(scope.entityTypesList, {name: searchText}); | |
55 | + deferred.resolve(entityTypes); | |
56 | + return deferred.promise; | |
57 | + } | |
58 | + | |
59 | + scope.updateValidity = function() { | |
60 | + var value = ngModelCtrl.$viewValue; | |
61 | + var valid = !scope.tbRequired || value && value.length > 0; | |
62 | + ngModelCtrl.$setValidity('entityTypeList', valid); | |
63 | + } | |
64 | + | |
65 | + ngModelCtrl.$render = function () { | |
66 | + scope.entityTypeList = []; | |
67 | + var value = ngModelCtrl.$viewValue; | |
68 | + if (value && value.length) { | |
69 | + value.forEach(function(type) { | |
70 | + var entityTypeInfo = {}; | |
71 | + entityTypeInfo.value = type; | |
72 | + entityTypeInfo.name = $translate.instant(types.entityTypeTranslations[entityTypeInfo.value].type) + ''; | |
73 | + scope.entityTypeList.push(entityTypeInfo); | |
74 | + }); | |
75 | + } | |
76 | + } | |
77 | + | |
78 | + scope.$watch('entityTypeList', function () { | |
79 | + var values = []; | |
80 | + if (scope.entityTypeList && scope.entityTypeList.length) { | |
81 | + scope.entityTypeList.forEach(function(entityType) { | |
82 | + values.push(entityType.value); | |
83 | + }); | |
84 | + } | |
85 | + ngModelCtrl.$setViewValue(values); | |
86 | + scope.updateValidity(); | |
87 | + }, true); | |
88 | + | |
89 | + $compile(element.contents())(scope); | |
90 | + | |
91 | + $mdUtil.nextTick(function(){ | |
92 | + var inputElement = angular.element('input', element); | |
93 | + inputElement.on('blur', function() { | |
94 | + scope.inputTouched = true; | |
95 | + } ); | |
96 | + }); | |
97 | + | |
98 | + } | |
99 | + | |
100 | + return { | |
101 | + restrict: "E", | |
102 | + require: "^ngModel", | |
103 | + link: linker, | |
104 | + scope: { | |
105 | + disabled:'=ngDisabled', | |
106 | + tbRequired: '=?', | |
107 | + allowedEntityTypes: '=?' | |
108 | + } | |
109 | + }; | |
110 | + | |
111 | +} | ... | ... |
ui/src/app/entity/entity-type-list.scss
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +/*.tb-entity-type-list { | |
18 | + #entity_type_list_chips { | |
19 | + .md-chips { | |
20 | + padding-bottom: 1px; | |
21 | + } | |
22 | + } | |
23 | + .tb-error-messages { | |
24 | + margin-top: -11px; | |
25 | + height: 35px; | |
26 | + .tb-error-message { | |
27 | + padding-left: 1px; | |
28 | + } | |
29 | + } | |
30 | +}*/ | |
\ No newline at end of file | ... | ... |
ui/src/app/entity/entity-type-list.tpl.html
0 → 100644
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2017 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | + | |
19 | +<section flex layout='column' class="tb-entity-type-list"> | |
20 | + <md-chips flex | |
21 | + readonly="disabled" | |
22 | + id="entity_type_list_chips" | |
23 | + ng-required="tbRequired" | |
24 | + ng-model="entityTypeList" | |
25 | + placeholder="{{placeholder}}" | |
26 | + secondary-placeholder="{{secondaryPlaceholder}}" | |
27 | + md-autocomplete-snap | |
28 | + md-require-match="true"> | |
29 | + <md-autocomplete | |
30 | + md-no-cache="true" | |
31 | + id="entityType" | |
32 | + md-selected-item="selectedEntityType" | |
33 | + md-search-text="entityTypeSearchText" | |
34 | + md-items="item in fetchEntityTypes(entityTypeSearchText)" | |
35 | + md-item-text="item.name" | |
36 | + md-min-length="0" | |
37 | + placeholder="{{ (!entityTypeList || !entityTypeList.length) ? placeholder : secondaryPlaceholder }}"> | |
38 | + <md-item-template> | |
39 | + <span md-highlight-text="entityTypeSearchText" md-highlight-flags="^i">{{item.name}}</span> | |
40 | + </md-item-template> | |
41 | + <md-not-found> | |
42 | + <span translate translate-values='{ entityType: entityTypeSearchText }'>entity.no-entity-types-matching</span> | |
43 | + </md-not-found> | |
44 | + </md-autocomplete> | |
45 | + <md-chip-template> | |
46 | + <span> | |
47 | + <strong>{{$chip.name}}</strong> | |
48 | + </span> | |
49 | + </md-chip-template> | |
50 | + </md-chips> | |
51 | + <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" ng-if="inputTouched && tbRequired" role="alert"> | |
52 | + <div translate ng-message="entityTypeList" class="tb-error-message">entity.entity-type-list-empty</div> | |
53 | + </div> | |
54 | +</section> | ... | ... |
... | ... | @@ -23,7 +23,7 @@ import entityTypeSelectTemplate from './entity-type-select.tpl.html'; |
23 | 23 | /* eslint-enable import/no-unresolved, import/default */ |
24 | 24 | |
25 | 25 | /*@ngInject*/ |
26 | -export default function EntityTypeSelect($compile, $templateCache, utils, userService, types) { | |
26 | +export default function EntityTypeSelect($compile, $templateCache, utils, entityService, userService, types) { | |
27 | 27 | |
28 | 28 | var linker = function (scope, element, attrs, ngModelCtrl) { |
29 | 29 | var template = $templateCache.get(entityTypeSelectTemplate); |
... | ... | @@ -39,36 +39,7 @@ export default function EntityTypeSelect($compile, $templateCache, utils, userSe |
39 | 39 | |
40 | 40 | scope.ngModelCtrl = ngModelCtrl; |
41 | 41 | |
42 | - var authority = userService.getAuthority(); | |
43 | - scope.entityTypes = {}; | |
44 | - switch(authority) { | |
45 | - case 'SYS_ADMIN': | |
46 | - scope.entityTypes.tenant = types.entityType.tenant; | |
47 | - scope.entityTypes.rule = types.entityType.rule; | |
48 | - scope.entityTypes.plugin = types.entityType.plugin; | |
49 | - break; | |
50 | - case 'TENANT_ADMIN': | |
51 | - scope.entityTypes.device = types.entityType.device; | |
52 | - scope.entityTypes.asset = types.entityType.asset; | |
53 | - scope.entityTypes.customer = types.entityType.customer; | |
54 | - scope.entityTypes.rule = types.entityType.rule; | |
55 | - scope.entityTypes.plugin = types.entityType.plugin; | |
56 | - scope.entityTypes.dashboard = types.entityType.dashboard; | |
57 | - break; | |
58 | - case 'CUSTOMER_USER': | |
59 | - scope.entityTypes.device = types.entityType.device; | |
60 | - scope.entityTypes.asset = types.entityType.asset; | |
61 | - scope.entityTypes.dashboard = types.entityType.dashboard; | |
62 | - break; | |
63 | - } | |
64 | - | |
65 | - if (scope.allowedEntityTypes) { | |
66 | - for (var entityType in scope.entityTypes) { | |
67 | - if (scope.allowedEntityTypes.indexOf(scope.entityTypes[entityType]) === -1) { | |
68 | - delete scope.entityTypes[entityType]; | |
69 | - } | |
70 | - } | |
71 | - } | |
42 | + scope.entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes); | |
72 | 43 | |
73 | 44 | scope.typeName = function(type) { |
74 | 45 | return type ? types.entityTypeTranslations[type].type : ''; | ... | ... |
... | ... | @@ -14,9 +14,11 @@ |
14 | 14 | * limitations under the License. |
15 | 15 | */ |
16 | 16 | |
17 | -import EntityAliasesController from './entity-aliases.controller'; | |
18 | -import EntityAliasDialogController from './entity-alias-dialog.controller'; | |
17 | +import EntityAliasesController from './alias/entity-aliases.controller'; | |
18 | +import EntityAliasDialogController from './alias/entity-alias-dialog.controller'; | |
19 | 19 | import EntityTypeSelectDirective from './entity-type-select.directive'; |
20 | +import EntityTypeListDirective from './entity-type-list.directive'; | |
21 | +import EntitySubtypeListDirective from './entity-subtype-list.directive'; | |
20 | 22 | import EntitySubtypeSelectDirective from './entity-subtype-select.directive'; |
21 | 23 | import EntitySubtypeAutocompleteDirective from './entity-subtype-autocomplete.directive'; |
22 | 24 | import EntityAutocompleteDirective from './entity-autocomplete.directive'; |
... | ... | @@ -24,11 +26,12 @@ import EntityListDirective from './entity-list.directive'; |
24 | 26 | import EntitySelectDirective from './entity-select.directive'; |
25 | 27 | import EntityFilterDirective from './entity-filter.directive'; |
26 | 28 | import EntityFilterViewDirective from './entity-filter-view.directive'; |
27 | -import AliasesEntitySelectPanelController from './aliases-entity-select-panel.controller'; | |
28 | -import AliasesEntitySelectDirective from './aliases-entity-select.directive'; | |
29 | +import AliasesEntitySelectPanelController from './alias/aliases-entity-select-panel.controller'; | |
30 | +import AliasesEntitySelectDirective from './alias/aliases-entity-select.directive'; | |
29 | 31 | import AddAttributeDialogController from './attribute/add-attribute-dialog.controller'; |
30 | 32 | import AddWidgetToDashboardDialogController from './attribute/add-widget-to-dashboard-dialog.controller'; |
31 | 33 | import AttributeTableDirective from './attribute/attribute-table.directive'; |
34 | +import RelationFiltersDirective from './relation/relation-filters.directive'; | |
32 | 35 | import RelationTableDirective from './relation/relation-table.directive'; |
33 | 36 | import RelationTypeAutocompleteDirective from './relation/relation-type-autocomplete.directive'; |
34 | 37 | |
... | ... | @@ -39,6 +42,8 @@ export default angular.module('thingsboard.entity', []) |
39 | 42 | .controller('AddAttributeDialogController', AddAttributeDialogController) |
40 | 43 | .controller('AddWidgetToDashboardDialogController', AddWidgetToDashboardDialogController) |
41 | 44 | .directive('tbEntityTypeSelect', EntityTypeSelectDirective) |
45 | + .directive('tbEntityTypeList', EntityTypeListDirective) | |
46 | + .directive('tbEntitySubtypeList', EntitySubtypeListDirective) | |
42 | 47 | .directive('tbEntitySubtypeSelect', EntitySubtypeSelectDirective) |
43 | 48 | .directive('tbEntitySubtypeAutocomplete', EntitySubtypeAutocompleteDirective) |
44 | 49 | .directive('tbEntityAutocomplete', EntityAutocompleteDirective) |
... | ... | @@ -48,6 +53,7 @@ export default angular.module('thingsboard.entity', []) |
48 | 53 | .directive('tbEntityFilterView', EntityFilterViewDirective) |
49 | 54 | .directive('tbAliasesEntitySelect', AliasesEntitySelectDirective) |
50 | 55 | .directive('tbAttributeTable', AttributeTableDirective) |
56 | + .directive('tbRelationFilters', RelationFiltersDirective) | |
51 | 57 | .directive('tbRelationTable', RelationTableDirective) |
52 | 58 | .directive('tbRelationTypeAutocomplete', RelationTypeAutocompleteDirective) |
53 | 59 | .name; | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +import './relation-filters.scss'; | |
18 | + | |
19 | +/* eslint-disable import/no-unresolved, import/default */ | |
20 | + | |
21 | +import relationFiltersTemplate from './relation-filters.tpl.html'; | |
22 | + | |
23 | +/* eslint-enable import/no-unresolved, import/default */ | |
24 | + | |
25 | +/*@ngInject*/ | |
26 | +export default function RelationFilters($compile, $templateCache) { | |
27 | + | |
28 | + return { | |
29 | + restrict: "E", | |
30 | + require: "^ngModel", | |
31 | + scope: { | |
32 | + allowedEntityTypes: '=?' | |
33 | + }, | |
34 | + link: linker | |
35 | + }; | |
36 | + | |
37 | + function linker( scope, element, attrs, ngModelCtrl ) { | |
38 | + | |
39 | + var template = $templateCache.get(relationFiltersTemplate); | |
40 | + element.html(template); | |
41 | + | |
42 | + scope.relationFilters = []; | |
43 | + | |
44 | + scope.addFilter = addFilter; | |
45 | + scope.removeFilter = removeFilter; | |
46 | + | |
47 | + ngModelCtrl.$render = function () { | |
48 | + if (ngModelCtrl.$viewValue) { | |
49 | + var value = ngModelCtrl.$viewValue; | |
50 | + value.forEach(function (filter) { | |
51 | + scope.relationFilters.push(filter); | |
52 | + }); | |
53 | + } | |
54 | + scope.$watch('relationFilters', function (newVal, prevVal) { | |
55 | + if (!angular.equals(newVal, prevVal)) { | |
56 | + updateValue(); | |
57 | + } | |
58 | + }, true); | |
59 | + } | |
60 | + | |
61 | + function addFilter() { | |
62 | + var filter = { | |
63 | + relationType: null, | |
64 | + entityTypes: [] | |
65 | + }; | |
66 | + scope.relationFilters.push(filter); | |
67 | + } | |
68 | + | |
69 | + function removeFilter($event, filter) { | |
70 | + var index = scope.relationFilters.indexOf(filter); | |
71 | + if (index > -1) { | |
72 | + scope.relationFilters.splice(index, 1); | |
73 | + } | |
74 | + } | |
75 | + | |
76 | + function updateValue() { | |
77 | + var value = []; | |
78 | + scope.relationFilters.forEach(function (filter) { | |
79 | + value.push(filter); | |
80 | + }); | |
81 | + ngModelCtrl.$setViewValue(value); | |
82 | + } | |
83 | + $compile(element.contents())(scope); | |
84 | + } | |
85 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +.tb-relation-filters { | |
18 | + .header { | |
19 | + padding-left: 5px; | |
20 | + padding-right: 5px; | |
21 | + padding-bottom: 5px; | |
22 | + .cell { | |
23 | + padding-left: 5px; | |
24 | + padding-right: 5px; | |
25 | + color: rgba(0,0,0,.54); | |
26 | + font-size: 12px; | |
27 | + font-weight: 700; | |
28 | + white-space: nowrap; | |
29 | + } | |
30 | + } | |
31 | + .body { | |
32 | + padding-left: 5px; | |
33 | + padding-right: 5px; | |
34 | + max-height: 300px; | |
35 | + overflow: auto; | |
36 | + padding-bottom: 20px; | |
37 | + .row { | |
38 | + padding-top: 5px; | |
39 | + } | |
40 | + .cell { | |
41 | + padding-left: 5px; | |
42 | + padding-right: 5px; | |
43 | + | |
44 | + md-select { | |
45 | + margin: 0 0 24px; | |
46 | + } | |
47 | + | |
48 | + md-input-container { | |
49 | + margin: 0; | |
50 | + } | |
51 | + | |
52 | + md-chips-wrap { | |
53 | + padding: 0px; | |
54 | + margin: 0 0 24px; | |
55 | + .md-chip-input-container { | |
56 | + margin: 0; | |
57 | + } | |
58 | + md-autocomplete { | |
59 | + height: 30px; | |
60 | + md-autocomplete-wrap { | |
61 | + height: 30px; | |
62 | + } | |
63 | + } | |
64 | + } | |
65 | + .md-chips .md-chip-input-container input { | |
66 | + padding: 2px 2px 2px; | |
67 | + height: 26px; | |
68 | + line-height: 26px; | |
69 | + } | |
70 | + | |
71 | + } | |
72 | + | |
73 | + .md-button { | |
74 | + margin: 0; | |
75 | + } | |
76 | + } | |
77 | +} | |
\ No newline at end of file | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2017 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<div class="tb-relation-filters"> | |
19 | + <div class="header" ng-show="relationFilters.length"> | |
20 | + <div layout="row" layout-align="start center"> | |
21 | + <span class="cell" style="width: 200px; min-width: 200px;" translate>relation.type</span> | |
22 | + <span class="cell" flex translate>entity.entity-types</span> | |
23 | + <span class="cell" style="width: 40px; min-width: 40px;"> </span> | |
24 | + </div> | |
25 | + </div> | |
26 | + <div class="body" ng-show="relationFilters.length"> | |
27 | + <div class="row" ng-form name="relationFilterForm" flex layout="row" layout-align="start center" ng-repeat="filter in relationFilters track by $index"> | |
28 | + <div class="md-whiteframe-1dp" flex layout="row" layout-align="start center"> | |
29 | + <tb-relation-type-autocomplete class="cell" style="width: 200px; min-width: 200px;" | |
30 | + hide-label | |
31 | + the-form="relationFilterForm" | |
32 | + ng-model="filter.relationType" | |
33 | + tb-required="false"> | |
34 | + </tb-relation-type-autocomplete> | |
35 | + <tb-entity-type-list class="cell" flex | |
36 | + ng-model="filter.entityTypes" | |
37 | + allowed-entity-types="allowedEntityTypes" | |
38 | + tb-required="false"> | |
39 | + </tb-entity-type-list> | |
40 | + <md-button ng-disabled="loading" class="md-icon-button md-primary" style="width: 40px; min-width: 40px;" | |
41 | + ng-click="removeFilter($event, filter)" aria-label="{{ 'action.remove' | translate }}"> | |
42 | + <md-tooltip md-direction="top"> | |
43 | + {{ 'relation.remove-relation-filter' | translate }} | |
44 | + </md-tooltip> | |
45 | + <md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons"> | |
46 | + close | |
47 | + </md-icon> | |
48 | + </md-button> | |
49 | + </div> | |
50 | + </div> | |
51 | + </div> | |
52 | + <div class="any-filter" ng-show="!relationFilters.length"> | |
53 | + <span layout-align="center center" | |
54 | + class="tb-prompt" translate>relation.any-relation</span> | |
55 | + </div> | |
56 | + <div> | |
57 | + <md-button ng-disabled="loading" class="md-primary md-raised" ng-click="addFilter($event)" aria-label="{{ 'action.add' | translate }}"> | |
58 | + <md-tooltip md-direction="top"> | |
59 | + {{ 'relation.add-relation-filter' | translate }} | |
60 | + </md-tooltip> | |
61 | + <md-icon aria-label="{{ 'action.add' | translate }}" class="material-icons"> | |
62 | + add | |
63 | + </md-icon> | |
64 | + {{ 'action.add' | translate }} | |
65 | + </md-button> | |
66 | + </div> | |
67 | +</div> | |
\ No newline at end of file | ... | ... |
... | ... | @@ -29,6 +29,8 @@ export default function RelationTypeAutocomplete($compile, $templateCache, $q, $ |
29 | 29 | element.html(template); |
30 | 30 | |
31 | 31 | scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false; |
32 | + scope.hideLabel = angular.isDefined(attrs.hideLabel) ? true : false; | |
33 | + | |
32 | 34 | scope.relationType = null; |
33 | 35 | scope.relationTypeSearchText = ''; |
34 | 36 | scope.relationTypes = []; | ... | ... |
... | ... | @@ -26,7 +26,7 @@ |
26 | 26 | md-items="item in fetchRelationTypes(relationTypeSearchText)" |
27 | 27 | md-item-text="item" |
28 | 28 | md-min-length="0" |
29 | - md-floating-label="{{ 'relation.relation-type' | translate }}" | |
29 | + md-floating-label="{{ tbRequired ? ('relation.relation-type' | translate) : ( !relationType ? ('relation.any-relation-type' | translate) : ' ') }}" | |
30 | 30 | md-select-on-match="true" |
31 | 31 | md-menu-class="tb-relation-type-autocomplete"> |
32 | 32 | <md-item-template> | ... | ... |
... | ... | @@ -16,7 +16,7 @@ |
16 | 16 | /* eslint-disable import/no-unresolved, import/default */ |
17 | 17 | |
18 | 18 | import importDialogTemplate from './import-dialog.tpl.html'; |
19 | -import entityAliasesTemplate from '../entity/entity-aliases.tpl.html'; | |
19 | +import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html'; | |
20 | 20 | |
21 | 21 | /* eslint-enable import/no-unresolved, import/default */ |
22 | 22 | ... | ... |
... | ... | @@ -129,14 +129,24 @@ export default angular.module('thingsboard.locale', []) |
129 | 129 | "filter-type-device-type-description": "Devices of type '{{deviceType}}'", |
130 | 130 | "filter-type-device-type-and-name-description": "Devices of type '{{deviceType}}' and with name starting with '{{prefix}}'", |
131 | 131 | "filter-type-relations-query": "Relations query", |
132 | + "filter-type-relations-query-description": "{{entities}} that have {{relationType}} relation {{direction}} {{rootEntity}}", | |
132 | 133 | "filter-type-asset-search-query": "Asset search query", |
134 | + "filter-type-asset-search-query-description": "Assets with types {{assetTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", | |
133 | 135 | "filter-type-device-search-query": "Device search query", |
136 | + "filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", | |
134 | 137 | "entity-filter": "Entity filter", |
135 | 138 | "resolve-multiple": "Resolve as multiple entities", |
136 | 139 | "filter-type": "Filter type", |
137 | 140 | "filter-type-required": "Filter type is required.", |
138 | 141 | "entity-filter-no-entity-matched": "No entities matching specified filter were found.", |
139 | - "no-entity-filter-specified": "No entity filter specified" | |
142 | + "no-entity-filter-specified": "No entity filter specified", | |
143 | + "root-state-entity": "Use dashboard state entity as root", | |
144 | + "root-entity": "Root entity", | |
145 | + "max-relation-level": "Max relation level", | |
146 | + "unlimited-level": "Unlimited level", | |
147 | + "state-entity": "Dashboard state entity", | |
148 | + "all-entities": "All entities", | |
149 | + "any-relation": "any" | |
140 | 150 | }, |
141 | 151 | "asset": { |
142 | 152 | "asset": "Asset", |
... | ... | @@ -159,6 +169,11 @@ export default angular.module('thingsboard.locale', []) |
159 | 169 | "asset-type": "Asset type", |
160 | 170 | "asset-type-required": "Asset type is required.", |
161 | 171 | "select-asset-type": "Select asset type", |
172 | + "enter-asset-type": "Enter asset type", | |
173 | + "any-asset": "Any asset", | |
174 | + "no-asset-types-matching": "No asset types matching '{{entitySubtype}}' were found.", | |
175 | + "asset-type-list-empty": "No asset types selected.", | |
176 | + "asset-types": "Asset types", | |
162 | 177 | "name": "Name", |
163 | 178 | "name-required": "Name is required.", |
164 | 179 | "description": "Description", |
... | ... | @@ -444,6 +459,7 @@ export default angular.module('thingsboard.locale', []) |
444 | 459 | }, |
445 | 460 | "datasource": { |
446 | 461 | "type": "Datasource type", |
462 | + "name": "Name", | |
447 | 463 | "add-datasource-prompt": "Please add datasource" |
448 | 464 | }, |
449 | 465 | "details": { |
... | ... | @@ -524,6 +540,11 @@ export default angular.module('thingsboard.locale', []) |
524 | 540 | "device-type": "Device type", |
525 | 541 | "device-type-required": "Device type is required.", |
526 | 542 | "select-device-type": "Select device type", |
543 | + "enter-device-type": "Enter device type", | |
544 | + "any-device": "Any device", | |
545 | + "no-device-types-matching": "No device types matching '{{entitySubtype}}' were found.", | |
546 | + "device-type-list-empty": "No device types selected.", | |
547 | + "device-types": "Device types", | |
527 | 548 | "name": "Name", |
528 | 549 | "name-required": "Name is required.", |
529 | 550 | "description": "Description", |
... | ... | @@ -564,10 +585,17 @@ export default angular.module('thingsboard.locale', []) |
564 | 585 | "remove-alias": "Remove entity alias", |
565 | 586 | "add-alias": "Add entity alias", |
566 | 587 | "entity-list": "Entity list", |
588 | + "entity-type": "Entity type", | |
589 | + "entity-types": "Entity types", | |
590 | + "entity-type-list": "Entity type list", | |
591 | + "any-entity": "Any entity", | |
592 | + "enter-entity-type": "Enter entity type", | |
567 | 593 | "no-entities-matching": "No entities matching '{{entity}}' were found.", |
594 | + "no-entity-types-matching": "No entity types matching '{{entityType}}' were found.", | |
568 | 595 | "name-starts-with": "Name starts with", |
569 | 596 | "use-entity-name-filter": "Use filter", |
570 | 597 | "entity-list-empty": "No entities selected.", |
598 | + "entity-type-list-empty": "No entity types selected.", | |
571 | 599 | "entity-name-filter-required": "Entity name filter is required.", |
572 | 600 | "entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.", |
573 | 601 | "all-subtypes": "All", |
... | ... | @@ -581,30 +609,39 @@ export default angular.module('thingsboard.locale', []) |
581 | 609 | "type": "Type", |
582 | 610 | "type-required": "Entity type is required.", |
583 | 611 | "type-device": "Device", |
612 | + "type-devices": "Devices", | |
584 | 613 | "list-of-devices": "{ count, select, 1 {One device} other {List of # devices} }", |
585 | 614 | "device-name-starts-with": "Devices whose names start with '{{prefix}}'", |
586 | 615 | "type-asset": "Asset", |
616 | + "type-assets": "Assets", | |
587 | 617 | "list-of-assets": "{ count, select, 1 {One asset} other {List of # assets} }", |
588 | 618 | "asset-name-starts-with": "Assets whose names start with '{{prefix}}'", |
589 | 619 | "type-rule": "Rule", |
620 | + "type-rules": "Rules", | |
590 | 621 | "list-of-rules": "{ count, select, 1 {One rule} other {List of # rules} }", |
591 | 622 | "rule-name-starts-with": "Rules whose names start with '{{prefix}}'", |
592 | 623 | "type-plugin": "Plugin", |
624 | + "type-plugins": "Plugins", | |
593 | 625 | "list-of-plugins": "{ count, select, 1 {One plugin} other {List of # plugins} }", |
594 | 626 | "plugin-name-starts-with": "Plugins whose names start with '{{prefix}}'", |
595 | 627 | "type-tenant": "Tenant", |
628 | + "type-tenants": "Tenants", | |
596 | 629 | "list-of-tenants": "{ count, select, 1 {One tenant} other {List of # tenants} }", |
597 | 630 | "tenant-name-starts-with": "Tenants whose names start with '{{prefix}}'", |
598 | 631 | "type-customer": "Customer", |
632 | + "type-customers": "Customers", | |
599 | 633 | "list-of-customers": "{ count, select, 1 {One customer} other {List of # customers} }", |
600 | 634 | "customer-name-starts-with": "Customers whose names start with '{{prefix}}'", |
601 | 635 | "type-user": "User", |
636 | + "type-users": "Users", | |
602 | 637 | "list-of-users": "{ count, select, 1 {One user} other {List of # users} }", |
603 | 638 | "user-name-starts-with": "Users whose names start with '{{prefix}}'", |
604 | 639 | "type-dashboard": "Dashboard", |
640 | + "type-dashboards": "Dashboards", | |
605 | 641 | "list-of-dashboards": "{ count, select, 1 {One dashboard} other {List of # dashboards} }", |
606 | 642 | "dashboard-name-starts-with": "Dashboards whose names start with '{{prefix}}'", |
607 | 643 | "type-alarm": "Alarm", |
644 | + "type-alarms": "Alarms", | |
608 | 645 | "list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }", |
609 | 646 | "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'" |
610 | 647 | }, |
... | ... | @@ -770,6 +807,10 @@ export default angular.module('thingsboard.locale', []) |
770 | 807 | "FROM": "From", |
771 | 808 | "TO": "To" |
772 | 809 | }, |
810 | + "direction-type": { | |
811 | + "FROM": "from", | |
812 | + "TO": "to" | |
813 | + }, | |
773 | 814 | "from-relations": "Outbound relations", |
774 | 815 | "to-relations": "Inbound relations", |
775 | 816 | "selected-relations": "{ count, select, 1 {1 relation} other {# relations} } selected", |
... | ... | @@ -783,6 +824,7 @@ export default angular.module('thingsboard.locale', []) |
783 | 824 | "delete": "Delete relation", |
784 | 825 | "relation-type": "Relation type", |
785 | 826 | "relation-type-required": "Relation type is required.", |
827 | + "any-relation-type": "Any type", | |
786 | 828 | "add": "Add relation", |
787 | 829 | "delete-to-relation-title": "Are you sure you want to delete relation to the entity '{{entityName}}'?", |
788 | 830 | "delete-to-relation-text": "Be careful, after the confirmation the entity '{{entityName}}' will be unrelated from the current entity.", |
... | ... | @@ -791,7 +833,11 @@ export default angular.module('thingsboard.locale', []) |
791 | 833 | "delete-from-relation-title": "Are you sure you want to delete relation from the entity '{{entityName}}'?", |
792 | 834 | "delete-from-relation-text": "Be careful, after the confirmation current entity will be unrelated from the entity '{{entityName}}'.", |
793 | 835 | "delete-from-relations-title": "Are you sure you want to delete { count, select, 1 {1 relation} other {# relations} }?", |
794 | - "delete-from-relations-text": "Be careful, after the confirmation all selected relations will be removed and current entity will be unrelated from the corresponding entities." | |
836 | + "delete-from-relations-text": "Be careful, after the confirmation all selected relations will be removed and current entity will be unrelated from the corresponding entities.", | |
837 | + "remove-relation-filter": "Remove relation filter", | |
838 | + "add-relation-filter": "Add relation filter", | |
839 | + "any-relation": "Any relation", | |
840 | + "relation-filters": "Relation filters" | |
795 | 841 | }, |
796 | 842 | "rule": { |
797 | 843 | "rule": "Rule", | ... | ... |
... | ... | @@ -236,6 +236,15 @@ div { |
236 | 236 | } |
237 | 237 | } |
238 | 238 | |
239 | +.md-caption { | |
240 | + &.tb-required:after { | |
241 | + content: ' *'; | |
242 | + font-size: 10px; | |
243 | + vertical-align: top; | |
244 | + color: rgba(0,0,0,0.54); | |
245 | + } | |
246 | +} | |
247 | + | |
239 | 248 | pre.tb-highlight { |
240 | 249 | background-color: #f7f7f7; |
241 | 250 | display: block; | ... | ... |