thingsboard-developers

Changes

Details

diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
index 3ddc597..4aa1a0a 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
@@ -250,6 +250,21 @@ public class EntityRelationController extends BaseController {
         }
     }
 
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
+    @RequestMapping(value = "/relations/info", method = RequestMethod.POST)
+    @ResponseBody
+    public List<EntityRelationInfo> findInfoByQuery(@RequestBody EntityRelationsQuery query) throws ThingsboardException {
+        checkNotNull(query);
+        checkNotNull(query.getParameters());
+        checkNotNull(query.getFilters());
+        checkEntityId(query.getParameters().getEntityId());
+        try {
+            return checkNotNull(relationService.findInfoByQuery(query).get());
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
     private RelationTypeGroup parseRelationTypeGroup(String strRelationTypeGroup, RelationTypeGroup defaultValue) {
         RelationTypeGroup result = defaultValue;
         if (strRelationTypeGroup != null && strRelationTypeGroup.trim().length()>0) {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
index 36ec567..296874e 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
@@ -191,7 +191,7 @@ public class BaseRelationService implements RelationService {
 
     @Override
     public ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query) {
-        log.trace("Executing findByQuery [{}][{}]", query);
+        log.trace("Executing findByQuery [{}]", query);
         RelationsSearchParameters params = query.getParameters();
         final List<EntityTypeFilter> filters = query.getFilters();
         if (filters == null || filters.isEmpty()) {
@@ -224,6 +224,30 @@ public class BaseRelationService implements RelationService {
         }
     }
 
+    @Override
+    public ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(EntityRelationsQuery query) {
+        log.trace("Executing findInfoByQuery [{}]", query);
+        ListenableFuture<List<EntityRelation>> relations = findByQuery(query);
+        EntitySearchDirection direction = query.getParameters().getDirection();
+        ListenableFuture<List<EntityRelationInfo>> relationsInfo = Futures.transform(relations,
+                (AsyncFunction<List<EntityRelation>, List<EntityRelationInfo>>) relations1 -> {
+                    List<ListenableFuture<EntityRelationInfo>> futures = new ArrayList<>();
+                    relations1.stream().forEach(relation ->
+                            futures.add(fetchRelationInfoAsync(relation,
+                                    relation2 -> direction == EntitySearchDirection.FROM ? relation2.getTo() : relation2.getFrom(),
+                                    (EntityRelationInfo relationInfo, String entityName) -> {
+                                        if (direction == EntitySearchDirection.FROM) {
+                                            relationInfo.setToName(entityName);
+                                        } else {
+                                            relationInfo.setFromName(entityName);
+                                        }
+                                    }))
+                    );
+                    return Futures.successfulAsList(futures);
+                });
+        return relationsInfo;
+    }
+
     protected void validate(EntityRelation relation) {
         if (relation == null) {
             throw new DataValidationException("Relation type should be specified!");
diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java
index a810454..bd2e785 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java
@@ -52,6 +52,8 @@ public interface RelationService {
 
     ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query);
 
+    ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(EntityRelationsQuery query);
+
 //    TODO: This method may be useful for some validations in the future
 //    ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to);
 
diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js
index e647d9c..20157ce 100644
--- a/ui/src/app/api/entity.service.js
+++ b/ui/src/app/api/entity.service.js
@@ -32,6 +32,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
         checkEntityAlias: checkEntityAlias,
         filterAliasByEntityTypes: filterAliasByEntityTypes,
         getAliasFilterTypesByEntityTypes: getAliasFilterTypesByEntityTypes,
+        prepareAllowedEntityTypesList: prepareAllowedEntityTypesList,
         getEntityKeys: getEntityKeys,
         createDatasourcesFromSubscriptionsInfo: createDatasourcesFromSubscriptionsInfo,
         getRelatedEntities: getRelatedEntities,
@@ -176,6 +177,54 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
         return deferred.promise;
     }
 
+    function getSingleTenantByPageLinkPromise(pageLink) {
+        var user = userService.getCurrentUser();
+        var tenantId = user.tenantId;
+        var deferred = $q.defer();
+        tenantService.getTenant(tenantId).then(
+            function success(tenant) {
+                var tenantName = tenant.name;
+                var result = {
+                    data: [],
+                    nextPageLink: pageLink,
+                    hasNext: false
+                };
+                if (tenantName.toLowerCase().startsWith(pageLink.textSearch)) {
+                    result.data.push(tenant);
+                }
+                deferred.resolve(result);
+            },
+            function fail() {
+                deferred.reject();
+            }
+        );
+        return deferred.promise;
+    }
+
+    function getSingleCustomerByPageLinkPromise(pageLink) {
+        var user = userService.getCurrentUser();
+        var customerId = user.customerId;
+        var deferred = $q.defer();
+        customerService.getCustomer(customerId).then(
+            function success(customer) {
+                var customerName = customer.name;
+                var result = {
+                    data: [],
+                    nextPageLink: pageLink,
+                    hasNext: false
+                };
+                if (customerName.toLowerCase().startsWith(pageLink.textSearch)) {
+                    result.data.push(customer);
+                }
+                deferred.resolve(result);
+            },
+            function fail() {
+                deferred.reject();
+            }
+        );
+        return deferred.promise;
+    }
+
     function getEntitiesByPageLinkPromise(entityType, pageLink, config, subType) {
         var promise;
         var user = userService.getCurrentUser();
@@ -196,10 +245,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                 }
                 break;
             case types.entityType.tenant:
-                promise = tenantService.getTenants(pageLink);
+                if (user.authority === 'TENANT_ADMIN') {
+                    promise = getSingleTenantByPageLinkPromise(pageLink);
+                } else {
+                    promise = tenantService.getTenants(pageLink);
+                }
                 break;
             case types.entityType.customer:
-                promise = customerService.getCustomers(pageLink);
+                if (user.authority === 'CUSTOMER_USER') {
+                    promise = getSingleCustomerByPageLinkPromise(pageLink);
+                } else {
+                    promise = customerService.getCustomers(pageLink);
+                }
                 break;
             case types.entityType.rule:
                 promise = ruleService.getAllRules(pageLink);
@@ -283,6 +340,16 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
         return { name: entity.name, entityType: entity.id.entityType, id: entity.id.id };
     }
 
+    function entityRelationInfoToEntityInfo(entityRelationInfo, direction) {
+        var entityId = direction == types.entitySearchDirection.from ? entityRelationInfo.to : entityRelationInfo.from;
+        var name = direction == types.entitySearchDirection.from ? entityRelationInfo.toName : entityRelationInfo.fromName;
+        return {
+            name: name,
+            entityType: entityId.entityType,
+            id: entityId.id
+        };
+    }
+
     function entitiesToEntitiesInfo(entities) {
         var entitiesInfo = [];
         for (var d = 0; d < entities.length; d++) {
@@ -291,19 +358,26 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
         return entitiesInfo;
     }
 
+    function entityRelationInfosToEntitiesInfo(entityRelations, direction) {
+        var entitiesInfo = [];
+        for (var d = 0; d < entityRelations.length; d++) {
+            entitiesInfo.push(entityRelationInfoToEntityInfo(entityRelations[d], direction));
+        }
+        return entitiesInfo;
+    }
+
+
     function resolveAlias(entityAlias, stateParams) {
         var deferred = $q.defer();
         var filter = entityAlias.filter;
         resolveAliasFilter(filter, stateParams, -1).then(
             function (result) {
-                var entities = result.entities;
                 var aliasInfo = {
                     alias: entityAlias.alias,
                     stateEntity: result.stateEntity,
                     resolveMultiple: filter.resolveMultiple
                 };
-                var resolvedEntities = entitiesToEntitiesInfo(entities);
-                aliasInfo.resolvedEntities = resolvedEntities;
+                aliasInfo.resolvedEntities = result.entities;
                 aliasInfo.currentEntity = null;
                 if (aliasInfo.resolvedEntities.length) {
                     aliasInfo.currentEntity = aliasInfo.resolvedEntities[0];
@@ -328,7 +402,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                 getEntities(filter.entityType, filter.entityList).then(
                     function success(entities) {
                         if (entities && entities.length) {
-                            result.entities = entities;
+                            result.entities = entitiesToEntitiesInfo(entities);
                             deferred.resolve(result);
                         } else {
                             deferred.reject();
@@ -343,7 +417,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                 getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems).then(
                     function success(entities) {
                         if (entities && entities.length) {
-                            result.entities = entities;
+                            result.entities = entitiesToEntitiesInfo(entities);
                             deferred.resolve(result);
                         } else {
                             deferred.reject();
@@ -359,7 +433,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                 if (stateParams && stateParams.entityId) {
                     getEntity(stateParams.entityId.entityType, stateParams.entityId.id).then(
                         function success(entity) {
-                            result.entities = [entity];
+                            result.entities = entitiesToEntitiesInfo([entity]);
                             deferred.resolve(result);
                         },
                         function fail() {
@@ -374,7 +448,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                 getEntitiesByNameFilter(types.entityType.asset, filter.assetNameFilter, maxItems, null, filter.assetType).then(
                     function success(entities) {
                         if (entities && entities.length) {
-                            result.entities = entities;
+                            result.entities = entitiesToEntitiesInfo(entities);
                             deferred.resolve(result);
                         } else {
                             deferred.reject();
@@ -389,7 +463,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                 getEntitiesByNameFilter(types.entityType.device, filter.deviceNameFilter, maxItems, null, filter.deviceType).then(
                     function success(entities) {
                         if (entities && entities.length) {
-                            result.entities = entities;
+                            result.entities = entitiesToEntitiesInfo(entities);
                             deferred.resolve(result);
                         } else {
                             deferred.reject();
@@ -400,8 +474,97 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                     }
                 );
                 break;
-
-            //TODO: Alias filter
+            case types.aliasFilterType.relationsQuery.value:
+                result.stateEntity = filter.rootStateEntity;
+                var rootEntityType;
+                var rootEntityId;
+                if (result.stateEntity && stateParams && stateParams.entityId) {
+                    rootEntityType = stateParams.entityId.entityType;
+                    rootEntityId = stateParams.entityId.id;
+                } else if (!result.stateEntity) {
+                    rootEntityType = filter.rootEntity.entityType;
+                    rootEntityId = filter.rootEntity.id;
+                }
+                if (rootEntityType && rootEntityId) {
+                    var searchQuery = {
+                        parameters: {
+                            rootId: rootEntityId,
+                            rootType: rootEntityType,
+                            direction: filter.direction
+                        },
+                        filters: filter.filters
+                    };
+                    searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1;
+                    entityRelationService.findInfoByQuery(searchQuery).then(
+                        function success(allRelations) {
+                            if (allRelations && allRelations.length) {
+                                if (angular.isDefined(maxItems) && maxItems > 0) {
+                                    var limit = Math.min(allRelations.length, maxItems);
+                                    allRelations.length = limit;
+                                }
+                                result.entities = entityRelationInfosToEntitiesInfo(allRelations, filter.direction);
+                                deferred.resolve(result);
+                            } else {
+                                deferred.reject();
+                            }
+                        },
+                        function fail() {
+                            deferred.reject();
+                        }
+                    );
+                } else {
+                    deferred.resolve(result);
+                }
+                break;
+            case types.aliasFilterType.assetSearchQuery.value:
+            case types.aliasFilterType.deviceSearchQuery.value:
+                result.stateEntity = filter.rootStateEntity;
+                if (result.stateEntity && stateParams && stateParams.entityId) {
+                    rootEntityType = stateParams.entityId.entityType;
+                    rootEntityId = stateParams.entityId.id;
+                } else if (!result.stateEntity) {
+                    rootEntityType = filter.rootEntity.entityType;
+                    rootEntityId = filter.rootEntity.id;
+                }
+                if (rootEntityType && rootEntityId) {
+                    searchQuery = {
+                        parameters: {
+                            rootId: rootEntityId,
+                            rootType: rootEntityType,
+                            direction: filter.direction
+                        },
+                        relationType: filter.relationType
+                    };
+                    searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1;
+                    var findByQueryPromise;
+                    if (filter.type == types.aliasFilterType.assetSearchQuery.value) {
+                        searchQuery.assetTypes = filter.assetTypes;
+                        findByQueryPromise = assetService.findByQuery(searchQuery, false);
+                    } else if (filter.type == types.aliasFilterType.deviceSearchQuery.value) {
+                        searchQuery.deviceTypes = filter.deviceTypes;
+                        findByQueryPromise = deviceService.findByQuery(searchQuery, false);
+                    }
+                    findByQueryPromise.then(
+                        function success(entities) {
+                            if (entities && entities.length) {
+                                if (angular.isDefined(maxItems) && maxItems > 0) {
+                                    var limit = Math.min(entities.length, maxItems);
+                                    entities.length = limit;
+                                }
+                                result.entities = entitiesToEntitiesInfo(entities);
+                                deferred.resolve(result);
+                            } else {
+                                deferred.reject();
+                            }
+                        },
+                        function fail() {
+                            deferred.reject();
+                        }
+                    );
+                } else {
+                    deferred.resolve(result);
+                }
+                break;
         }
         return deferred.promise;
     }
@@ -420,9 +583,33 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                     return entityTypes.indexOf(types.entityType.asset)  > -1 ? true : false;
                 case types.aliasFilterType.deviceType.value:
                     return entityTypes.indexOf(types.entityType.device)  > -1 ? true : false;
+                case types.aliasFilterType.relationsQuery.value:
+                    if (filter.filters && filter.filters.length) {
+                        var match = false;
+                        for (var f=0;f<filter.filters.length;f++) {
+                            var relationFilter = filter.filters[f];
+                            if (relationFilter.entityTypes && relationFilter.entityTypes.length) {
+                                for (var et=0;et<relationFilter.entityTypes.length;et++) {
+                                    if (entityTypes.indexOf(relationFilter.entityTypes[et]) > -1) {
+                                        match = true;
+                                        break;
+                                    }
+                                }
+                            } else {
+                                match = true;
+                                break;
+                            }
+                        }
+                        return match;
+                    } else {
+                        return true;
+                    }
+                case types.aliasFilterType.assetSearchQuery.value:
+                    return entityTypes.indexOf(types.entityType.asset)  > -1 ? true : false;
+                case types.aliasFilterType.deviceSearchQuery.value:
+                    return entityTypes.indexOf(types.entityType.device)  > -1 ? true : false;
             }
         }
-        //TODO: Alias filter
         return false;
     }
 
@@ -474,6 +661,42 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
         return result;
     }
 
+    function prepareAllowedEntityTypesList(allowedEntityTypes) {
+        var authority = userService.getAuthority();
+        var entityTypes = {};
+        switch(authority) {
+            case 'SYS_ADMIN':
+                entityTypes.tenant = types.entityType.tenant;
+                entityTypes.rule = types.entityType.rule;
+                entityTypes.plugin = types.entityType.plugin;
+                break;
+            case 'TENANT_ADMIN':
+                entityTypes.device = types.entityType.device;
+                entityTypes.asset = types.entityType.asset;
+                entityTypes.tenant = types.entityType.tenant;
+                entityTypes.customer = types.entityType.customer;
+                entityTypes.rule = types.entityType.rule;
+                entityTypes.plugin = types.entityType.plugin;
+                entityTypes.dashboard = types.entityType.dashboard;
+                break;
+            case 'CUSTOMER_USER':
+                entityTypes.device = types.entityType.device;
+                entityTypes.asset = types.entityType.asset;
+                entityTypes.customer = types.entityType.customer;
+                entityTypes.dashboard = types.entityType.dashboard;
+                break;
+        }
+
+        if (allowedEntityTypes) {
+            for (var entityType in entityTypes) {
+                if (allowedEntityTypes.indexOf(entityTypes[entityType]) === -1) {
+                    delete entityTypes[entityType];
+                }
+            }
+        }
+        return entityTypes;
+    }
+
 
     function checkEntityAlias(entityAlias) {
         var deferred = $q.defer();
diff --git a/ui/src/app/api/entity-relation.service.js b/ui/src/app/api/entity-relation.service.js
index 875b2fa..351c252 100644
--- a/ui/src/app/api/entity-relation.service.js
+++ b/ui/src/app/api/entity-relation.service.js
@@ -30,7 +30,8 @@ function EntityRelationService($http, $q) {
         findByTo: findByTo,
         findInfoByTo: findInfoByTo,
         findByToAndType: findByToAndType,
-        findByQuery: findByQuery
+        findByQuery: findByQuery,
+        findInfoByQuery: findInfoByQuery
     }
 
     return service;
@@ -159,4 +160,15 @@ function EntityRelationService($http, $q) {
         return deferred.promise;
     }
 
+    function findInfoByQuery(query) {
+        var deferred = $q.defer();
+        var url = '/api/relations/info';
+        $http.post(url, query).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail() {
+            deferred.reject();
+        });
+        return deferred.promise;
+    }
+
 }
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index 522abff..fe3f73c 100644
--- a/ui/src/app/common/types.constant.js
+++ b/ui/src/app/common/types.constant.js
@@ -146,46 +146,55 @@ export default angular.module('thingsboard.types', [])
             entityTypeTranslations: {
                 "DEVICE": {
                     type: 'entity.type-device',
+                    typePlural: 'entity.type-devices',
                     list: 'entity.list-of-devices',
                     nameStartsWith: 'entity.device-name-starts-with'
                 },
                 "ASSET": {
                     type: 'entity.type-asset',
+                    typePlural: 'entity.type-assets',
                     list: 'entity.list-of-assets',
                     nameStartsWith: 'entity.asset-name-starts-with'
                 },
                 "RULE": {
                     type: 'entity.type-rule',
+                    typePlural: 'entity.type-rules',
                     list: 'entity.list-of-rules',
                     nameStartsWith: 'entity.rule-name-starts-with'
                 },
                 "PLUGIN": {
                     type: 'entity.type-plugin',
+                    typePlural: 'entity.type-plugins',
                     list: 'entity.list-of-plugins',
                     nameStartsWith: 'entity.plugin-name-starts-with'
                 },
                 "TENANT": {
                     type: 'entity.type-tenant',
+                    typePlural: 'entity.type-tenants',
                     list: 'entity.list-of-tenants',
                     nameStartsWith: 'entity.tenant-name-starts-with'
                 },
                 "CUSTOMER": {
                     type: 'entity.type-customer',
+                    typePlural: 'entity.type-customers',
                     list: 'entity.list-of-customers',
                     nameStartsWith: 'entity.customer-name-starts-with'
                 },
                 "USER": {
                     type: 'entity.type-user',
+                    typePlural: 'entity.type-users',
                     list: 'entity.list-of-users',
                     nameStartsWith: 'entity.user-name-starts-with'
                 },
                 "DASHBOARD": {
                     type: 'entity.type-dashboard',
+                    typePlural: 'entity.type-dashboards',
                     list: 'entity.list-of-dashboards',
                     nameStartsWith: 'entity.dashboard-name-starts-with'
                 },
                 "ALARM": {
                     type: 'entity.type-alarm',
+                    typePlural: 'entity.type-alarms',
                     list: 'entity.list-of-alarms',
                     nameStartsWith: 'entity.alarm-name-starts-with'
                 }
diff --git a/ui/src/app/components/datasource-func.directive.js b/ui/src/app/components/datasource-func.directive.js
index 0f66dca..fe54696 100644
--- a/ui/src/app/components/datasource-func.directive.js
+++ b/ui/src/app/components/datasource-func.directive.js
@@ -71,6 +71,13 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
             }
         }, true);
 
+        scope.$watch('datasourceName', function () {
+            if (ngModelCtrl.$viewValue) {
+                ngModelCtrl.$viewValue.name = scope.datasourceName;
+                scope.updateValidity();
+            }
+        });
+
         ngModelCtrl.$render = function () {
             if (ngModelCtrl.$viewValue) {
                 var funcDataKeys = [];
@@ -78,6 +85,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
                     funcDataKeys = funcDataKeys.concat(ngModelCtrl.$viewValue.dataKeys);
                 }
                 scope.funcDataKeys = funcDataKeys;
+                scope.datasourceName = ngModelCtrl.$viewValue.name;
             }
         };
 
diff --git a/ui/src/app/components/datasource-func.scss b/ui/src/app/components/datasource-func.scss
index 1a5dcc7..8739ed8 100644
--- a/ui/src/app/components/datasource-func.scss
+++ b/ui/src/app/components/datasource-func.scss
@@ -15,23 +15,29 @@
  */
 @import '../../scss/constants';
 
-.tb-func-datakey-autocomplete {
-  .tb-not-found {
-    display: block;
-    line-height: 1.5;
-    height: 48px;
-    .tb-no-entries {
-      line-height: 48px;
-    }
+.tb-datasource-func {
+  @media (min-width: $layout-breakpoint-gt-sm) {
+    padding-left: 8px;
   }
-  li {
-    height: auto !important;
-    white-space: normal !important;
+
+  md-input-container.tb-datasource-name {
+    .md-errors-spacer {
+        display: none;
+    }
   }
-}
 
-tb-datasource-func {
-  @media (min-width: $layout-breakpoint-gt-sm) {
-    padding-left: 8px;
+  .tb-func-datakey-autocomplete {
+    .tb-not-found {
+      display: block;
+      line-height: 1.5;
+      height: 48px;
+      .tb-no-entries {
+        line-height: 48px;
+      }
+    }
+    li {
+      height: auto !important;
+      white-space: normal !important;
+    }
   }
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/components/datasource-func.tpl.html b/ui/src/app/components/datasource-func.tpl.html
index 1853b9d..e8e08f7 100644
--- a/ui/src/app/components/datasource-func.tpl.html
+++ b/ui/src/app/components/datasource-func.tpl.html
@@ -15,59 +15,68 @@
     limitations under the License.
 
 -->
-<section flex layout='column' style="padding-left: 4px;">
-   <md-chips flex
-			     id="function_datakey_chips"
-	   			 ng-required="true"
-	             ng-model="funcDataKeys" md-autocomplete-snap
-	             md-transform-chip="transformDataKeyChip($chip)"
-	             md-require-match="false">    
-		      <md-autocomplete 
-		    		md-no-cache="false"
-		    		id="dataKey"
-					md-selected-item="selectedDataKey"
-					md-search-text="dataKeySearchText"
-					md-items="item in dataKeysSearch(dataKeySearchText)"
-					md-item-text="item.name"
-					md-min-length="0"
-		            placeholder="{{ 'datakey.function-types' | translate }}"
-					md-menu-class="tb-func-datakey-autocomplete">
-		            <span md-highlight-text="dataKeySearchText" md-highlight-flags="^i">{{item}}</span>
-				    <md-not-found>
-						<div class="tb-not-found">
-							<div class="tb-no-entries" ng-if="!textIsNotEmpty(dataKeySearchText)">
-								<span translate>device.no-keys-found</span>
+<section class="tb-datasource-func" flex layout='column'
+		 layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
+	<md-input-container class="tb-datasource-name" md-no-float style="min-width: 200px;">
+		<input name="datasourceName"
+			   placeholder="{{ 'datasource.name' | translate }}"
+			   ng-model="datasourceName"
+			   aria-label="{{ 'datasource.name' | translate }}">
+	</md-input-container>
+    <section flex layout='column' style="padding-left: 4px;">
+	   <md-chips flex
+					 id="function_datakey_chips"
+					 ng-required="true"
+					 ng-model="funcDataKeys" md-autocomplete-snap
+					 md-transform-chip="transformDataKeyChip($chip)"
+					 md-require-match="false">
+				  <md-autocomplete
+						md-no-cache="false"
+						id="dataKey"
+						md-selected-item="selectedDataKey"
+						md-search-text="dataKeySearchText"
+						md-items="item in dataKeysSearch(dataKeySearchText)"
+						md-item-text="item.name"
+						md-min-length="0"
+						placeholder="{{ 'datakey.function-types' | translate }}"
+						md-menu-class="tb-func-datakey-autocomplete">
+						<span md-highlight-text="dataKeySearchText" md-highlight-flags="^i">{{item}}</span>
+						<md-not-found>
+							<div class="tb-not-found">
+								<div class="tb-no-entries" ng-if="!textIsNotEmpty(dataKeySearchText)">
+									<span translate>device.no-keys-found</span>
+								</div>
+								<div ng-if="textIsNotEmpty(dataKeySearchText)">
+									<span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>device.no-key-matching</span>
+									<span>
+										<a translate ng-click="createKey($event, '#function_datakey_chips')">device.create-new-key</a>
+									</span>
+								</div>
 							</div>
-							<div ng-if="textIsNotEmpty(dataKeySearchText)">
-								<span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>device.no-key-matching</span>
-								<span>
-		          					<a translate ng-click="createKey($event, '#function_datakey_chips')">device.create-new-key</a>
-						  		</span>
-							</div>
-						</div>
-				    </md-not-found>
-			  </md-autocomplete>
-		      <md-chip-template>
-				  <div layout="row" layout-align="start center" class="tb-attribute-chip">
-					  <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
-						  <div class="tb-color-result" ng-style="{background: $chip.color}"></div>
-					  </div>
-					  <div layout="row" flex>
-						  <div class="tb-chip-label">
-							  {{$chip.label}}
+						</md-not-found>
+				  </md-autocomplete>
+				  <md-chip-template>
+					  <div layout="row" layout-align="start center" class="tb-attribute-chip">
+						  <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
+							  <div class="tb-color-result" ng-style="{background: $chip.color}"></div>
 						  </div>
-						  <div class="tb-chip-separator">: </div>
-						  <div class="tb-chip-label">
-							  <strong>{{$chip.name}}</strong>
+						  <div layout="row" flex>
+							  <div class="tb-chip-label">
+								  {{$chip.label}}
+							  </div>
+							  <div class="tb-chip-separator">: </div>
+							  <div class="tb-chip-label">
+								  <strong>{{$chip.name}}</strong>
+							  </div>
 						  </div>
+						  <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
+							  <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
+						  </md-button>
 					  </div>
-					  <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
-						  <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
-					  </md-button>
-				  </div>
-		      </md-chip-template>
-	</md-chips>
-	<div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
-		<div translate ng-message="funcTypes" class="tb-error-message">datakey.function-types-required</div>
-	</div>
+				  </md-chip-template>
+		</md-chips>
+		<div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
+			<div translate ng-message="funcTypes" class="tb-error-message">datakey.function-types-required</div>
+		</div>
+   </section>
 </section>
diff --git a/ui/src/app/dashboard/add-widget.controller.js b/ui/src/app/dashboard/add-widget.controller.js
index a9a68c7..5372b62 100644
--- a/ui/src/app/dashboard/add-widget.controller.js
+++ b/ui/src/app/dashboard/add-widget.controller.js
@@ -15,7 +15,7 @@
  */
 /* eslint-disable import/no-unresolved, import/default */
 
-import entityAliasDialogTemplate from '../entity/entity-alias-dialog.tpl.html';
+import entityAliasDialogTemplate from '../entity/alias/entity-alias-dialog.tpl.html';
 
 /* eslint-enable import/no-unresolved, import/default */
 
diff --git a/ui/src/app/dashboard/dashboard.controller.js b/ui/src/app/dashboard/dashboard.controller.js
index 7c97a94..65eb833 100644
--- a/ui/src/app/dashboard/dashboard.controller.js
+++ b/ui/src/app/dashboard/dashboard.controller.js
@@ -15,7 +15,7 @@
  */
 /* eslint-disable import/no-unresolved, import/default */
 
-import entityAliasesTemplate from '../entity/entity-aliases.tpl.html';
+import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html';
 import dashboardSettingsTemplate from './dashboard-settings.tpl.html';
 import manageDashboardLayoutsTemplate from './layouts/manage-dashboard-layouts.tpl.html';
 import manageDashboardStatesTemplate from './states/manage-dashboard-states.tpl.html';
diff --git a/ui/src/app/dashboard/edit-widget.directive.js b/ui/src/app/dashboard/edit-widget.directive.js
index 5b788cc..6532914 100644
--- a/ui/src/app/dashboard/edit-widget.directive.js
+++ b/ui/src/app/dashboard/edit-widget.directive.js
@@ -15,7 +15,7 @@
  */
 /* eslint-disable import/no-unresolved, import/default */
 
-import entityAliasDialogTemplate from '../entity/entity-alias-dialog.tpl.html';
+import entityAliasDialogTemplate from '../entity/alias/entity-alias-dialog.tpl.html';
 import editWidgetTemplate from './edit-widget.tpl.html';
 
 /* eslint-enable import/no-unresolved, import/default */
diff --git a/ui/src/app/entity/entity-filter.directive.js b/ui/src/app/entity/entity-filter.directive.js
index 9566716..b81f369 100644
--- a/ui/src/app/entity/entity-filter.directive.js
+++ b/ui/src/app/entity/entity-filter.directive.js
@@ -63,7 +63,23 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
                     filter.deviceType = null;
                     filter.deviceNameFilter = '';
                     break;
-                //TODO: Alias filter
+                case types.aliasFilterType.relationsQuery.value:
+                case types.aliasFilterType.assetSearchQuery.value:
+                case types.aliasFilterType.deviceSearchQuery.value:
+                    filter.rootStateEntity = false;
+                    filter.rootEntity = null;
+                    filter.direction = types.entitySearchDirection.from;
+                    filter.maxLevel = 1;
+                    if (filter.type === types.aliasFilterType.relationsQuery.value) {
+                        filter.filters = [];
+                    } else if (filter.type === types.aliasFilterType.assetSearchQuery.value) {
+                        filter.relationType = null;
+                        filter.assetTypes = [];
+                    } else if (filter.type === types.aliasFilterType.deviceSearchQuery.value) {
+                        filter.relationType = null;
+                        filter.deviceTypes = [];
+                    }
+                    break;
             }
             scope.filter = filter;
         }
diff --git a/ui/src/app/entity/entity-filter.scss b/ui/src/app/entity/entity-filter.scss
index ebbea3d..7e998ca 100644
--- a/ui/src/app/entity/entity-filter.scss
+++ b/ui/src/app/entity/entity-filter.scss
@@ -16,4 +16,21 @@
 
 .tb-entity-filter {
 
+  #relationsQueryFilter {
+    padding-top: 20px;
+    tb-entity-select {
+      min-height: 92px;
+    }
+  }
+
+  .tb-root-state-entity-switch {
+    padding-left: 10px;
+    .root-state-entity-switch {
+      margin: 0;
+    }
+    .root-state-entity-label {
+      margin: 5px 0;
+    }
+  }
+
 }
\ No newline at end of file
diff --git a/ui/src/app/entity/entity-filter.tpl.html b/ui/src/app/entity/entity-filter.tpl.html
index 77b6d3c..4f5af00 100644
--- a/ui/src/app/entity/entity-filter.tpl.html
+++ b/ui/src/app/entity/entity-filter.tpl.html
@@ -88,4 +88,146 @@
                    aria-label="{{ 'device.name-starts-with' | translate }}">
         </md-input-container>
     </section>
+    <section layout="column" ng-if="filter.type == types.aliasFilterType.relationsQuery.value" id="relationsQueryFilter">
+        <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
+        <div flex layout="row">
+            <tb-entity-select flex
+                              the-form="theForm"
+                              tb-required="!filter.rootStateEntity"
+                              ng-disabled="filter.rootStateEntity"
+                              ng-model="filter.rootEntity">
+            </tb-entity-select>
+            <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
+                <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
+                <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
+                           aria-label="{{ 'alias.root-state-entity' | translate }}">
+                </md-switch>
+            </section>
+        </div>
+        <div flex layout="row">
+            <md-input-container class="md-block" style="min-width: 100px;">
+                <label translate>relation.direction</label>
+                <md-select required ng-model="filter.direction">
+                    <md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction">
+                        {{ ('relation.search-direction.' + direction) | translate}}
+                    </md-option>
+                </md-select>
+            </md-input-container>
+            <md-input-container flex class="md-block">
+                <label translate>alias.max-relation-level</label>
+                <input name="maxRelationLevel"
+                       type="number"
+                       min="1"
+                       step="1"
+                       placeholder="{{ 'alias.unlimited-level' | translate }}"
+                       ng-model="filter.maxLevel"
+                       aria-label="{{ 'alias.max-relation-level' | translate }}">
+            </md-input-container>
+        </div>
+        <div class="md-caption" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>relation.relation-filters</div>
+        <tb-relation-filters
+                ng-model="filter.filters"
+                allowed-entity-types="allowedEntityTypes">
+        </tb-relation-filters>
+    </section>
+    <section layout="column" ng-if="filter.type == types.aliasFilterType.assetSearchQuery.value" id="assetSearchQueryFilter">
+        <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
+        <div flex layout="row">
+            <tb-entity-select flex
+                              the-form="theForm"
+                              tb-required="!filter.rootStateEntity"
+                              ng-disabled="filter.rootStateEntity"
+                              ng-model="filter.rootEntity">
+            </tb-entity-select>
+            <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
+                <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
+                <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
+                           aria-label="{{ 'alias.root-state-entity' | translate }}">
+                </md-switch>
+            </section>
+        </div>
+        <div flex layout="row">
+            <md-input-container class="md-block" style="min-width: 100px;">
+                <label translate>relation.direction</label>
+                <md-select required ng-model="filter.direction">
+                    <md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction">
+                        {{ ('relation.search-direction.' + direction) | translate}}
+                    </md-option>
+                </md-select>
+            </md-input-container>
+            <md-input-container flex class="md-block">
+                <label translate>alias.max-relation-level</label>
+                <input name="maxRelationLevel"
+                       type="number"
+                       min="1"
+                       step="1"
+                       placeholder="{{ 'alias.unlimited-level' | translate }}"
+                       ng-model="filter.maxLevel"
+                       aria-label="{{ 'alias.max-relation-level' | translate }}">
+            </md-input-container>
+        </div>
+        <div class="md-caption" style="color: rgba(0,0,0,0.57);" translate>relation.relation-type</div>
+        <tb-relation-type-autocomplete flex
+                                       hide-label
+                                       the-form="theForm"
+                                       ng-model="filter.relationType"
+                                       tb-required="false">
+        </tb-relation-type-autocomplete>
+        <div class="md-caption tb-required" style="color: rgba(0,0,0,0.57);" translate>asset.asset-types</div>
+        <tb-entity-subtype-list
+                tb-required="true"
+                entity-type="types.entityType.asset"
+                ng-model="filter.assetTypes">
+        </tb-entity-subtype-list>
+    </section>
+    <section layout="column" ng-if="filter.type == types.aliasFilterType.deviceSearchQuery.value" id="deviceSearchQueryFilter">
+        <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
+        <div flex layout="row">
+            <tb-entity-select flex
+                              the-form="theForm"
+                              tb-required="!filter.rootStateEntity"
+                              ng-disabled="filter.rootStateEntity"
+                              ng-model="filter.rootEntity">
+            </tb-entity-select>
+            <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
+                <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
+                <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
+                           aria-label="{{ 'alias.root-state-entity' | translate }}">
+                </md-switch>
+            </section>
+        </div>
+        <div flex layout="row">
+            <md-input-container class="md-block" style="min-width: 100px;">
+                <label translate>relation.direction</label>
+                <md-select required ng-model="filter.direction">
+                    <md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction">
+                        {{ ('relation.search-direction.' + direction) | translate}}
+                    </md-option>
+                </md-select>
+            </md-input-container>
+            <md-input-container flex class="md-block">
+                <label translate>alias.max-relation-level</label>
+                <input name="maxRelationLevel"
+                       type="number"
+                       min="1"
+                       step="1"
+                       placeholder="{{ 'alias.unlimited-level' | translate }}"
+                       ng-model="filter.maxLevel"
+                       aria-label="{{ 'alias.max-relation-level' | translate }}">
+            </md-input-container>
+        </div>
+        <div class="md-caption" style="color: rgba(0,0,0,0.57);" translate>relation.relation-type</div>
+        <tb-relation-type-autocomplete flex
+                                       hide-label
+                                       the-form="theForm"
+                                       ng-model="filter.relationType"
+                                       tb-required="false">
+        </tb-relation-type-autocomplete>
+        <div class="md-caption tb-required" style="color: rgba(0,0,0,0.57);" translate>device.device-types</div>
+        <tb-entity-subtype-list
+                tb-required="true"
+                entity-type="types.entityType.device"
+                ng-model="filter.deviceTypes">
+        </tb-entity-subtype-list>
+    </section>
 </div>
diff --git a/ui/src/app/entity/entity-filter-view.directive.js b/ui/src/app/entity/entity-filter-view.directive.js
index 6a358b2..66c1b66 100644
--- a/ui/src/app/entity/entity-filter-view.directive.js
+++ b/ui/src/app/entity/entity-filter-view.directive.js
@@ -74,7 +74,106 @@ export default function EntityFilterViewDirective($compile, $templateCache, $q, 
                             scope.filterDisplayValue = $translate.instant('alias.filter-type-device-type-description', {deviceType: deviceType});
                         }
                         break;
-                    //TODO: Alias filter
+                    case types.aliasFilterType.relationsQuery.value:
+                        var rootEntityText;
+                        var directionText;
+                        var allEntitiesText = $translate.instant('alias.all-entities');
+                        var anyRelationText = $translate.instant('alias.any-relation');
+                        if (scope.filter.rootStateEntity) {
+                            rootEntityText = $translate.instant('alias.state-entity');
+                        } else {
+                            rootEntityText = $translate.instant(types.entityTypeTranslations[scope.filter.rootEntity.entityType].type);
+                        }
+                        directionText = $translate.instant('relation.direction-type.' + scope.filter.direction);
+                        var relationFilters = scope.filter.filters;
+                        if (relationFilters && relationFilters.length) {
+                            var relationFiltersDisplayValues = [];
+                            relationFilters.forEach(function(relationFilter) {
+                                var entitiesText;
+                                if (relationFilter.entityTypes && relationFilter.entityTypes.length) {
+                                    var entitiesNamesList = [];
+                                    relationFilter.entityTypes.forEach(function(entityType) {
+                                        entitiesNamesList.push(
+                                            $translate.instant(types.entityTypeTranslations[entityType].typePlural)
+                                        );
+                                    });
+                                    entitiesText = entitiesNamesList.join(', ');
+                                } else {
+                                    entitiesText = allEntitiesText;
+                                }
+                                var relationTypeText;
+                                if (relationFilter.relationType && relationFilter.relationType.length) {
+                                    relationTypeText = "'" + relationFilter.relationType + "'";
+                                } else {
+                                    relationTypeText = anyRelationText;
+                                }
+                                var relationFilterDisplayValue = $translate.instant('alias.filter-type-relations-query-description',
+                                    {
+                                        entities: entitiesText,
+                                        relationType: relationTypeText,
+                                        direction: directionText,
+                                        rootEntity: rootEntityText
+                                    }
+                                );
+                                relationFiltersDisplayValues.push(relationFilterDisplayValue);
+                            });
+                            scope.filterDisplayValue = relationFiltersDisplayValues.join(', ');
+                        } else {
+                            scope.filterDisplayValue = $translate.instant('alias.filter-type-relations-query-description',
+                                {
+                                    entities: allEntitiesText,
+                                    relationType: anyRelationText,
+                                    direction: directionText,
+                                    rootEntity: rootEntityText
+                                }
+                            );
+                        }
+                        break;
+                    case types.aliasFilterType.assetSearchQuery.value:
+                    case types.aliasFilterType.deviceSearchQuery.value:
+                        allEntitiesText = $translate.instant('alias.all-entities');
+                        anyRelationText = $translate.instant('alias.any-relation');
+                        if (scope.filter.rootStateEntity) {
+                            rootEntityText = $translate.instant('alias.state-entity');
+                        } else {
+                            rootEntityText = $translate.instant(types.entityTypeTranslations[scope.filter.rootEntity.entityType].type);
+                        }
+                        directionText = $translate.instant('relation.direction-type.' + scope.filter.direction);
+                        var relationTypeText;
+                        if (scope.filter.relationType && scope.filter.relationType.length) {
+                            relationTypeText = "'" + scope.filter.relationType + "'";
+                        } else {
+                            relationTypeText = anyRelationText;
+                        }
+
+                        var translationValues = {
+                            relationType: relationTypeText,
+                            direction: directionText,
+                            rootEntity: rootEntityText
+                        }
+
+                        if (scope.filter.type == types.aliasFilterType.assetSearchQuery.value) {
+                            var assetTypesQuoted = [];
+                            scope.filter.assetTypes.forEach(function(assetType) {
+                                assetTypesQuoted.push("'"+assetType+"'");
+                            });
+                            var assetTypesText = assetTypesQuoted.join(', ');
+                            translationValues.assetTypes = assetTypesText;
+                            scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-search-query-description',
+                                translationValues
+                            );
+                        } else {
+                            var deviceTypesQuoted = [];
+                            scope.filter.deviceTypes.forEach(function(deviceType) {
+                                deviceTypesQuoted.push("'"+deviceType+"'");
+                            });
+                            var deviceTypesText = deviceTypesQuoted.join(', ');
+                            translationValues.deviceTypes = deviceTypesText;
+                            scope.filterDisplayValue = $translate.instant('alias.filter-type-device-search-query-description',
+                                translationValues
+                            );
+                        }
+                        break;
                     default:
                         scope.filterDisplayValue = scope.filter.type;
                         break;
diff --git a/ui/src/app/entity/entity-subtype-autocomplete.directive.js b/ui/src/app/entity/entity-subtype-autocomplete.directive.js
index 98110b0..76c15d3 100644
--- a/ui/src/app/entity/entity-subtype-autocomplete.directive.js
+++ b/ui/src/app/entity/entity-subtype-autocomplete.directive.js
@@ -114,6 +114,9 @@ export default function EntitySubtypeAutocomplete($compile, $templateCache, $q, 
                 scope.selectEntitySubtypeText = 'asset.select-asset-type';
                 scope.entitySubtypeText = 'asset.asset-type';
                 scope.entitySubtypeRequiredText = 'asset.asset-type-required';
+                scope.$on('assetSaved', function() {
+                    scope.entitySubtypes = null;
+                });
             } else if (scope.entityType == types.entityType.device) {
                 scope.selectEntitySubtypeText = 'device.select-device-type';
                 scope.entitySubtypeText = 'device.device-type';
diff --git a/ui/src/app/entity/entity-subtype-list.directive.js b/ui/src/app/entity/entity-subtype-list.directive.js
new file mode 100644
index 0000000..c7d6329
--- /dev/null
+++ b/ui/src/app/entity/entity-subtype-list.directive.js
@@ -0,0 +1,146 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import entitySubtypeListTemplate from './entity-subtype-list.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+import './entity-subtype-list.scss';
+
+/*@ngInject*/
+export default function EntitySubtypeListDirective($compile, $templateCache, $q, $mdUtil, $translate, $filter, types, assetService, deviceService) {
+
+    var linker = function (scope, element, attrs, ngModelCtrl) {
+
+        var template = $templateCache.get(entitySubtypeListTemplate);
+        element.html(template);
+
+        scope.ngModelCtrl = ngModelCtrl;
+
+
+        scope.entitySubtypesList = [];
+        scope.entitySubtypes = null;
+
+        if (scope.entityType == types.entityType.asset) {
+            scope.placeholder = scope.tbRequired ? $translate.instant('asset.enter-asset-type')
+                : $translate.instant('asset.any-asset');
+            scope.secondaryPlaceholder = '+' + $translate.instant('asset.asset-type');
+            scope.noSubtypesMathingText = 'asset.no-asset-types-matching';
+            scope.subtypeListEmptyText = 'asset.asset-type-list-empty';
+        } else if (scope.entityType == types.entityType.device) {
+            scope.placeholder = scope.tbRequired ? $translate.instant('device.enter-device-type')
+                : $translate.instant('device.any-device');
+            scope.secondaryPlaceholder = '+' + $translate.instant('device.device-type');
+            scope.noSubtypesMathingText = 'device.no-device-types-matching';
+            scope.subtypeListEmptyText = 'device.device-type-list-empty';
+        }
+
+        scope.$watch('tbRequired', function () {
+            scope.updateValidity();
+        });
+
+        scope.fetchEntitySubtypes = function(searchText) {
+            var deferred = $q.defer();
+            loadSubTypes().then(
+                function success(subTypes) {
+                    var result = $filter('filter')(subTypes, {'$': searchText});
+                    if (result && result.length) {
+                        deferred.resolve(result);
+                    } else {
+                        deferred.resolve([searchText]);
+                    }
+                },
+                function fail() {
+                    deferred.reject();
+                }
+            );
+            return deferred.promise;
+        }
+
+        scope.updateValidity = function() {
+            var value = ngModelCtrl.$viewValue;
+            var valid = !scope.tbRequired || value && value.length > 0;
+            ngModelCtrl.$setValidity('entitySubtypesList', valid);
+        }
+
+        ngModelCtrl.$render = function () {
+            scope.entitySubtypesList = ngModelCtrl.$viewValue;
+            if (!scope.entitySubtypesList) {
+                scope.entitySubtypesList = [];
+            }
+        }
+
+        scope.$watch('entitySubtypesList', function () {
+            ngModelCtrl.$setViewValue(scope.entitySubtypesList);
+            scope.updateValidity();
+        }, true);
+
+        function loadSubTypes() {
+            var deferred = $q.defer();
+            if (!scope.entitySubtypes) {
+                var entitySubtypesPromise;
+                if (scope.entityType == types.entityType.asset) {
+                    entitySubtypesPromise = assetService.getAssetTypes();
+                } else if (scope.entityType == types.entityType.device) {
+                    entitySubtypesPromise = deviceService.getDeviceTypes();
+                }
+                if (entitySubtypesPromise) {
+                    entitySubtypesPromise.then(
+                        function success(types) {
+                            scope.entitySubtypes = [];
+                            types.forEach(function (type) {
+                                scope.entitySubtypes.push(type.type);
+                            });
+                            deferred.resolve(scope.entitySubtypes);
+                        },
+                        function fail() {
+                            deferred.reject();
+                        }
+                    );
+                } else {
+                    deferred.reject();
+                }
+            } else {
+                deferred.resolve(scope.entitySubtypes);
+            }
+            return deferred.promise;
+        }
+
+        $compile(element.contents())(scope);
+
+        $mdUtil.nextTick(function(){
+            var inputElement = angular.element('input', element);
+            inputElement.on('blur', function() {
+                scope.inputTouched = true;
+            } );
+        });
+
+    }
+
+    return {
+        restrict: "E",
+        require: "^ngModel",
+        link: linker,
+        scope: {
+            disabled:'=ngDisabled',
+            tbRequired: '=?',
+            entityType: "="
+        }
+    };
+
+}
diff --git a/ui/src/app/entity/entity-subtype-list.scss b/ui/src/app/entity/entity-subtype-list.scss
new file mode 100644
index 0000000..bbb2a1c
--- /dev/null
+++ b/ui/src/app/entity/entity-subtype-list.scss
@@ -0,0 +1,30 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*.tb-entity-subtype-list {
+  #entity_subtype_list_chips {
+    .md-chips {
+      padding-bottom: 1px;
+    }
+  }
+  .tb-error-messages {
+    margin-top: -11px;
+    height: 35px;
+    .tb-error-message {
+      padding-left: 1px;
+    }
+  }
+}*/
diff --git a/ui/src/app/entity/entity-subtype-list.tpl.html b/ui/src/app/entity/entity-subtype-list.tpl.html
new file mode 100644
index 0000000..2a1519a
--- /dev/null
+++ b/ui/src/app/entity/entity-subtype-list.tpl.html
@@ -0,0 +1,54 @@
+<!--
+
+    Copyright © 2016-2017 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+
+<section flex layout='column' class="tb-entity-subtype-list">
+    <md-chips flex
+              readonly="disabled"
+              id="entity_subtype_list_chips"
+              ng-required="tbRequired"
+              ng-model="entitySubtypesList"
+              placeholder="{{placeholder}}"
+              secondary-placeholder="{{secondaryPlaceholder}}"
+              md-autocomplete-snap
+              md-require-match="false">
+        <md-autocomplete
+                md-no-cache="true"
+                id="entitySubtype"
+                md-selected-item="selectedEntitySubtype"
+                md-search-text="entitySubtypeSearchText"
+                md-items="item in fetchEntitySubtypes(entitySubtypeSearchText)"
+                md-item-text="item"
+                md-min-length="0"
+                placeholder="{{ (!entitySubtypesList || !entitySubtypesList.length) ? placeholder : secondaryPlaceholder }}">
+            <md-item-template>
+                <span md-highlight-text="entitySubtypeSearchText" md-highlight-flags="^i">{{item}}</span>
+            </md-item-template>
+            <md-not-found>
+                <span translate translate-values='{ entitySubtype: entitySubtypeSearchText }'>{{noSubtypesMathingText}}</span>
+            </md-not-found>
+        </md-autocomplete>
+        <md-chip-template>
+            <span>
+                <strong>{{$chip}}</strong>
+            </span>
+        </md-chip-template>
+    </md-chips>
+    <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" ng-if="inputTouched && tbRequired" role="alert">
+        <div translate ng-message="entitySubtypesList" class="tb-error-message">{{subtypeListEmptyText}}</div>
+    </div>
+</section>
diff --git a/ui/src/app/entity/entity-type-list.directive.js b/ui/src/app/entity/entity-type-list.directive.js
new file mode 100644
index 0000000..cb07cfc
--- /dev/null
+++ b/ui/src/app/entity/entity-type-list.directive.js
@@ -0,0 +1,111 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import entityTypeListTemplate from './entity-type-list.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+import './entity-type-list.scss';
+
+/*@ngInject*/
+export default function EntityTypeListDirective($compile, $templateCache, $q, $mdUtil, $translate, $filter, types, entityService) {
+
+    var linker = function (scope, element, attrs, ngModelCtrl) {
+
+        var template = $templateCache.get(entityTypeListTemplate);
+        element.html(template);
+
+        scope.ngModelCtrl = ngModelCtrl;
+
+        scope.placeholder = scope.tbRequired ? $translate.instant('entity.enter-entity-type')
+                                : $translate.instant('entity.any-entity');
+        scope.secondaryPlaceholder = '+' + $translate.instant('entity.entity-type');
+
+        var entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes);
+        scope.entityTypesList = [];
+        for (var type in entityTypes) {
+            var entityTypeInfo = {};
+            entityTypeInfo.value = entityTypes[type];
+            entityTypeInfo.name = $translate.instant(types.entityTypeTranslations[entityTypeInfo.value].type) + '';
+            scope.entityTypesList.push(entityTypeInfo);
+        }
+
+        scope.$watch('tbRequired', function () {
+            scope.updateValidity();
+        });
+
+        scope.fetchEntityTypes = function(searchText) {
+            var deferred = $q.defer();
+            var entityTypes = $filter('filter')(scope.entityTypesList, {name: searchText});
+            deferred.resolve(entityTypes);
+            return deferred.promise;
+        }
+
+        scope.updateValidity = function() {
+            var value = ngModelCtrl.$viewValue;
+            var valid = !scope.tbRequired || value && value.length > 0;
+            ngModelCtrl.$setValidity('entityTypeList', valid);
+        }
+
+        ngModelCtrl.$render = function () {
+            scope.entityTypeList = [];
+            var value = ngModelCtrl.$viewValue;
+            if (value && value.length) {
+                value.forEach(function(type) {
+                    var entityTypeInfo = {};
+                    entityTypeInfo.value = type;
+                    entityTypeInfo.name = $translate.instant(types.entityTypeTranslations[entityTypeInfo.value].type) + '';
+                    scope.entityTypeList.push(entityTypeInfo);
+                });
+            }
+        }
+
+        scope.$watch('entityTypeList', function () {
+            var values = [];
+            if (scope.entityTypeList && scope.entityTypeList.length) {
+                scope.entityTypeList.forEach(function(entityType) {
+                    values.push(entityType.value);
+                });
+            }
+            ngModelCtrl.$setViewValue(values);
+            scope.updateValidity();
+        }, true);
+
+        $compile(element.contents())(scope);
+
+        $mdUtil.nextTick(function(){
+            var inputElement = angular.element('input', element);
+            inputElement.on('blur', function() {
+                scope.inputTouched = true;
+            } );
+        });
+
+    }
+
+    return {
+        restrict: "E",
+        require: "^ngModel",
+        link: linker,
+        scope: {
+            disabled:'=ngDisabled',
+            tbRequired: '=?',
+            allowedEntityTypes: '=?'
+        }
+    };
+
+}
diff --git a/ui/src/app/entity/entity-type-list.scss b/ui/src/app/entity/entity-type-list.scss
new file mode 100644
index 0000000..b94b992
--- /dev/null
+++ b/ui/src/app/entity/entity-type-list.scss
@@ -0,0 +1,30 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*.tb-entity-type-list {
+  #entity_type_list_chips {
+    .md-chips {
+      padding-bottom: 1px;
+    }
+  }
+  .tb-error-messages {
+    margin-top: -11px;
+    height: 35px;
+    .tb-error-message {
+      padding-left: 1px;
+    }
+  }
+}*/
\ No newline at end of file
diff --git a/ui/src/app/entity/entity-type-list.tpl.html b/ui/src/app/entity/entity-type-list.tpl.html
new file mode 100644
index 0000000..ff10a30
--- /dev/null
+++ b/ui/src/app/entity/entity-type-list.tpl.html
@@ -0,0 +1,54 @@
+<!--
+
+    Copyright © 2016-2017 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+
+<section flex layout='column' class="tb-entity-type-list">
+    <md-chips flex
+              readonly="disabled"
+              id="entity_type_list_chips"
+              ng-required="tbRequired"
+              ng-model="entityTypeList"
+              placeholder="{{placeholder}}"
+              secondary-placeholder="{{secondaryPlaceholder}}"
+              md-autocomplete-snap
+              md-require-match="true">
+        <md-autocomplete
+                md-no-cache="true"
+                id="entityType"
+                md-selected-item="selectedEntityType"
+                md-search-text="entityTypeSearchText"
+                md-items="item in fetchEntityTypes(entityTypeSearchText)"
+                md-item-text="item.name"
+                md-min-length="0"
+                placeholder="{{ (!entityTypeList || !entityTypeList.length) ? placeholder : secondaryPlaceholder }}">
+            <md-item-template>
+                <span md-highlight-text="entityTypeSearchText" md-highlight-flags="^i">{{item.name}}</span>
+            </md-item-template>
+            <md-not-found>
+                <span translate translate-values='{ entityType: entityTypeSearchText }'>entity.no-entity-types-matching</span>
+            </md-not-found>
+        </md-autocomplete>
+        <md-chip-template>
+            <span>
+                <strong>{{$chip.name}}</strong>
+            </span>
+        </md-chip-template>
+    </md-chips>
+    <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" ng-if="inputTouched && tbRequired" role="alert">
+        <div translate ng-message="entityTypeList" class="tb-error-message">entity.entity-type-list-empty</div>
+    </div>
+</section>
diff --git a/ui/src/app/entity/entity-type-select.directive.js b/ui/src/app/entity/entity-type-select.directive.js
index bca2ee0..068c141 100644
--- a/ui/src/app/entity/entity-type-select.directive.js
+++ b/ui/src/app/entity/entity-type-select.directive.js
@@ -23,7 +23,7 @@ import entityTypeSelectTemplate from './entity-type-select.tpl.html';
 /* eslint-enable import/no-unresolved, import/default */
 
 /*@ngInject*/
-export default function EntityTypeSelect($compile, $templateCache, utils, userService, types) {
+export default function EntityTypeSelect($compile, $templateCache, utils, entityService, userService, types) {
 
     var linker = function (scope, element, attrs, ngModelCtrl) {
         var template = $templateCache.get(entityTypeSelectTemplate);
@@ -39,36 +39,7 @@ export default function EntityTypeSelect($compile, $templateCache, utils, userSe
 
         scope.ngModelCtrl = ngModelCtrl;
 
-        var authority = userService.getAuthority();
-        scope.entityTypes = {};
-        switch(authority) {
-            case 'SYS_ADMIN':
-                scope.entityTypes.tenant = types.entityType.tenant;
-                scope.entityTypes.rule = types.entityType.rule;
-                scope.entityTypes.plugin = types.entityType.plugin;
-                break;
-            case 'TENANT_ADMIN':
-                scope.entityTypes.device = types.entityType.device;
-                scope.entityTypes.asset = types.entityType.asset;
-                scope.entityTypes.customer = types.entityType.customer;
-                scope.entityTypes.rule = types.entityType.rule;
-                scope.entityTypes.plugin = types.entityType.plugin;
-                scope.entityTypes.dashboard = types.entityType.dashboard;
-                break;
-            case 'CUSTOMER_USER':
-                scope.entityTypes.device = types.entityType.device;
-                scope.entityTypes.asset = types.entityType.asset;
-                scope.entityTypes.dashboard = types.entityType.dashboard;
-                break;
-        }
-
-        if (scope.allowedEntityTypes) {
-            for (var entityType in scope.entityTypes) {
-                if (scope.allowedEntityTypes.indexOf(scope.entityTypes[entityType]) === -1) {
-                    delete scope.entityTypes[entityType];
-                }
-            }
-        }
+        scope.entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes);
 
         scope.typeName = function(type) {
             return type ? types.entityTypeTranslations[type].type : '';
diff --git a/ui/src/app/entity/index.js b/ui/src/app/entity/index.js
index 2fc1d63..2b8d434 100644
--- a/ui/src/app/entity/index.js
+++ b/ui/src/app/entity/index.js
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-import EntityAliasesController from './entity-aliases.controller';
-import EntityAliasDialogController from './entity-alias-dialog.controller';
+import EntityAliasesController from './alias/entity-aliases.controller';
+import EntityAliasDialogController from './alias/entity-alias-dialog.controller';
 import EntityTypeSelectDirective from './entity-type-select.directive';
+import EntityTypeListDirective from './entity-type-list.directive';
+import EntitySubtypeListDirective from './entity-subtype-list.directive';
 import EntitySubtypeSelectDirective from './entity-subtype-select.directive';
 import EntitySubtypeAutocompleteDirective from './entity-subtype-autocomplete.directive';
 import EntityAutocompleteDirective from './entity-autocomplete.directive';
@@ -24,11 +26,12 @@ import EntityListDirective from './entity-list.directive';
 import EntitySelectDirective from './entity-select.directive';
 import EntityFilterDirective from './entity-filter.directive';
 import EntityFilterViewDirective from './entity-filter-view.directive';
-import AliasesEntitySelectPanelController from './aliases-entity-select-panel.controller';
-import AliasesEntitySelectDirective from './aliases-entity-select.directive';
+import AliasesEntitySelectPanelController from './alias/aliases-entity-select-panel.controller';
+import AliasesEntitySelectDirective from './alias/aliases-entity-select.directive';
 import AddAttributeDialogController from './attribute/add-attribute-dialog.controller';
 import AddWidgetToDashboardDialogController from './attribute/add-widget-to-dashboard-dialog.controller';
 import AttributeTableDirective from './attribute/attribute-table.directive';
+import RelationFiltersDirective from './relation/relation-filters.directive';
 import RelationTableDirective from './relation/relation-table.directive';
 import RelationTypeAutocompleteDirective from './relation/relation-type-autocomplete.directive';
 
@@ -39,6 +42,8 @@ export default angular.module('thingsboard.entity', [])
     .controller('AddAttributeDialogController', AddAttributeDialogController)
     .controller('AddWidgetToDashboardDialogController', AddWidgetToDashboardDialogController)
     .directive('tbEntityTypeSelect', EntityTypeSelectDirective)
+    .directive('tbEntityTypeList', EntityTypeListDirective)
+    .directive('tbEntitySubtypeList', EntitySubtypeListDirective)
     .directive('tbEntitySubtypeSelect', EntitySubtypeSelectDirective)
     .directive('tbEntitySubtypeAutocomplete', EntitySubtypeAutocompleteDirective)
     .directive('tbEntityAutocomplete', EntityAutocompleteDirective)
@@ -48,6 +53,7 @@ export default angular.module('thingsboard.entity', [])
     .directive('tbEntityFilterView', EntityFilterViewDirective)
     .directive('tbAliasesEntitySelect', AliasesEntitySelectDirective)
     .directive('tbAttributeTable', AttributeTableDirective)
+    .directive('tbRelationFilters', RelationFiltersDirective)
     .directive('tbRelationTable', RelationTableDirective)
     .directive('tbRelationTypeAutocomplete', RelationTypeAutocompleteDirective)
     .name;
diff --git a/ui/src/app/entity/relation/relation-filters.directive.js b/ui/src/app/entity/relation/relation-filters.directive.js
new file mode 100644
index 0000000..59821dd
--- /dev/null
+++ b/ui/src/app/entity/relation/relation-filters.directive.js
@@ -0,0 +1,85 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import './relation-filters.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import relationFiltersTemplate from './relation-filters.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function RelationFilters($compile, $templateCache) {
+
+    return {
+        restrict: "E",
+        require: "^ngModel",
+        scope: {
+            allowedEntityTypes: '=?'
+        },
+        link: linker
+    };
+
+    function linker( scope, element, attrs, ngModelCtrl ) {
+
+        var template = $templateCache.get(relationFiltersTemplate);
+        element.html(template);
+
+        scope.relationFilters = [];
+
+        scope.addFilter = addFilter;
+        scope.removeFilter = removeFilter;
+
+        ngModelCtrl.$render = function () {
+            if (ngModelCtrl.$viewValue) {
+                var value = ngModelCtrl.$viewValue;
+                value.forEach(function (filter) {
+                    scope.relationFilters.push(filter);
+                });
+            }
+            scope.$watch('relationFilters', function (newVal, prevVal) {
+                if (!angular.equals(newVal, prevVal)) {
+                    updateValue();
+                }
+            }, true);
+        }
+
+        function addFilter() {
+            var filter = {
+                relationType: null,
+                entityTypes: []
+            };
+            scope.relationFilters.push(filter);
+        }
+
+        function removeFilter($event, filter) {
+            var index = scope.relationFilters.indexOf(filter);
+            if (index > -1) {
+                scope.relationFilters.splice(index, 1);
+            }
+        }
+
+        function updateValue() {
+            var value = [];
+            scope.relationFilters.forEach(function (filter) {
+                value.push(filter);
+            });
+            ngModelCtrl.$setViewValue(value);
+        }
+        $compile(element.contents())(scope);
+    }
+}
diff --git a/ui/src/app/entity/relation/relation-filters.scss b/ui/src/app/entity/relation/relation-filters.scss
new file mode 100644
index 0000000..50d49af
--- /dev/null
+++ b/ui/src/app/entity/relation/relation-filters.scss
@@ -0,0 +1,77 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.tb-relation-filters {
+  .header {
+    padding-left: 5px;
+    padding-right: 5px;
+    padding-bottom: 5px;
+    .cell {
+      padding-left: 5px;
+      padding-right: 5px;
+      color: rgba(0,0,0,.54);
+      font-size: 12px;
+      font-weight: 700;
+      white-space: nowrap;
+    }
+  }
+  .body {
+    padding-left: 5px;
+    padding-right: 5px;
+    max-height: 300px;
+    overflow: auto;
+    padding-bottom: 20px;
+    .row {
+      padding-top: 5px;
+    }
+    .cell {
+      padding-left: 5px;
+      padding-right: 5px;
+
+      md-select {
+        margin: 0 0 24px;
+      }
+
+      md-input-container {
+        margin: 0;
+      }
+
+      md-chips-wrap {
+        padding: 0px;
+        margin: 0 0 24px;
+        .md-chip-input-container {
+          margin: 0;
+        }
+        md-autocomplete {
+          height: 30px;
+          md-autocomplete-wrap {
+            height: 30px;
+          }
+        }
+      }
+      .md-chips .md-chip-input-container input {
+        padding: 2px 2px 2px;
+        height: 26px;
+        line-height: 26px;
+      }
+
+    }
+
+    .md-button {
+      margin: 0;
+    }
+  }
+}
\ No newline at end of file
diff --git a/ui/src/app/entity/relation/relation-filters.tpl.html b/ui/src/app/entity/relation/relation-filters.tpl.html
new file mode 100644
index 0000000..679a488
--- /dev/null
+++ b/ui/src/app/entity/relation/relation-filters.tpl.html
@@ -0,0 +1,67 @@
+<!--
+
+    Copyright © 2016-2017 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<div class="tb-relation-filters">
+    <div class="header" ng-show="relationFilters.length">
+        <div layout="row" layout-align="start center">
+            <span class="cell" style="width: 200px; min-width: 200px;" translate>relation.type</span>
+            <span class="cell" flex translate>entity.entity-types</span>
+            <span class="cell" style="width: 40px; min-width: 40px;">&nbsp</span>
+        </div>
+    </div>
+    <div class="body" ng-show="relationFilters.length">
+        <div class="row" ng-form name="relationFilterForm" flex layout="row" layout-align="start center" ng-repeat="filter in relationFilters track by $index">
+            <div class="md-whiteframe-1dp" flex layout="row" layout-align="start center">
+                <tb-relation-type-autocomplete class="cell" style="width: 200px; min-width: 200px;"
+                                               hide-label
+                                               the-form="relationFilterForm"
+                                               ng-model="filter.relationType"
+                                               tb-required="false">
+                </tb-relation-type-autocomplete>
+                <tb-entity-type-list class="cell" flex
+                        ng-model="filter.entityTypes"
+                        allowed-entity-types="allowedEntityTypes"
+                        tb-required="false">
+                </tb-entity-type-list>
+                <md-button ng-disabled="loading" class="md-icon-button md-primary" style="width: 40px; min-width: 40px;"
+                           ng-click="removeFilter($event, filter)" aria-label="{{ 'action.remove' | translate }}">
+                    <md-tooltip md-direction="top">
+                        {{ 'relation.remove-relation-filter' | translate }}
+                    </md-tooltip>
+                    <md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">
+                        close
+                    </md-icon>
+                </md-button>
+            </div>
+        </div>
+    </div>
+    <div class="any-filter" ng-show="!relationFilters.length">
+        <span layout-align="center center"
+              class="tb-prompt" translate>relation.any-relation</span>
+    </div>
+    <div>
+        <md-button ng-disabled="loading" class="md-primary md-raised" ng-click="addFilter($event)" aria-label="{{ 'action.add' | translate }}">
+            <md-tooltip md-direction="top">
+                {{ 'relation.add-relation-filter' | translate }}
+            </md-tooltip>
+            <md-icon aria-label="{{ 'action.add' | translate }}" class="material-icons">
+                add
+            </md-icon>
+            {{ 'action.add' | translate }}
+        </md-button>
+    </div>
+</div>
\ No newline at end of file
diff --git a/ui/src/app/entity/relation/relation-type-autocomplete.directive.js b/ui/src/app/entity/relation/relation-type-autocomplete.directive.js
index a7e5ea4..4b5480d 100644
--- a/ui/src/app/entity/relation/relation-type-autocomplete.directive.js
+++ b/ui/src/app/entity/relation/relation-type-autocomplete.directive.js
@@ -29,6 +29,8 @@ export default function RelationTypeAutocomplete($compile, $templateCache, $q, $
         element.html(template);
 
         scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
+        scope.hideLabel = angular.isDefined(attrs.hideLabel) ? true : false;
+
         scope.relationType = null;
         scope.relationTypeSearchText = '';
         scope.relationTypes = [];
diff --git a/ui/src/app/entity/relation/relation-type-autocomplete.tpl.html b/ui/src/app/entity/relation/relation-type-autocomplete.tpl.html
index f71c134..39f32d6 100644
--- a/ui/src/app/entity/relation/relation-type-autocomplete.tpl.html
+++ b/ui/src/app/entity/relation/relation-type-autocomplete.tpl.html
@@ -26,7 +26,7 @@
                  md-items="item in fetchRelationTypes(relationTypeSearchText)"
                  md-item-text="item"
                  md-min-length="0"
-                 md-floating-label="{{ 'relation.relation-type' | translate }}"
+                 md-floating-label="{{ tbRequired  ? ('relation.relation-type' | translate) : ( !relationType ? ('relation.any-relation-type' | translate) : ' ') }}"
                  md-select-on-match="true"
                  md-menu-class="tb-relation-type-autocomplete">
     <md-item-template>
diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js
index 8d4a098..86d5240 100644
--- a/ui/src/app/import-export/import-export.service.js
+++ b/ui/src/app/import-export/import-export.service.js
@@ -16,7 +16,7 @@
 /* eslint-disable import/no-unresolved, import/default */
 
 import importDialogTemplate from './import-dialog.tpl.html';
-import entityAliasesTemplate from '../entity/entity-aliases.tpl.html';
+import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html';
 
 /* eslint-enable import/no-unresolved, import/default */
 
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 88a6021..cd21747 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -129,14 +129,24 @@ export default angular.module('thingsboard.locale', [])
                     "filter-type-device-type-description": "Devices of type '{{deviceType}}'",
                     "filter-type-device-type-and-name-description": "Devices of type '{{deviceType}}' and with name starting with '{{prefix}}'",
                     "filter-type-relations-query": "Relations query",
+                    "filter-type-relations-query-description": "{{entities}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
                     "filter-type-asset-search-query": "Asset search query",
+                    "filter-type-asset-search-query-description": "Assets with types {{assetTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
                     "filter-type-device-search-query": "Device search query",
+                    "filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
                     "entity-filter": "Entity filter",
                     "resolve-multiple": "Resolve as multiple entities",
                     "filter-type": "Filter type",
                     "filter-type-required": "Filter type is required.",
                     "entity-filter-no-entity-matched": "No entities matching specified filter were found.",
-                    "no-entity-filter-specified": "No entity filter specified"
+                    "no-entity-filter-specified": "No entity filter specified",
+                    "root-state-entity": "Use dashboard state entity as root",
+                    "root-entity": "Root entity",
+                    "max-relation-level": "Max relation level",
+                    "unlimited-level": "Unlimited level",
+                    "state-entity": "Dashboard state entity",
+                    "all-entities": "All entities",
+                    "any-relation": "any"
                 },
                 "asset": {
                     "asset": "Asset",
@@ -159,6 +169,11 @@ export default angular.module('thingsboard.locale', [])
                     "asset-type": "Asset type",
                     "asset-type-required": "Asset type is required.",
                     "select-asset-type": "Select asset type",
+                    "enter-asset-type": "Enter asset type",
+                    "any-asset": "Any asset",
+                    "no-asset-types-matching": "No asset types matching '{{entitySubtype}}' were found.",
+                    "asset-type-list-empty": "No asset types selected.",
+                    "asset-types": "Asset types",
                     "name": "Name",
                     "name-required": "Name is required.",
                     "description": "Description",
@@ -444,6 +459,7 @@ export default angular.module('thingsboard.locale', [])
                 },
                 "datasource": {
                     "type": "Datasource type",
+                    "name": "Name",
                     "add-datasource-prompt": "Please add datasource"
                 },
                 "details": {
@@ -524,6 +540,11 @@ export default angular.module('thingsboard.locale', [])
                     "device-type": "Device type",
                     "device-type-required": "Device type is required.",
                     "select-device-type": "Select device type",
+                    "enter-device-type": "Enter device type",
+                    "any-device": "Any device",
+                    "no-device-types-matching": "No device types matching '{{entitySubtype}}' were found.",
+                    "device-type-list-empty": "No device types selected.",
+                    "device-types": "Device types",
                     "name": "Name",
                     "name-required": "Name is required.",
                     "description": "Description",
@@ -564,10 +585,17 @@ export default angular.module('thingsboard.locale', [])
                     "remove-alias": "Remove entity alias",
                     "add-alias": "Add entity alias",
                     "entity-list": "Entity list",
+                    "entity-type": "Entity type",
+                    "entity-types": "Entity types",
+                    "entity-type-list": "Entity type list",
+                    "any-entity": "Any entity",
+                    "enter-entity-type": "Enter entity type",
                     "no-entities-matching": "No entities matching '{{entity}}' were found.",
+                    "no-entity-types-matching": "No entity types matching '{{entityType}}' were found.",
                     "name-starts-with": "Name starts with",
                     "use-entity-name-filter": "Use filter",
                     "entity-list-empty": "No entities selected.",
+                    "entity-type-list-empty": "No entity types selected.",
                     "entity-name-filter-required": "Entity name filter is required.",
                     "entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.",
                     "all-subtypes": "All",
@@ -581,30 +609,39 @@ export default angular.module('thingsboard.locale', [])
                     "type": "Type",
                     "type-required": "Entity type is required.",
                     "type-device": "Device",
+                    "type-devices": "Devices",
                     "list-of-devices": "{ count, select, 1 {One device} other {List of # devices} }",
                     "device-name-starts-with": "Devices whose names start with '{{prefix}}'",
                     "type-asset": "Asset",
+                    "type-assets": "Assets",
                     "list-of-assets": "{ count, select, 1 {One asset} other {List of # assets} }",
                     "asset-name-starts-with": "Assets whose names start with '{{prefix}}'",
                     "type-rule": "Rule",
+                    "type-rules": "Rules",
                     "list-of-rules": "{ count, select, 1 {One rule} other {List of # rules} }",
                     "rule-name-starts-with": "Rules whose names start with '{{prefix}}'",
                     "type-plugin": "Plugin",
+                    "type-plugins": "Plugins",
                     "list-of-plugins": "{ count, select, 1 {One plugin} other {List of # plugins} }",
                     "plugin-name-starts-with": "Plugins whose names start with '{{prefix}}'",
                     "type-tenant": "Tenant",
+                    "type-tenants": "Tenants",
                     "list-of-tenants": "{ count, select, 1 {One tenant} other {List of # tenants} }",
                     "tenant-name-starts-with": "Tenants whose names start with '{{prefix}}'",
                     "type-customer": "Customer",
+                    "type-customers": "Customers",
                     "list-of-customers": "{ count, select, 1 {One customer} other {List of # customers} }",
                     "customer-name-starts-with": "Customers whose names start with '{{prefix}}'",
                     "type-user": "User",
+                    "type-users": "Users",
                     "list-of-users": "{ count, select, 1 {One user} other {List of # users} }",
                     "user-name-starts-with": "Users whose names start with '{{prefix}}'",
                     "type-dashboard": "Dashboard",
+                    "type-dashboards": "Dashboards",
                     "list-of-dashboards": "{ count, select, 1 {One dashboard} other {List of # dashboards} }",
                     "dashboard-name-starts-with": "Dashboards whose names start with '{{prefix}}'",
                     "type-alarm": "Alarm",
+                    "type-alarms": "Alarms",
                     "list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }",
                     "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'"
                 },
@@ -770,6 +807,10 @@ export default angular.module('thingsboard.locale', [])
                         "FROM": "From",
                         "TO": "To"
                     },
+                    "direction-type": {
+                        "FROM": "from",
+                        "TO": "to"
+                    },
                     "from-relations": "Outbound relations",
                     "to-relations": "Inbound relations",
                     "selected-relations": "{ count, select, 1 {1 relation} other {# relations} } selected",
@@ -783,6 +824,7 @@ export default angular.module('thingsboard.locale', [])
                     "delete": "Delete relation",
                     "relation-type": "Relation type",
                     "relation-type-required": "Relation type is required.",
+                    "any-relation-type": "Any type",
                     "add": "Add relation",
                     "delete-to-relation-title": "Are you sure you want to delete relation to the entity '{{entityName}}'?",
                     "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', [])
                     "delete-from-relation-title": "Are you sure you want to delete relation from the entity '{{entityName}}'?",
                     "delete-from-relation-text": "Be careful, after the confirmation current entity will be unrelated from the entity '{{entityName}}'.",
                     "delete-from-relations-title": "Are you sure you want to delete { count, select, 1 {1 relation} other {# relations} }?",
-                    "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."
+                    "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.",
+                    "remove-relation-filter": "Remove relation filter",
+                    "add-relation-filter": "Add relation filter",
+                    "any-relation": "Any relation",
+                    "relation-filters": "Relation filters"
                 },
                 "rule": {
                     "rule": "Rule",
diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss
index dbe3543..bf44493 100644
--- a/ui/src/scss/main.scss
+++ b/ui/src/scss/main.scss
@@ -236,6 +236,15 @@ div {
   }
 }
 
+.md-caption {
+  &.tb-required:after {
+    content: ' *';
+    font-size: 10px;
+    vertical-align: top;
+    color: rgba(0,0,0,0.54);
+  }
+}
+
 pre.tb-highlight {
   background-color: #f7f7f7;
   display: block;