Commit 2cb71c15b216f27c0b4371c5c16ab4a2031190fd

Authored by Igor Kulikov
Committed by GitHub
2 parents 0bc4252f 9cdfe4ef

Merge pull request #164 from thingsboard/feature/TB-61

TB-61: Improve Alias Filter
Showing 52 changed files with 2617 additions and 1097 deletions

Too many changes to show.

To preserve performance only 52 of 74 files are displayed.

@@ -250,6 +250,21 @@ public class EntityRelationController extends BaseController { @@ -250,6 +250,21 @@ public class EntityRelationController extends BaseController {
250 } 250 }
251 } 251 }
252 252
  253 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
  254 + @RequestMapping(value = "/relations/info", method = RequestMethod.POST)
  255 + @ResponseBody
  256 + public List<EntityRelationInfo> findInfoByQuery(@RequestBody EntityRelationsQuery query) throws ThingsboardException {
  257 + checkNotNull(query);
  258 + checkNotNull(query.getParameters());
  259 + checkNotNull(query.getFilters());
  260 + checkEntityId(query.getParameters().getEntityId());
  261 + try {
  262 + return checkNotNull(relationService.findInfoByQuery(query).get());
  263 + } catch (Exception e) {
  264 + throw handleException(e);
  265 + }
  266 + }
  267 +
253 private RelationTypeGroup parseRelationTypeGroup(String strRelationTypeGroup, RelationTypeGroup defaultValue) { 268 private RelationTypeGroup parseRelationTypeGroup(String strRelationTypeGroup, RelationTypeGroup defaultValue) {
254 RelationTypeGroup result = defaultValue; 269 RelationTypeGroup result = defaultValue;
255 if (strRelationTypeGroup != null && strRelationTypeGroup.trim().length()>0) { 270 if (strRelationTypeGroup != null && strRelationTypeGroup.trim().length()>0) {
@@ -191,7 +191,7 @@ public class BaseRelationService implements RelationService { @@ -191,7 +191,7 @@ public class BaseRelationService implements RelationService {
191 191
192 @Override 192 @Override
193 public ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query) { 193 public ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query) {
194 - log.trace("Executing findByQuery [{}][{}]", query); 194 + log.trace("Executing findByQuery [{}]", query);
195 RelationsSearchParameters params = query.getParameters(); 195 RelationsSearchParameters params = query.getParameters();
196 final List<EntityTypeFilter> filters = query.getFilters(); 196 final List<EntityTypeFilter> filters = query.getFilters();
197 if (filters == null || filters.isEmpty()) { 197 if (filters == null || filters.isEmpty()) {
@@ -224,6 +224,30 @@ public class BaseRelationService implements RelationService { @@ -224,6 +224,30 @@ public class BaseRelationService implements RelationService {
224 } 224 }
225 } 225 }
226 226
  227 + @Override
  228 + public ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(EntityRelationsQuery query) {
  229 + log.trace("Executing findInfoByQuery [{}]", query);
  230 + ListenableFuture<List<EntityRelation>> relations = findByQuery(query);
  231 + EntitySearchDirection direction = query.getParameters().getDirection();
  232 + ListenableFuture<List<EntityRelationInfo>> relationsInfo = Futures.transform(relations,
  233 + (AsyncFunction<List<EntityRelation>, List<EntityRelationInfo>>) relations1 -> {
  234 + List<ListenableFuture<EntityRelationInfo>> futures = new ArrayList<>();
  235 + relations1.stream().forEach(relation ->
  236 + futures.add(fetchRelationInfoAsync(relation,
  237 + relation2 -> direction == EntitySearchDirection.FROM ? relation2.getTo() : relation2.getFrom(),
  238 + (EntityRelationInfo relationInfo, String entityName) -> {
  239 + if (direction == EntitySearchDirection.FROM) {
  240 + relationInfo.setToName(entityName);
  241 + } else {
  242 + relationInfo.setFromName(entityName);
  243 + }
  244 + }))
  245 + );
  246 + return Futures.successfulAsList(futures);
  247 + });
  248 + return relationsInfo;
  249 + }
  250 +
227 protected void validate(EntityRelation relation) { 251 protected void validate(EntityRelation relation) {
228 if (relation == null) { 252 if (relation == null) {
229 throw new DataValidationException("Relation type should be specified!"); 253 throw new DataValidationException("Relation type should be specified!");
@@ -52,6 +52,8 @@ public interface RelationService { @@ -52,6 +52,8 @@ public interface RelationService {
52 52
53 ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query); 53 ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query);
54 54
  55 + ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(EntityRelationsQuery query);
  56 +
55 // TODO: This method may be useful for some validations in the future 57 // TODO: This method may be useful for some validations in the future
56 // ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to); 58 // ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to);
57 59
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +const varsRegex = /\$\{([^\}]*)\}/g;
  18 +
  19 +export default class AliasController {
  20 +
  21 + constructor($scope, $q, $filter, utils, types, entityService, stateController, entityAliases) {
  22 + this.$scope = $scope;
  23 + this.$q = $q;
  24 + this.$filter = $filter;
  25 + this.utils = utils;
  26 + this.types = types;
  27 + this.entityService = entityService;
  28 + this.stateController = stateController;
  29 + this.entityAliases = angular.copy(entityAliases);
  30 + this.resolvedAliases = {};
  31 + this.resolvedAliasesPromise = {};
  32 + this.resolvedAliasesToStateEntities = {};
  33 + }
  34 +
  35 + updateEntityAliases(newEntityAliases) {
  36 + var changedAliasIds = [];
  37 + for (var aliasId in newEntityAliases) {
  38 + var newEntityAlias = newEntityAliases[aliasId];
  39 + var prevEntityAlias = this.entityAliases[aliasId];
  40 + if (!angular.equals(newEntityAlias, prevEntityAlias)) {
  41 + changedAliasIds.push(aliasId);
  42 + this.setAliasUnresolved(aliasId);
  43 + }
  44 + }
  45 + for (aliasId in this.entityAliases) {
  46 + if (!newEntityAliases[aliasId]) {
  47 + changedAliasIds.push(aliasId);
  48 + this.setAliasUnresolved(aliasId);
  49 + }
  50 + }
  51 + this.entityAliases = angular.copy(newEntityAliases);
  52 + if (changedAliasIds.length) {
  53 + this.$scope.$broadcast('entityAliasesChanged', changedAliasIds);
  54 + }
  55 + }
  56 +
  57 + dashboardStateChanged() {
  58 + var newEntityId = this.stateController.getStateParams().entityId;
  59 + var changedAliasIds = [];
  60 + for (var aliasId in this.resolvedAliasesToStateEntities) {
  61 + var prevEntityId = this.resolvedAliasesToStateEntities[aliasId];
  62 + if (!angular.equals(newEntityId, prevEntityId)) {
  63 + changedAliasIds.push(aliasId);
  64 + this.setAliasUnresolved(aliasId);
  65 + }
  66 + }
  67 + if (changedAliasIds.length) {
  68 + this.$scope.$broadcast('entityAliasesChanged', changedAliasIds);
  69 + }
  70 + }
  71 +
  72 + setAliasUnresolved(aliasId) {
  73 + delete this.resolvedAliases[aliasId];
  74 + delete this.resolvedAliasesPromise[aliasId];
  75 + delete this.resolvedAliasesToStateEntities[aliasId];
  76 + }
  77 +
  78 + getEntityAliases() {
  79 + return this.entityAliases;
  80 + }
  81 +
  82 + getAliasInfo(aliasId) {
  83 + var deferred = this.$q.defer();
  84 + var aliasInfo = this.resolvedAliases[aliasId];
  85 + if (aliasInfo) {
  86 + deferred.resolve(aliasInfo);
  87 + return deferred.promise;
  88 + } else if (this.resolvedAliasesPromise[aliasId]) {
  89 + return this.resolvedAliasesPromise[aliasId];
  90 + } else {
  91 + this.resolvedAliasesPromise[aliasId] = deferred.promise;
  92 + var aliasCtrl = this;
  93 + var entityAlias = this.entityAliases[aliasId];
  94 + if (entityAlias) {
  95 + this.entityService.resolveAlias(entityAlias, this.stateController.getStateParams()).then(
  96 + function success(aliasInfo) {
  97 + aliasCtrl.resolvedAliases[aliasId] = aliasInfo;
  98 + if (aliasInfo.stateEntity) {
  99 + aliasCtrl.resolvedAliasesToStateEntities[aliasId] =
  100 + aliasCtrl.stateController.getStateParams().entityId;
  101 + }
  102 + aliasCtrl.$scope.$broadcast('entityAliasResolved', aliasId);
  103 + deferred.resolve(aliasInfo);
  104 + },
  105 + function fail() {
  106 + deferred.reject();
  107 + }
  108 + );
  109 + } else {
  110 + deferred.reject();
  111 + }
  112 + return this.resolvedAliasesPromise[aliasId];
  113 + }
  114 + }
  115 +
  116 + resolveDatasource(datasource) {
  117 + var deferred = this.$q.defer();
  118 + if (datasource.type === this.types.datasourceType.entity) {
  119 + if (datasource.entityAliasId) {
  120 + this.getAliasInfo(datasource.entityAliasId).then(
  121 + function success(aliasInfo) {
  122 + datasource.aliasName = aliasInfo.alias;
  123 + if (aliasInfo.resolveMultiple) {
  124 + var newDatasource;
  125 + var resolvedEntities = aliasInfo.resolvedEntities;
  126 + if (resolvedEntities && resolvedEntities.length) {
  127 + var datasources = [];
  128 + for (var i=0;i<resolvedEntities.length;i++) {
  129 + var resolvedEntity = resolvedEntities[i];
  130 + newDatasource = angular.copy(datasource);
  131 + newDatasource.entityId = resolvedEntity.id;
  132 + newDatasource.entityType = resolvedEntity.entityType;
  133 + newDatasource.entityName = resolvedEntity.name;
  134 + newDatasource.name = resolvedEntity.name;
  135 + newDatasource.generated = i > 0 ? true : false;
  136 + datasources.push(newDatasource);
  137 + }
  138 + deferred.resolve(datasources);
  139 + } else {
  140 + if (aliasInfo.stateEntity) {
  141 + newDatasource = angular.copy(datasource);
  142 + newDatasource.unresolvedStateEntity = true;
  143 + deferred.resolve([newDatasource]);
  144 + } else {
  145 + deferred.reject();
  146 + }
  147 + }
  148 + } else {
  149 + var entity = aliasInfo.currentEntity;
  150 + if (entity) {
  151 + datasource.entityId = entity.id;
  152 + datasource.entityType = entity.entityType;
  153 + datasource.entityName = entity.name;
  154 + datasource.name = entity.name;
  155 + deferred.resolve([datasource]);
  156 + } else {
  157 + if (aliasInfo.stateEntity) {
  158 + datasource.unresolvedStateEntity = true;
  159 + deferred.resolve([datasource]);
  160 + } else {
  161 + deferred.reject();
  162 + }
  163 + }
  164 + }
  165 + },
  166 + function fail() {
  167 + deferred.reject();
  168 + }
  169 + );
  170 + } else { // entityId
  171 + datasource.aliasName = datasource.entityName;
  172 + datasource.name = datasource.entityName;
  173 + deferred.resolve([datasource]);
  174 + }
  175 + } else { // function
  176 + deferred.resolve([datasource]);
  177 + }
  178 + return deferred.promise;
  179 + }
  180 +
  181 + resolveDatasources(datasources) {
  182 +
  183 + function updateDataKeyLabel(dataKey, datasource) {
  184 + if (!dataKey.pattern) {
  185 + dataKey.pattern = angular.copy(dataKey.label);
  186 + }
  187 + var pattern = dataKey.pattern;
  188 + var label = dataKey.pattern;
  189 + var match = varsRegex.exec(pattern);
  190 + while (match !== null) {
  191 + var variable = match[0];
  192 + var variableName = match[1];
  193 + if (variableName === 'dsName') {
  194 + label = label.split(variable).join(datasource.name);
  195 + } else if (variableName === 'entityName') {
  196 + label = label.split(variable).join(datasource.entityName);
  197 + } else if (variableName === 'deviceName') {
  198 + label = label.split(variable).join(datasource.entityName);
  199 + } else if (variableName === 'aliasName') {
  200 + label = label.split(variable).join(datasource.aliasName);
  201 + }
  202 + match = varsRegex.exec(pattern);
  203 + }
  204 + dataKey.label = label;
  205 + }
  206 +
  207 + function updateDatasourceKeyLabels(datasource) {
  208 + for (var dk = 0; dk < datasource.dataKeys.length; dk++) {
  209 + updateDataKeyLabel(datasource.dataKeys[dk], datasource);
  210 + }
  211 + }
  212 +
  213 + var deferred = this.$q.defer();
  214 + var newDatasources = angular.copy(datasources);
  215 + var datasorceResolveTasks = [];
  216 + var aliasCtrl = this;
  217 + newDatasources.forEach(function (datasource) {
  218 + var resolveDatasourceTask = aliasCtrl.resolveDatasource(datasource);
  219 + datasorceResolveTasks.push(resolveDatasourceTask);
  220 + });
  221 + this.$q.all(datasorceResolveTasks).then(
  222 + function success(datasourcesArrays) {
  223 + var datasources = [].concat.apply([], datasourcesArrays);
  224 + datasources = aliasCtrl.$filter('orderBy')(datasources, '+generated');
  225 + var index = 0;
  226 + var functionIndex = 0;
  227 + datasources.forEach(function(datasource) {
  228 + if (datasource.type === aliasCtrl.types.datasourceType.function) {
  229 + var name;
  230 + if (datasource.name && datasource.name.length) {
  231 + name = datasource.name;
  232 + } else {
  233 + functionIndex++;
  234 + name = aliasCtrl.types.datasourceType.function;
  235 + if (functionIndex > 1) {
  236 + name += ' ' + functionIndex;
  237 + }
  238 + }
  239 + datasource.name = name;
  240 + datasource.aliasName = name;
  241 + datasource.entityName = name;
  242 + } else if (datasource.unresolvedStateEntity) {
  243 + datasource.name = "Unresolved";
  244 + datasource.entityName = "Unresolved";
  245 + }
  246 + datasource.dataKeys.forEach(function(dataKey) {
  247 + if (datasource.generated) {
  248 + dataKey._hash = Math.random();
  249 + dataKey.color = aliasCtrl.utils.getMaterialColor(index);
  250 + }
  251 + index++;
  252 + });
  253 + updateDatasourceKeyLabels(datasource);
  254 + });
  255 + deferred.resolve(datasources);
  256 + },
  257 + function fail() {
  258 + deferred.reject();
  259 + }
  260 + );
  261 + return deferred.promise;
  262 + }
  263 +
  264 + getInstantAliasInfo(aliasId) {
  265 + return this.resolvedAliases[aliasId];
  266 + }
  267 +
  268 + updateCurrentAliasEntity(aliasId, currentEntity) {
  269 + var aliasInfo = this.resolvedAliases[aliasId];
  270 + if (aliasInfo) {
  271 + var prevCurrentEntity = aliasInfo.currentEntity;
  272 + if (!angular.equals(currentEntity, prevCurrentEntity)) {
  273 + aliasInfo.currentEntity = currentEntity;
  274 + this.$scope.$broadcast('entityAliasesChanged', [aliasId]);
  275 + }
  276 + }
  277 + }
  278 +
  279 +}
@@ -30,7 +30,8 @@ function EntityRelationService($http, $q) { @@ -30,7 +30,8 @@ function EntityRelationService($http, $q) {
30 findByTo: findByTo, 30 findByTo: findByTo,
31 findInfoByTo: findInfoByTo, 31 findInfoByTo: findInfoByTo,
32 findByToAndType: findByToAndType, 32 findByToAndType: findByToAndType,
33 - findByQuery: findByQuery 33 + findByQuery: findByQuery,
  34 + findInfoByQuery: findInfoByQuery
34 } 35 }
35 36
36 return service; 37 return service;
@@ -159,4 +160,15 @@ function EntityRelationService($http, $q) { @@ -159,4 +160,15 @@ function EntityRelationService($http, $q) {
159 return deferred.promise; 160 return deferred.promise;
160 } 161 }
161 162
  163 + function findInfoByQuery(query) {
  164 + var deferred = $q.defer();
  165 + var url = '/api/relations/info';
  166 + $http.post(url, query).then(function success(response) {
  167 + deferred.resolve(response.data);
  168 + }, function fail() {
  169 + deferred.reject();
  170 + });
  171 + return deferred.promise;
  172 + }
  173 +
162 } 174 }
@@ -27,10 +27,14 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -27,10 +27,14 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
27 getEntity: getEntity, 27 getEntity: getEntity,
28 getEntities: getEntities, 28 getEntities: getEntities,
29 getEntitiesByNameFilter: getEntitiesByNameFilter, 29 getEntitiesByNameFilter: getEntitiesByNameFilter,
30 - processEntityAliases: processEntityAliases,  
31 - getEntityKeys: getEntityKeys, 30 + resolveAlias: resolveAlias,
  31 + resolveAliasFilter: resolveAliasFilter,
32 checkEntityAlias: checkEntityAlias, 32 checkEntityAlias: checkEntityAlias,
33 - createDatasoucesFromSubscriptionsInfo: createDatasoucesFromSubscriptionsInfo, 33 + filterAliasByEntityTypes: filterAliasByEntityTypes,
  34 + getAliasFilterTypesByEntityTypes: getAliasFilterTypesByEntityTypes,
  35 + prepareAllowedEntityTypesList: prepareAllowedEntityTypesList,
  36 + getEntityKeys: getEntityKeys,
  37 + createDatasourcesFromSubscriptionsInfo: createDatasourcesFromSubscriptionsInfo,
34 getRelatedEntities: getRelatedEntities, 38 getRelatedEntities: getRelatedEntities,
35 saveRelatedEntity: saveRelatedEntity, 39 saveRelatedEntity: saveRelatedEntity,
36 getRelatedEntity: getRelatedEntity, 40 getRelatedEntity: getRelatedEntity,
@@ -173,6 +177,54 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -173,6 +177,54 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
173 return deferred.promise; 177 return deferred.promise;
174 } 178 }
175 179
  180 + function getSingleTenantByPageLinkPromise(pageLink) {
  181 + var user = userService.getCurrentUser();
  182 + var tenantId = user.tenantId;
  183 + var deferred = $q.defer();
  184 + tenantService.getTenant(tenantId).then(
  185 + function success(tenant) {
  186 + var tenantName = tenant.name;
  187 + var result = {
  188 + data: [],
  189 + nextPageLink: pageLink,
  190 + hasNext: false
  191 + };
  192 + if (tenantName.toLowerCase().startsWith(pageLink.textSearch)) {
  193 + result.data.push(tenant);
  194 + }
  195 + deferred.resolve(result);
  196 + },
  197 + function fail() {
  198 + deferred.reject();
  199 + }
  200 + );
  201 + return deferred.promise;
  202 + }
  203 +
  204 + function getSingleCustomerByPageLinkPromise(pageLink) {
  205 + var user = userService.getCurrentUser();
  206 + var customerId = user.customerId;
  207 + var deferred = $q.defer();
  208 + customerService.getCustomer(customerId).then(
  209 + function success(customer) {
  210 + var customerName = customer.name;
  211 + var result = {
  212 + data: [],
  213 + nextPageLink: pageLink,
  214 + hasNext: false
  215 + };
  216 + if (customerName.toLowerCase().startsWith(pageLink.textSearch)) {
  217 + result.data.push(customer);
  218 + }
  219 + deferred.resolve(result);
  220 + },
  221 + function fail() {
  222 + deferred.reject();
  223 + }
  224 + );
  225 + return deferred.promise;
  226 + }
  227 +
176 function getEntitiesByPageLinkPromise(entityType, pageLink, config, subType) { 228 function getEntitiesByPageLinkPromise(entityType, pageLink, config, subType) {
177 var promise; 229 var promise;
178 var user = userService.getCurrentUser(); 230 var user = userService.getCurrentUser();
@@ -193,10 +245,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -193,10 +245,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
193 } 245 }
194 break; 246 break;
195 case types.entityType.tenant: 247 case types.entityType.tenant:
196 - promise = tenantService.getTenants(pageLink); 248 + if (user.authority === 'TENANT_ADMIN') {
  249 + promise = getSingleTenantByPageLinkPromise(pageLink);
  250 + } else {
  251 + promise = tenantService.getTenants(pageLink);
  252 + }
197 break; 253 break;
198 case types.entityType.customer: 254 case types.entityType.customer:
199 - promise = customerService.getCustomers(pageLink); 255 + if (user.authority === 'CUSTOMER_USER') {
  256 + promise = getSingleCustomerByPageLinkPromise(pageLink);
  257 + } else {
  258 + promise = customerService.getCustomers(pageLink);
  259 + }
200 break; 260 break;
201 case types.entityType.rule: 261 case types.entityType.rule:
202 promise = ruleService.getAllRules(pageLink); 262 promise = ruleService.getAllRules(pageLink);
@@ -221,17 +281,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -221,17 +281,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
221 return promise; 281 return promise;
222 } 282 }
223 283
224 - function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config, subType) {  
225 - var deferred = $q.defer();  
226 - var pageLink = {limit: limit, textSearch: entityNameFilter}; 284 + function getEntitiesByPageLink(entityType, pageLink, config, subType, data, deferred) {
227 var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config, subType); 285 var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config, subType);
228 if (promise) { 286 if (promise) {
229 promise.then( 287 promise.then(
230 function success(result) { 288 function success(result) {
231 - if (result.data && result.data.length > 0) {  
232 - deferred.resolve(result.data); 289 + data = data.concat(result.data);
  290 + if (result.hasNext) {
  291 + pageLink = result.nextPageLink;
  292 + getEntitiesByPageLink(entityType, pageLink, config, subType, data, deferred);
233 } else { 293 } else {
234 - deferred.resolve(null); 294 + if (data && data.length > 0) {
  295 + deferred.resolve(data);
  296 + } else {
  297 + deferred.resolve(null);
  298 + }
235 } 299 }
236 }, 300 },
237 function fail() { 301 function fail() {
@@ -241,92 +305,418 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -241,92 +305,418 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
241 } else { 305 } else {
242 deferred.resolve(null); 306 deferred.resolve(null);
243 } 307 }
  308 + }
  309 +
  310 + function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config, subType) {
  311 + var deferred = $q.defer();
  312 + var pageLink = {limit: limit, textSearch: entityNameFilter};
  313 + if (limit == -1) { // all
  314 + var data = [];
  315 + pageLink.limit = 100;
  316 + getEntitiesByPageLink(entityType, pageLink, config, subType, data, deferred);
  317 + } else {
  318 + var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config, subType);
  319 + if (promise) {
  320 + promise.then(
  321 + function success(result) {
  322 + if (result.data && result.data.length > 0) {
  323 + deferred.resolve(result.data);
  324 + } else {
  325 + deferred.resolve(null);
  326 + }
  327 + },
  328 + function fail() {
  329 + deferred.resolve(null);
  330 + }
  331 + );
  332 + } else {
  333 + deferred.resolve(null);
  334 + }
  335 + }
244 return deferred.promise; 336 return deferred.promise;
245 } 337 }
246 338
247 - function entityToEntityInfo(entityType, entity) {  
248 - return { name: entity.name, entityType: entityType, id: entity.id.id }; 339 + function entityToEntityInfo(entity) {
  340 + return { name: entity.name, entityType: entity.id.entityType, id: entity.id.id };
249 } 341 }
250 342
251 - function entitiesToEntitiesInfo(entityType, entities) { 343 + function entityRelationInfoToEntityInfo(entityRelationInfo, direction) {
  344 + var entityId = direction == types.entitySearchDirection.from ? entityRelationInfo.to : entityRelationInfo.from;
  345 + var name = direction == types.entitySearchDirection.from ? entityRelationInfo.toName : entityRelationInfo.fromName;
  346 + return {
  347 + name: name,
  348 + entityType: entityId.entityType,
  349 + id: entityId.id
  350 + };
  351 + }
  352 +
  353 + function entitiesToEntitiesInfo(entities) {
252 var entitiesInfo = []; 354 var entitiesInfo = [];
253 for (var d = 0; d < entities.length; d++) { 355 for (var d = 0; d < entities.length; d++) {
254 - entitiesInfo.push(entityToEntityInfo(entityType, entities[d])); 356 + entitiesInfo.push(entityToEntityInfo(entities[d]));
255 } 357 }
256 return entitiesInfo; 358 return entitiesInfo;
257 } 359 }
258 360
259 - function processEntityAlias(index, aliasIds, entityAliases, resolution, deferred) {  
260 - if (index < aliasIds.length) {  
261 - var aliasId = aliasIds[index];  
262 - var entityAlias = entityAliases[aliasId];  
263 - var alias = entityAlias.alias;  
264 - var entityFilter = entityAlias.entityFilter;  
265 - if (entityFilter.useFilter) {  
266 - var entityNameFilter = entityFilter.entityNameFilter;  
267 - getEntitiesByNameFilter(entityAlias.entityType, entityNameFilter, 100).then(  
268 - function(entities) {  
269 - if (entities && entities != null) {  
270 - var resolvedAlias = {alias: alias, entityType: entityAlias.entityType, entityId: entities[0].id.id};  
271 - resolution.aliasesInfo.entityAliases[aliasId] = resolvedAlias;  
272 - resolution.aliasesInfo.entityAliasesInfo[aliasId] = entitiesToEntitiesInfo(entityAlias.entityType, entities);  
273 - index++;  
274 - processEntityAlias(index, aliasIds, entityAliases, resolution, deferred); 361 + function entityRelationInfosToEntitiesInfo(entityRelations, direction) {
  362 + var entitiesInfo = [];
  363 + for (var d = 0; d < entityRelations.length; d++) {
  364 + entitiesInfo.push(entityRelationInfoToEntityInfo(entityRelations[d], direction));
  365 + }
  366 + return entitiesInfo;
  367 + }
  368 +
  369 +
  370 + function resolveAlias(entityAlias, stateParams) {
  371 + var deferred = $q.defer();
  372 + var filter = entityAlias.filter;
  373 + resolveAliasFilter(filter, stateParams, -1).then(
  374 + function (result) {
  375 + var aliasInfo = {
  376 + alias: entityAlias.alias,
  377 + stateEntity: result.stateEntity,
  378 + resolveMultiple: filter.resolveMultiple
  379 + };
  380 + aliasInfo.resolvedEntities = result.entities;
  381 + aliasInfo.currentEntity = null;
  382 + if (aliasInfo.resolvedEntities.length) {
  383 + aliasInfo.currentEntity = aliasInfo.resolvedEntities[0];
  384 + }
  385 + deferred.resolve(aliasInfo);
  386 + },
  387 + function fail() {
  388 + deferred.reject();
  389 + }
  390 + );
  391 + return deferred.promise;
  392 + }
  393 +
  394 + function resolveAliasFilter(filter, stateParams, maxItems) {
  395 + var deferred = $q.defer();
  396 + var result = {
  397 + entities: [],
  398 + stateEntity: false
  399 + };
  400 + switch (filter.type) {
  401 + case types.aliasFilterType.entityList.value:
  402 + getEntities(filter.entityType, filter.entityList).then(
  403 + function success(entities) {
  404 + if (entities && entities.length) {
  405 + result.entities = entitiesToEntitiesInfo(entities);
  406 + deferred.resolve(result);
275 } else { 407 } else {
276 - if (!resolution.error) {  
277 - resolution.error = 'dashboard.invalid-aliases-config';  
278 - }  
279 - index++;  
280 - processEntityAlias(index, aliasIds, entityAliases, resolution, deferred); 408 + deferred.reject();
281 } 409 }
282 - });  
283 - } else {  
284 - var entityList = entityFilter.entityList;  
285 - getEntities(entityAlias.entityType, entityList).then( 410 + },
  411 + function fail() {
  412 + deferred.reject();
  413 + }
  414 + );
  415 + break;
  416 + case types.aliasFilterType.entityName.value:
  417 + getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems).then(
286 function success(entities) { 418 function success(entities) {
287 - if (entities && entities.length > 0) {  
288 - var resolvedAlias = {alias: alias, entityType: entityAlias.entityType, entityId: entities[0].id.id};  
289 - resolution.aliasesInfo.entityAliases[aliasId] = resolvedAlias;  
290 - resolution.aliasesInfo.entityAliasesInfo[aliasId] = entitiesToEntitiesInfo(entityAlias.entityType, entities);  
291 - index++;  
292 - processEntityAlias(index, aliasIds, entityAliases, resolution, deferred); 419 + if (entities && entities.length) {
  420 + result.entities = entitiesToEntitiesInfo(entities);
  421 + deferred.resolve(result);
293 } else { 422 } else {
294 - if (!resolution.error) {  
295 - resolution.error = 'dashboard.invalid-aliases-config';  
296 - }  
297 - index++;  
298 - processEntityAlias(index, aliasIds, entityAliases, resolution, deferred); 423 + deferred.reject();
  424 + }
  425 + },
  426 + function fail() {
  427 + deferred.reject();
  428 + }
  429 + );
  430 + break;
  431 + case types.aliasFilterType.stateEntity.value:
  432 + result.stateEntity = true;
  433 + if (stateParams && stateParams.entityId) {
  434 + getEntity(stateParams.entityId.entityType, stateParams.entityId.id).then(
  435 + function success(entity) {
  436 + result.entities = entitiesToEntitiesInfo([entity]);
  437 + deferred.resolve(result);
  438 + },
  439 + function fail() {
  440 + deferred.reject();
  441 + }
  442 + );
  443 + } else {
  444 + deferred.resolve(result);
  445 + }
  446 + break;
  447 + case types.aliasFilterType.assetType.value:
  448 + getEntitiesByNameFilter(types.entityType.asset, filter.assetNameFilter, maxItems, null, filter.assetType).then(
  449 + function success(entities) {
  450 + if (entities && entities.length) {
  451 + result.entities = entitiesToEntitiesInfo(entities);
  452 + deferred.resolve(result);
  453 + } else {
  454 + deferred.reject();
299 } 455 }
300 }, 456 },
301 function fail() { 457 function fail() {
302 - if (!resolution.error) {  
303 - resolution.error = 'dashboard.invalid-aliases-config'; 458 + deferred.reject();
  459 + }
  460 + );
  461 + break;
  462 + case types.aliasFilterType.deviceType.value:
  463 + getEntitiesByNameFilter(types.entityType.device, filter.deviceNameFilter, maxItems, null, filter.deviceType).then(
  464 + function success(entities) {
  465 + if (entities && entities.length) {
  466 + result.entities = entitiesToEntitiesInfo(entities);
  467 + deferred.resolve(result);
  468 + } else {
  469 + deferred.reject();
304 } 470 }
305 - index++;  
306 - processEntityAlias(index, aliasIds, entityAliases, resolution, deferred); 471 + },
  472 + function fail() {
  473 + deferred.reject();
307 } 474 }
308 ); 475 );
  476 + break;
  477 + case types.aliasFilterType.relationsQuery.value:
  478 + result.stateEntity = filter.rootStateEntity;
  479 + var rootEntityType;
  480 + var rootEntityId;
  481 + if (result.stateEntity && stateParams && stateParams.entityId) {
  482 + rootEntityType = stateParams.entityId.entityType;
  483 + rootEntityId = stateParams.entityId.id;
  484 + } else if (!result.stateEntity) {
  485 + rootEntityType = filter.rootEntity.entityType;
  486 + rootEntityId = filter.rootEntity.id;
  487 + }
  488 + if (rootEntityType && rootEntityId) {
  489 + var searchQuery = {
  490 + parameters: {
  491 + rootId: rootEntityId,
  492 + rootType: rootEntityType,
  493 + direction: filter.direction
  494 + },
  495 + filters: filter.filters
  496 + };
  497 + searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1;
  498 + entityRelationService.findInfoByQuery(searchQuery).then(
  499 + function success(allRelations) {
  500 + if (allRelations && allRelations.length) {
  501 + if (angular.isDefined(maxItems) && maxItems > 0) {
  502 + var limit = Math.min(allRelations.length, maxItems);
  503 + allRelations.length = limit;
  504 + }
  505 + result.entities = entityRelationInfosToEntitiesInfo(allRelations, filter.direction);
  506 + deferred.resolve(result);
  507 + } else {
  508 + deferred.reject();
  509 + }
  510 + },
  511 + function fail() {
  512 + deferred.reject();
  513 + }
  514 + );
  515 + } else {
  516 + deferred.resolve(result);
  517 + }
  518 + break;
  519 + case types.aliasFilterType.assetSearchQuery.value:
  520 + case types.aliasFilterType.deviceSearchQuery.value:
  521 + result.stateEntity = filter.rootStateEntity;
  522 + if (result.stateEntity && stateParams && stateParams.entityId) {
  523 + rootEntityType = stateParams.entityId.entityType;
  524 + rootEntityId = stateParams.entityId.id;
  525 + } else if (!result.stateEntity) {
  526 + rootEntityType = filter.rootEntity.entityType;
  527 + rootEntityId = filter.rootEntity.id;
  528 + }
  529 + if (rootEntityType && rootEntityId) {
  530 + searchQuery = {
  531 + parameters: {
  532 + rootId: rootEntityId,
  533 + rootType: rootEntityType,
  534 + direction: filter.direction
  535 + },
  536 + relationType: filter.relationType
  537 + };
  538 + searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1;
  539 + var findByQueryPromise;
  540 + if (filter.type == types.aliasFilterType.assetSearchQuery.value) {
  541 + searchQuery.assetTypes = filter.assetTypes;
  542 + findByQueryPromise = assetService.findByQuery(searchQuery, false);
  543 + } else if (filter.type == types.aliasFilterType.deviceSearchQuery.value) {
  544 + searchQuery.deviceTypes = filter.deviceTypes;
  545 + findByQueryPromise = deviceService.findByQuery(searchQuery, false);
  546 + }
  547 + findByQueryPromise.then(
  548 + function success(entities) {
  549 + if (entities && entities.length) {
  550 + if (angular.isDefined(maxItems) && maxItems > 0) {
  551 + var limit = Math.min(entities.length, maxItems);
  552 + entities.length = limit;
  553 + }
  554 + result.entities = entitiesToEntitiesInfo(entities);
  555 + deferred.resolve(result);
  556 + } else {
  557 + deferred.reject();
  558 + }
  559 + },
  560 + function fail() {
  561 + deferred.reject();
  562 + }
  563 + );
  564 + } else {
  565 + deferred.resolve(result);
  566 + }
  567 + break;
  568 + }
  569 + return deferred.promise;
  570 + }
  571 +
  572 + function filterAliasByEntityTypes(entityAlias, entityTypes) {
  573 + var filter = entityAlias.filter;
  574 + if (filterAliasFilterTypeByEntityTypes(filter.type, entityTypes)) {
  575 + switch (filter.type) {
  576 + case types.aliasFilterType.entityList.value:
  577 + return entityTypes.indexOf(filter.entityType) > -1 ? true : false;
  578 + case types.aliasFilterType.entityName.value:
  579 + return entityTypes.indexOf(filter.entityType) > -1 ? true : false;
  580 + case types.aliasFilterType.stateEntity.value:
  581 + return true;
  582 + case types.aliasFilterType.assetType.value:
  583 + return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false;
  584 + case types.aliasFilterType.deviceType.value:
  585 + return entityTypes.indexOf(types.entityType.device) > -1 ? true : false;
  586 + case types.aliasFilterType.relationsQuery.value:
  587 + if (filter.filters && filter.filters.length) {
  588 + var match = false;
  589 + for (var f=0;f<filter.filters.length;f++) {
  590 + var relationFilter = filter.filters[f];
  591 + if (relationFilter.entityTypes && relationFilter.entityTypes.length) {
  592 + for (var et=0;et<relationFilter.entityTypes.length;et++) {
  593 + if (entityTypes.indexOf(relationFilter.entityTypes[et]) > -1) {
  594 + match = true;
  595 + break;
  596 + }
  597 + }
  598 + } else {
  599 + match = true;
  600 + break;
  601 + }
  602 + }
  603 + return match;
  604 + } else {
  605 + return true;
  606 + }
  607 + case types.aliasFilterType.assetSearchQuery.value:
  608 + return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false;
  609 + case types.aliasFilterType.deviceSearchQuery.value:
  610 + return entityTypes.indexOf(types.entityType.device) > -1 ? true : false;
309 } 611 }
310 - } else {  
311 - deferred.resolve(resolution);  
312 } 612 }
  613 + return false;
313 } 614 }
314 615
315 - function processEntityAliases(entityAliases) {  
316 - var deferred = $q.defer();  
317 - var resolution = {  
318 - aliasesInfo: {  
319 - entityAliases: {},  
320 - entityAliasesInfo: {} 616 + function filterAliasFilterTypeByEntityType(aliasFilterType, entityType) {
  617 + switch (aliasFilterType) {
  618 + case types.aliasFilterType.entityList.value:
  619 + return true;
  620 + case types.aliasFilterType.entityName.value:
  621 + return true;
  622 + case types.aliasFilterType.stateEntity.value:
  623 + return true;
  624 + case types.aliasFilterType.assetType.value:
  625 + return entityType === types.entityType.asset;
  626 + case types.aliasFilterType.deviceType.value:
  627 + return entityType === types.entityType.device;
  628 + case types.aliasFilterType.relationsQuery.value:
  629 + return true;
  630 + case types.aliasFilterType.assetSearchQuery.value:
  631 + return entityType === types.entityType.asset;
  632 + case types.aliasFilterType.deviceSearchQuery.value:
  633 + return entityType === types.entityType.device;
  634 + }
  635 + return false;
  636 + }
  637 +
  638 + function filterAliasFilterTypeByEntityTypes(aliasFilterType, entityTypes) {
  639 + if (!entityTypes || !entityTypes.length) {
  640 + return true;
  641 + }
  642 + var valid = false;
  643 + entityTypes.forEach(function(entityType) {
  644 + valid = valid || filterAliasFilterTypeByEntityType(aliasFilterType, entityType);
  645 + });
  646 + return valid;
  647 + }
  648 +
  649 + function getAliasFilterTypesByEntityTypes(entityTypes) {
  650 + var allAliasFilterTypes = types.aliasFilterType;
  651 + if (!entityTypes || !entityTypes.length) {
  652 + return allAliasFilterTypes;
  653 + }
  654 + var result = {};
  655 + for (var type in allAliasFilterTypes) {
  656 + var aliasFilterType = allAliasFilterTypes[type];
  657 + if (filterAliasFilterTypeByEntityTypes(aliasFilterType.value, entityTypes)) {
  658 + result[type] = aliasFilterType;
321 } 659 }
322 - };  
323 - var aliasIds = [];  
324 - if (entityAliases) {  
325 - for (var aliasId in entityAliases) {  
326 - aliasIds.push(aliasId); 660 + }
  661 + return result;
  662 + }
  663 +
  664 + function prepareAllowedEntityTypesList(allowedEntityTypes) {
  665 + var authority = userService.getAuthority();
  666 + var entityTypes = {};
  667 + switch(authority) {
  668 + case 'SYS_ADMIN':
  669 + entityTypes.tenant = types.entityType.tenant;
  670 + entityTypes.rule = types.entityType.rule;
  671 + entityTypes.plugin = types.entityType.plugin;
  672 + break;
  673 + case 'TENANT_ADMIN':
  674 + entityTypes.device = types.entityType.device;
  675 + entityTypes.asset = types.entityType.asset;
  676 + entityTypes.tenant = types.entityType.tenant;
  677 + entityTypes.customer = types.entityType.customer;
  678 + entityTypes.rule = types.entityType.rule;
  679 + entityTypes.plugin = types.entityType.plugin;
  680 + entityTypes.dashboard = types.entityType.dashboard;
  681 + break;
  682 + case 'CUSTOMER_USER':
  683 + entityTypes.device = types.entityType.device;
  684 + entityTypes.asset = types.entityType.asset;
  685 + entityTypes.customer = types.entityType.customer;
  686 + entityTypes.dashboard = types.entityType.dashboard;
  687 + break;
  688 + }
  689 +
  690 + if (allowedEntityTypes) {
  691 + for (var entityType in entityTypes) {
  692 + if (allowedEntityTypes.indexOf(entityTypes[entityType]) === -1) {
  693 + delete entityTypes[entityType];
  694 + }
327 } 695 }
328 } 696 }
329 - processEntityAlias(0, aliasIds, entityAliases, resolution, deferred); 697 + return entityTypes;
  698 + }
  699 +
  700 +
  701 + function checkEntityAlias(entityAlias) {
  702 + var deferred = $q.defer();
  703 + resolveAliasFilter(entityAlias.filter, null, 1).then(
  704 + function success(result) {
  705 + if (result.stateEntity) {
  706 + deferred.resolve(true);
  707 + } else {
  708 + var entities = result.entities;
  709 + if (entities && entities.length) {
  710 + deferred.resolve(true);
  711 + } else {
  712 + deferred.resolve(false);
  713 + }
  714 + }
  715 + },
  716 + function fail() {
  717 + deferred.resolve(false);
  718 + }
  719 + );
330 return deferred.promise; 720 return deferred.promise;
331 } 721 }
332 722
@@ -354,40 +744,13 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -354,40 +744,13 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
354 } 744 }
355 } 745 }
356 deferred.resolve(result); 746 deferred.resolve(result);
357 - }, function fail(response) {  
358 - deferred.reject(response.data); 747 + }, function fail() {
  748 + deferred.reject();
359 }); 749 });
360 return deferred.promise; 750 return deferred.promise;
361 } 751 }
362 752
363 - function checkEntityAlias(entityAlias) {  
364 - var deferred = $q.defer();  
365 - var entityType = entityAlias.entityType;  
366 - var entityFilter = entityAlias.entityFilter;  
367 - var promise;  
368 - if (entityFilter.useFilter) {  
369 - var entityNameFilter = entityFilter.entityNameFilter;  
370 - promise = getEntitiesByNameFilter(entityType, entityNameFilter, 1);  
371 - } else {  
372 - var entityList = entityFilter.entityList;  
373 - promise = getEntities(entityType, entityList);  
374 - }  
375 - promise.then(  
376 - function success(entities) {  
377 - if (entities && entities.length > 0) {  
378 - deferred.resolve(true);  
379 - } else {  
380 - deferred.resolve(false);  
381 - }  
382 - },  
383 - function fail() {  
384 - deferred.resolve(false);  
385 - }  
386 - );  
387 - return deferred.promise;  
388 - }  
389 -  
390 - function createDatasoucesFromSubscriptionsInfo(subscriptionsInfo) { 753 + function createDatasourcesFromSubscriptionsInfo(subscriptionsInfo) {
391 var deferred = $q.defer(); 754 var deferred = $q.defer();
392 var datasources = []; 755 var datasources = [];
393 processSubscriptionsInfo(0, subscriptionsInfo, datasources, deferred); 756 processSubscriptionsInfo(0, subscriptionsInfo, datasources, deferred);
@@ -822,4 +1185,4 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -822,4 +1185,4 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
822 } 1185 }
823 } 1186 }
824 1187
825 -}  
  1188 +}
@@ -39,6 +39,9 @@ export default class Subscription { @@ -39,6 +39,9 @@ export default class Subscription {
39 this.cafs = {}; 39 this.cafs = {};
40 this.registrations = []; 40 this.registrations = [];
41 41
  42 + var subscription = this;
  43 + var deferred = this.ctx.$q.defer();
  44 +
42 if (this.type === this.ctx.types.widgetType.rpc.value) { 45 if (this.type === this.ctx.types.widgetType.rpc.value) {
43 this.callbacks.rpcStateChanged = this.callbacks.rpcStateChanged || function(){}; 46 this.callbacks.rpcStateChanged = this.callbacks.rpcStateChanged || function(){};
44 this.callbacks.onRpcSuccess = this.callbacks.onRpcSuccess || function(){}; 47 this.callbacks.onRpcSuccess = this.callbacks.onRpcSuccess || function(){};
@@ -56,7 +59,11 @@ export default class Subscription { @@ -56,7 +59,11 @@ export default class Subscription {
56 this.rpcEnabled = false; 59 this.rpcEnabled = false;
57 this.executingRpcRequest = false; 60 this.executingRpcRequest = false;
58 this.executingPromises = []; 61 this.executingPromises = [];
59 - this.initRpc(); 62 + this.initRpc().then(
  63 + function() {
  64 + deferred.resolve(subscription);
  65 + }
  66 + );
60 } else { 67 } else {
61 this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || function(){}; 68 this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || function(){};
62 this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || function(){}; 69 this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || function(){};
@@ -66,6 +73,15 @@ export default class Subscription { @@ -66,6 +73,15 @@ export default class Subscription {
66 73
67 this.datasources = this.ctx.utils.validateDatasources(options.datasources); 74 this.datasources = this.ctx.utils.validateDatasources(options.datasources);
68 this.datasourceListeners = []; 75 this.datasourceListeners = [];
  76 +
  77 + /*
  78 + * data = array of datasourceData
  79 + * datasourceData = {
  80 + * tbDatasource,
  81 + * dataKey, { name, config }
  82 + * data = array of [time, value]
  83 + * }
  84 + */
69 this.data = []; 85 this.data = [];
70 this.hiddenData = []; 86 this.hiddenData = [];
71 this.originalTimewindow = null; 87 this.originalTimewindow = null;
@@ -103,11 +119,41 @@ export default class Subscription { @@ -103,11 +119,41 @@ export default class Subscription {
103 this.legendConfig.showMax === true || 119 this.legendConfig.showMax === true ||
104 this.legendConfig.showAvg === true || 120 this.legendConfig.showAvg === true ||
105 this.legendConfig.showTotal === true); 121 this.legendConfig.showTotal === true);
106 - this.initDataSubscription(); 122 + this.initDataSubscription().then(
  123 + function success() {
  124 + deferred.resolve(subscription);
  125 + },
  126 + function fail() {
  127 + deferred.reject();
  128 + }
  129 + );
107 } 130 }
  131 +
  132 + return deferred.promise;
108 } 133 }
109 134
110 initDataSubscription() { 135 initDataSubscription() {
  136 + var deferred = this.ctx.$q.defer();
  137 + if (!this.ctx.aliasController) {
  138 + this.configureData();
  139 + deferred.resolve();
  140 + } else {
  141 + var subscription = this;
  142 + this.ctx.aliasController.resolveDatasources(this.datasources).then(
  143 + function success(datasources) {
  144 + subscription.datasources = datasources;
  145 + subscription.configureData();
  146 + deferred.resolve();
  147 + },
  148 + function fail() {
  149 + deferred.reject();
  150 + }
  151 + );
  152 + }
  153 + return deferred.promise;
  154 + }
  155 +
  156 + configureData() {
111 var dataIndex = 0; 157 var dataIndex = 0;
112 for (var i = 0; i < this.datasources.length; i++) { 158 for (var i = 0; i < this.datasources.length; i++) {
113 var datasource = this.datasources[i]; 159 var datasource = this.datasources[i];
@@ -199,21 +245,46 @@ export default class Subscription { @@ -199,21 +245,46 @@ export default class Subscription {
199 } 245 }
200 246
201 initRpc() { 247 initRpc() {
  248 + var deferred = this.ctx.$q.defer();
202 if (this.targetDeviceAliasIds && this.targetDeviceAliasIds.length > 0) { 249 if (this.targetDeviceAliasIds && this.targetDeviceAliasIds.length > 0) {
203 this.targetDeviceAliasId = this.targetDeviceAliasIds[0]; 250 this.targetDeviceAliasId = this.targetDeviceAliasIds[0];
204 - if (this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId]) {  
205 - this.targetDeviceId = this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId].entityId; 251 + var subscription = this;
  252 + this.ctx.aliasController.getAliasInfo(this.targetDeviceAliasId).then(
  253 + function success(aliasInfo) {
  254 + if (aliasInfo.currentEntity && aliasInfo.currentEntity.entityType == subscription.ctx.types.entityType.device) {
  255 + subscription.targetDeviceId = aliasInfo.currentEntity.id;
  256 + if (subscription.targetDeviceId) {
  257 + subscription.rpcEnabled = true;
  258 + } else {
  259 + subscription.rpcEnabled = subscription.ctx.$scope.widgetEditMode ? true : false;
  260 + }
  261 + subscription.callbacks.rpcStateChanged(subscription);
  262 + deferred.resolve();
  263 + } else {
  264 + subscription.rpcEnabled = false;
  265 + subscription.callbacks.rpcStateChanged(subscription);
  266 + deferred.resolve();
  267 + }
  268 + },
  269 + function fail () {
  270 + subscription.rpcEnabled = false;
  271 + subscription.callbacks.rpcStateChanged(subscription);
  272 + deferred.resolve();
  273 + }
  274 + );
  275 + } else {
  276 + if (this.targetDeviceIds && this.targetDeviceIds.length > 0) {
  277 + this.targetDeviceId = this.targetDeviceIds[0];
206 } 278 }
207 - } else if (this.targetDeviceIds && this.targetDeviceIds.length > 0) {  
208 - this.targetDeviceId = this.targetDeviceIds[0];  
209 - }  
210 -  
211 - if (this.targetDeviceId) {  
212 - this.rpcEnabled = true;  
213 - } else {  
214 - this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false; 279 + if (this.targetDeviceId) {
  280 + this.rpcEnabled = true;
  281 + } else {
  282 + this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false;
  283 + }
  284 + this.callbacks.rpcStateChanged(this);
  285 + deferred.resolve();
215 } 286 }
216 - this.callbacks.rpcStateChanged(this); 287 + return deferred.promise;
217 } 288 }
218 289
219 clearRpcError() { 290 clearRpcError() {
@@ -319,11 +390,11 @@ export default class Subscription { @@ -319,11 +390,11 @@ export default class Subscription {
319 this.onDataUpdated(); 390 this.onDataUpdated();
320 } 391 }
321 392
322 - onAliasesChanged() { 393 + onAliasesChanged(aliasIds) {
323 if (this.type === this.ctx.types.widgetType.rpc.value) { 394 if (this.type === this.ctx.types.widgetType.rpc.value) {
324 - this.checkRpcTarget(); 395 + return this.checkRpcTarget(aliasIds);
325 } else { 396 } else {
326 - this.checkSubscriptions(); 397 + return this.checkSubscriptions(aliasIds);
327 } 398 }
328 } 399 }
329 400
@@ -481,39 +552,6 @@ export default class Subscription { @@ -481,39 +552,6 @@ export default class Subscription {
481 var datasource = this.datasources[i]; 552 var datasource = this.datasources[i];
482 if (angular.isFunction(datasource)) 553 if (angular.isFunction(datasource))
483 continue; 554 continue;
484 - var entityId = null;  
485 - var entityType = null;  
486 - if (datasource.type === this.ctx.types.datasourceType.entity) {  
487 - var aliasName = null;  
488 - var entityName = null;  
489 - if (datasource.entityId) {  
490 - entityId = datasource.entityId;  
491 - entityType = datasource.entityType;  
492 - datasource.name = datasource.entityName;  
493 - aliasName = datasource.entityName;  
494 - entityName = datasource.entityName;  
495 - } else if (datasource.entityAliasId) {  
496 - if (this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId]) {  
497 - entityId = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].entityId;  
498 - entityType = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].entityType;  
499 - datasource.name = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].alias;  
500 - aliasName = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].alias;  
501 - entityName = '';  
502 - var entitiesInfo = this.ctx.aliasesInfo.entityAliasesInfo[datasource.entityAliasId];  
503 - for (var d = 0; d < entitiesInfo.length; d++) {  
504 - if (entitiesInfo[d].id === entityId) {  
505 - entityName = entitiesInfo[d].name;  
506 - break;  
507 - }  
508 - }  
509 - }  
510 - }  
511 - } else {  
512 - datasource.name = datasource.name || this.ctx.types.datasourceType.function;  
513 - }  
514 - for (var dk = 0; dk < datasource.dataKeys.length; dk++) {  
515 - updateDataKeyLabel(datasource.dataKeys[dk], datasource.name, entityName, aliasName);  
516 - }  
517 555
518 var subscription = this; 556 var subscription = this;
519 557
@@ -521,8 +559,8 @@ export default class Subscription { @@ -521,8 +559,8 @@ export default class Subscription {
521 subscriptionType: this.type, 559 subscriptionType: this.type,
522 subscriptionTimewindow: this.subscriptionTimewindow, 560 subscriptionTimewindow: this.subscriptionTimewindow,
523 datasource: datasource, 561 datasource: datasource,
524 - entityType: entityType,  
525 - entityId: entityId, 562 + entityType: datasource.entityType,
  563 + entityId: datasource.entityId,
526 dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) { 564 dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) {
527 subscription.dataUpdated(data, datasourceIndex, dataKeyIndex, apply); 565 subscription.dataUpdated(data, datasourceIndex, dataKeyIndex, apply);
528 }, 566 },
@@ -544,6 +582,10 @@ export default class Subscription { @@ -544,6 +582,10 @@ export default class Subscription {
544 582
545 this.datasourceListeners.push(listener); 583 this.datasourceListeners.push(listener);
546 this.ctx.datasourceService.subscribeToDatasource(listener); 584 this.ctx.datasourceService.subscribeToDatasource(listener);
  585 + if (datasource.unresolvedStateEntity) {
  586 + this.notifyDataLoaded();
  587 + this.onDataUpdated();
  588 + }
547 } 589 }
548 } 590 }
549 591
@@ -557,48 +599,26 @@ export default class Subscription { @@ -557,48 +599,26 @@ export default class Subscription {
557 } 599 }
558 } 600 }
559 601
560 - checkRpcTarget() {  
561 - var deviceId = null;  
562 - if (this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId]) {  
563 - deviceId = this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId].entityId;  
564 - }  
565 - if (!angular.equals(deviceId, this.targetDeviceId)) {  
566 - this.targetDeviceId = deviceId;  
567 - if (this.targetDeviceId) {  
568 - this.rpcEnabled = true;  
569 - } else {  
570 - this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false;  
571 - }  
572 - this.callbacks.rpcStateChanged(this); 602 + checkRpcTarget(aliasIds) {
  603 + if (aliasIds.indexOf(this.targetDeviceAliasId) > -1) {
  604 + return true;
  605 + } else {
  606 + return false;
573 } 607 }
574 } 608 }
575 609
576 - checkSubscriptions() { 610 + checkSubscriptions(aliasIds) {
577 var subscriptionsChanged = false; 611 var subscriptionsChanged = false;
578 for (var i = 0; i < this.datasourceListeners.length; i++) { 612 for (var i = 0; i < this.datasourceListeners.length; i++) {
579 var listener = this.datasourceListeners[i]; 613 var listener = this.datasourceListeners[i];
580 - var entityId = null;  
581 - var entityType = null;  
582 - var aliasName = null;  
583 - if (listener.datasource.type === this.ctx.types.datasourceType.entity) {  
584 - if (listener.datasource.entityAliasId &&  
585 - this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId]) {  
586 - entityId = this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId].entityId;  
587 - entityType = this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId].entityType;  
588 - aliasName = this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId].alias;  
589 - }  
590 - if (!angular.equals(entityId, listener.entityId) ||  
591 - !angular.equals(entityType, listener.entityType) ||  
592 - !angular.equals(aliasName, listener.datasource.name)) { 614 + if (listener.datasource.entityAliasId) {
  615 + if (aliasIds.indexOf(listener.datasource.entityAliasId) > -1) {
593 subscriptionsChanged = true; 616 subscriptionsChanged = true;
594 break; 617 break;
595 } 618 }
596 } 619 }
597 } 620 }
598 - if (subscriptionsChanged) {  
599 - this.unsubscribe();  
600 - this.subscribe();  
601 - } 621 + return subscriptionsChanged;
602 } 622 }
603 623
604 destroy() { 624 destroy() {
@@ -617,29 +637,6 @@ export default class Subscription { @@ -617,29 +637,6 @@ export default class Subscription {
617 637
618 } 638 }
619 639
620 -const varsRegex = /\$\{([^\}]*)\}/g;  
621 -  
622 -function updateDataKeyLabel(dataKey, dsName, entityName, aliasName) {  
623 - var pattern = dataKey.pattern;  
624 - var label = dataKey.pattern;  
625 - var match = varsRegex.exec(pattern);  
626 - while (match !== null) {  
627 - var variable = match[0];  
628 - var variableName = match[1];  
629 - if (variableName === 'dsName') {  
630 - label = label.split(variable).join(dsName);  
631 - } else if (variableName === 'entityName') {  
632 - label = label.split(variable).join(entityName);  
633 - } else if (variableName === 'deviceName') {  
634 - label = label.split(variable).join(entityName);  
635 - } else if (variableName === 'aliasName') {  
636 - label = label.split(variable).join(aliasName);  
637 - }  
638 - match = varsRegex.exec(pattern);  
639 - }  
640 - dataKey.label = label;  
641 -}  
642 -  
643 function calculateMin(data) { 640 function calculateMin(data) {
644 if (data.length > 0) { 641 if (data.length > 0) {
645 var result = Number(data[0][1]); 642 var result = Number(data[0][1]);
@@ -23,8 +23,10 @@ function DashboardUtils(types, utils, timeService) { @@ -23,8 +23,10 @@ function DashboardUtils(types, utils, timeService) {
23 23
24 var service = { 24 var service = {
25 validateAndUpdateDashboard: validateAndUpdateDashboard, 25 validateAndUpdateDashboard: validateAndUpdateDashboard,
  26 + validateAndUpdateWidget: validateAndUpdateWidget,
26 getRootStateId: getRootStateId, 27 getRootStateId: getRootStateId,
27 createSingleWidgetDashboard: createSingleWidgetDashboard, 28 createSingleWidgetDashboard: createSingleWidgetDashboard,
  29 + createSingleEntityFilter: createSingleEntityFilter,
28 getStateLayoutsData: getStateLayoutsData, 30 getStateLayoutsData: getStateLayoutsData,
29 createDefaultState: createDefaultState, 31 createDefaultState: createDefaultState,
30 createDefaultLayoutData: createDefaultLayoutData, 32 createDefaultLayoutData: createDefaultLayoutData,
@@ -39,40 +41,104 @@ function DashboardUtils(types, utils, timeService) { @@ -39,40 +41,104 @@ function DashboardUtils(types, utils, timeService) {
39 41
40 return service; 42 return service;
41 43
42 - function validateAndUpdateEntityAliases(configuration) { 44 + function validateAndUpdateEntityAliases(configuration, datasourcesByAliasId, targetDevicesByAliasId) {
  45 + var aliasId, entityAlias;
43 if (angular.isUndefined(configuration.entityAliases)) { 46 if (angular.isUndefined(configuration.entityAliases)) {
44 configuration.entityAliases = {}; 47 configuration.entityAliases = {};
45 if (configuration.deviceAliases) { 48 if (configuration.deviceAliases) {
46 var deviceAliases = configuration.deviceAliases; 49 var deviceAliases = configuration.deviceAliases;
47 - for (var aliasId in deviceAliases) { 50 + for (aliasId in deviceAliases) {
48 var deviceAlias = deviceAliases[aliasId]; 51 var deviceAlias = deviceAliases[aliasId];
49 - var alias = deviceAlias.alias;  
50 - var entityFilter = {  
51 - useFilter: false,  
52 - entityNameFilter: '',  
53 - entityList: []  
54 - }  
55 - if (deviceAlias.deviceFilter) {  
56 - entityFilter.useFilter = deviceAlias.deviceFilter.useFilter;  
57 - entityFilter.entityNameFilter = deviceAlias.deviceFilter.deviceNameFilter;  
58 - entityFilter.entityList = deviceAlias.deviceFilter.deviceList;  
59 - } else if (deviceAlias.deviceId) {  
60 - entityFilter.entityList = [deviceAlias.deviceId];  
61 - }  
62 - var entityAlias = {  
63 - id: aliasId,  
64 - alias: alias,  
65 - entityType: types.entityType.device,  
66 - entityFilter: entityFilter  
67 - };  
68 - configuration.entityAliases[aliasId] = entityAlias; 52 + entityAlias = validateAndUpdateDeviceAlias(aliasId, deviceAlias, datasourcesByAliasId, targetDevicesByAliasId);
  53 + configuration.entityAliases[entityAlias.id] = entityAlias;
69 } 54 }
70 delete configuration.deviceAliases; 55 delete configuration.deviceAliases;
71 } 56 }
  57 + } else {
  58 + var entityAliases = configuration.entityAliases;
  59 + for (aliasId in entityAliases) {
  60 + entityAlias = entityAliases[aliasId];
  61 + entityAlias = validateAndUpdateEntityAlias(aliasId, entityAlias, datasourcesByAliasId, targetDevicesByAliasId);
  62 + if (aliasId != entityAlias.id) {
  63 + delete entityAliases[aliasId];
  64 + }
  65 + entityAliases[entityAlias.id] = entityAlias;
  66 + }
72 } 67 }
73 return configuration; 68 return configuration;
74 } 69 }
75 70
  71 + function validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId) {
  72 + if (!aliasId || !angular.isString(aliasId) || aliasId.length != 36) {
  73 + var newAliasId = utils.guid();
  74 + var aliasDatasources = datasourcesByAliasId[aliasId];
  75 + if (aliasDatasources) {
  76 + aliasDatasources.forEach(
  77 + function(datasource) {
  78 + datasource.entityAliasId = newAliasId;
  79 + }
  80 + );
  81 + }
  82 + var targetDeviceAliasIdsList = targetDevicesByAliasId[aliasId];
  83 + if (targetDeviceAliasIdsList) {
  84 + targetDeviceAliasIdsList.forEach(
  85 + function(targetDeviceAliasIds) {
  86 + targetDeviceAliasIds[0] = newAliasId;
  87 + }
  88 + );
  89 + }
  90 + return newAliasId;
  91 + } else {
  92 + return aliasId;
  93 + }
  94 + }
  95 +
  96 + function validateAndUpdateDeviceAlias(aliasId, deviceAlias, datasourcesByAliasId, targetDevicesByAliasId) {
  97 + aliasId = validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId);
  98 + var alias = deviceAlias.alias;
  99 + var entityAlias = {
  100 + id: aliasId,
  101 + alias: alias,
  102 + filter: {
  103 + type: null,
  104 + entityType: types.entityType.device,
  105 + resolveMultiple: false
  106 + },
  107 + }
  108 + if (deviceAlias.deviceFilter) {
  109 + entityAlias.filter.type =
  110 + deviceAlias.deviceFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value;
  111 + if (entityAlias.filter.type == types.aliasFilterType.entityList.value) {
  112 + entityAlias.filter.entityList = deviceAlias.deviceFilter.deviceList;
  113 + } else {
  114 + entityAlias.filter.entityNameFilter = deviceAlias.deviceFilter.deviceNameFilter;
  115 + }
  116 + } else {
  117 + entityAlias.filter.type = types.aliasFilterType.entityList.value;
  118 + entityAlias.filter.entityList = [deviceAlias.deviceId];
  119 + }
  120 + return entityAlias;
  121 + }
  122 +
  123 + function validateAndUpdateEntityAlias(aliasId, entityAlias, datasourcesByAliasId, targetDevicesByAliasId) {
  124 + entityAlias.id = validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId);
  125 + if (!entityAlias.filter) {
  126 + entityAlias.filter = {
  127 + type: entityAlias.entityFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value,
  128 + entityType: entityAlias.entityType,
  129 + resolveMultiple: false
  130 + }
  131 + if (entityAlias.filter.type == types.aliasFilterType.entityList.value) {
  132 + entityAlias.filter.entityList = entityAlias.entityFilter.entityList;
  133 + } else {
  134 + entityAlias.filter.entityNameFilter = entityAlias.entityFilter.entityNameFilter;
  135 + }
  136 + delete entityAlias.entityType;
  137 + delete entityAlias.entityFilter;
  138 + }
  139 + return entityAlias;
  140 + }
  141 +
76 function validateAndUpdateWidget(widget) { 142 function validateAndUpdateWidget(widget) {
77 if (!widget.config) { 143 if (!widget.config) {
78 widget.config = {}; 144 widget.config = {};
@@ -166,7 +232,34 @@ function DashboardUtils(types, utils, timeService) { @@ -166,7 +232,34 @@ function DashboardUtils(types, utils, timeService) {
166 states[firstStateId].root = true; 232 states[firstStateId].root = true;
167 } 233 }
168 } 234 }
169 - dashboard.configuration = validateAndUpdateEntityAliases(dashboard.configuration); 235 +
  236 + var datasourcesByAliasId = {};
  237 + var targetDevicesByAliasId = {};
  238 + for (var widgetId in dashboard.configuration.widgets) {
  239 + widget = dashboard.configuration.widgets[widgetId];
  240 + widget.config.datasources.forEach(function (datasource) {
  241 + if (datasource.entityAliasId) {
  242 + var aliasId = datasource.entityAliasId;
  243 + var aliasDatasources = datasourcesByAliasId[aliasId];
  244 + if (!aliasDatasources) {
  245 + aliasDatasources = [];
  246 + datasourcesByAliasId[aliasId] = aliasDatasources;
  247 + }
  248 + aliasDatasources.push(datasource);
  249 + }
  250 + });
  251 + if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length) {
  252 + var aliasId = widget.config.targetDeviceAliasIds[0];
  253 + var targetDeviceAliasIdsList = targetDevicesByAliasId[aliasId];
  254 + if (!targetDeviceAliasIdsList) {
  255 + targetDeviceAliasIdsList = [];
  256 + targetDevicesByAliasId[aliasId] = targetDeviceAliasIdsList;
  257 + }
  258 + targetDeviceAliasIdsList.push(widget.config.targetDeviceAliasIds);
  259 + }
  260 + }
  261 +
  262 + dashboard.configuration = validateAndUpdateEntityAliases(dashboard.configuration, datasourcesByAliasId, targetDevicesByAliasId);
170 263
171 if (angular.isUndefined(dashboard.configuration.timewindow)) { 264 if (angular.isUndefined(dashboard.configuration.timewindow)) {
172 dashboard.configuration.timewindow = timeService.defaultTimewindow(); 265 dashboard.configuration.timewindow = timeService.defaultTimewindow();
@@ -243,6 +336,15 @@ function DashboardUtils(types, utils, timeService) { @@ -243,6 +336,15 @@ function DashboardUtils(types, utils, timeService) {
243 return dashboard; 336 return dashboard;
244 } 337 }
245 338
  339 + function createSingleEntityFilter(entityType, entityId) {
  340 + return {
  341 + type: types.aliasFilterType.entityList.value,
  342 + entityList: [entityId],
  343 + entityType: entityType,
  344 + resolveMultiple: false
  345 + };
  346 + }
  347 +
246 function getStateLayoutsData(dashboard, targetState) { 348 function getStateLayoutsData(dashboard, targetState) {
247 var dashboardConfiguration = dashboard.configuration; 349 var dashboardConfiguration = dashboard.configuration;
248 var states = dashboardConfiguration.states; 350 var states = dashboardConfiguration.states;
@@ -65,6 +65,40 @@ export default angular.module('thingsboard.types', []) @@ -65,6 +65,40 @@ export default angular.module('thingsboard.types', [])
65 clearedUnack: "CLEARED_UNACK", 65 clearedUnack: "CLEARED_UNACK",
66 clearedAck: "CLEARED_ACK" 66 clearedAck: "CLEARED_ACK"
67 }, 67 },
  68 + aliasFilterType: {
  69 + entityList: {
  70 + value: 'entityList',
  71 + name: 'alias.filter-type-entity-list'
  72 + },
  73 + entityName: {
  74 + value: 'entityName',
  75 + name: 'alias.filter-type-entity-name'
  76 + },
  77 + stateEntity: {
  78 + value: 'stateEntity',
  79 + name: 'alias.filter-type-state-entity'
  80 + },
  81 + assetType: {
  82 + value: 'assetType',
  83 + name: 'alias.filter-type-asset-type'
  84 + },
  85 + deviceType: {
  86 + value: 'deviceType',
  87 + name: 'alias.filter-type-device-type'
  88 + },
  89 + relationsQuery: {
  90 + value: 'relationsQuery',
  91 + name: 'alias.filter-type-relations-query'
  92 + },
  93 + assetSearchQuery: {
  94 + value: 'assetSearchQuery',
  95 + name: 'alias.filter-type-asset-search-query'
  96 + },
  97 + deviceSearchQuery: {
  98 + value: 'deviceSearchQuery',
  99 + name: 'alias.filter-type-device-search-query'
  100 + }
  101 + },
68 position: { 102 position: {
69 top: { 103 top: {
70 value: "top", 104 value: "top",
@@ -109,6 +143,62 @@ export default angular.module('thingsboard.types', []) @@ -109,6 +143,62 @@ export default angular.module('thingsboard.types', [])
109 dashboard: "DASHBOARD", 143 dashboard: "DASHBOARD",
110 alarm: "ALARM" 144 alarm: "ALARM"
111 }, 145 },
  146 + entityTypeTranslations: {
  147 + "DEVICE": {
  148 + type: 'entity.type-device',
  149 + typePlural: 'entity.type-devices',
  150 + list: 'entity.list-of-devices',
  151 + nameStartsWith: 'entity.device-name-starts-with'
  152 + },
  153 + "ASSET": {
  154 + type: 'entity.type-asset',
  155 + typePlural: 'entity.type-assets',
  156 + list: 'entity.list-of-assets',
  157 + nameStartsWith: 'entity.asset-name-starts-with'
  158 + },
  159 + "RULE": {
  160 + type: 'entity.type-rule',
  161 + typePlural: 'entity.type-rules',
  162 + list: 'entity.list-of-rules',
  163 + nameStartsWith: 'entity.rule-name-starts-with'
  164 + },
  165 + "PLUGIN": {
  166 + type: 'entity.type-plugin',
  167 + typePlural: 'entity.type-plugins',
  168 + list: 'entity.list-of-plugins',
  169 + nameStartsWith: 'entity.plugin-name-starts-with'
  170 + },
  171 + "TENANT": {
  172 + type: 'entity.type-tenant',
  173 + typePlural: 'entity.type-tenants',
  174 + list: 'entity.list-of-tenants',
  175 + nameStartsWith: 'entity.tenant-name-starts-with'
  176 + },
  177 + "CUSTOMER": {
  178 + type: 'entity.type-customer',
  179 + typePlural: 'entity.type-customers',
  180 + list: 'entity.list-of-customers',
  181 + nameStartsWith: 'entity.customer-name-starts-with'
  182 + },
  183 + "USER": {
  184 + type: 'entity.type-user',
  185 + typePlural: 'entity.type-users',
  186 + list: 'entity.list-of-users',
  187 + nameStartsWith: 'entity.user-name-starts-with'
  188 + },
  189 + "DASHBOARD": {
  190 + type: 'entity.type-dashboard',
  191 + typePlural: 'entity.type-dashboards',
  192 + list: 'entity.list-of-dashboards',
  193 + nameStartsWith: 'entity.dashboard-name-starts-with'
  194 + },
  195 + "ALARM": {
  196 + type: 'entity.type-alarm',
  197 + typePlural: 'entity.type-alarms',
  198 + list: 'entity.list-of-alarms',
  199 + nameStartsWith: 'entity.alarm-name-starts-with'
  200 + }
  201 + },
112 entitySearchDirection: { 202 entitySearchDirection: {
113 from: "FROM", 203 from: "FROM",
114 to: "TO" 204 to: "TO"
@@ -106,10 +106,10 @@ function Utils($mdColorPalette, $rootScope, $window, types) { @@ -106,10 +106,10 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
106 isDescriptorSchemaNotEmpty: isDescriptorSchemaNotEmpty, 106 isDescriptorSchemaNotEmpty: isDescriptorSchemaNotEmpty,
107 filterSearchTextEntities: filterSearchTextEntities, 107 filterSearchTextEntities: filterSearchTextEntities,
108 guid: guid, 108 guid: guid,
  109 + cleanCopy: cleanCopy,
109 isLocalUrl: isLocalUrl, 110 isLocalUrl: isLocalUrl,
110 validateDatasources: validateDatasources, 111 validateDatasources: validateDatasources,
111 - createKey: createKey,  
112 - entityTypeName: entityTypeName 112 + createKey: createKey
113 } 113 }
114 114
115 return service; 115 return service;
@@ -291,6 +291,16 @@ function Utils($mdColorPalette, $rootScope, $window, types) { @@ -291,6 +291,16 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
291 s4() + '-' + s4() + s4() + s4(); 291 s4() + '-' + s4() + s4() + s4();
292 } 292 }
293 293
  294 + function cleanCopy(object) {
  295 + var copy = angular.copy(object);
  296 + for (var prop in copy) {
  297 + if (prop && prop.startsWith('$$')) {
  298 + delete copy[prop];
  299 + }
  300 + }
  301 + return copy;
  302 + }
  303 +
294 function genNextColor(datasources) { 304 function genNextColor(datasources) {
295 var index = 0; 305 var index = 0;
296 if (datasources) { 306 if (datasources) {
@@ -347,27 +357,4 @@ function Utils($mdColorPalette, $rootScope, $window, types) { @@ -347,27 +357,4 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
347 return dataKey; 357 return dataKey;
348 } 358 }
349 359
350 - function entityTypeName (type) {  
351 - switch (type) {  
352 - case types.entityType.device:  
353 - return 'entity.type-device';  
354 - case types.entityType.asset:  
355 - return 'entity.type-asset';  
356 - case types.entityType.rule:  
357 - return 'entity.type-rule';  
358 - case types.entityType.plugin:  
359 - return 'entity.type-plugin';  
360 - case types.entityType.tenant:  
361 - return 'entity.type-tenant';  
362 - case types.entityType.customer:  
363 - return 'entity.type-customer';  
364 - case types.entityType.user:  
365 - return 'entity.type-user';  
366 - case types.entityType.dashboard:  
367 - return 'entity.type-dashboard';  
368 - case types.entityType.alarm:  
369 - return 'entity.type-alarm';  
370 - }  
371 - }  
372 -  
373 } 360 }
@@ -52,7 +52,7 @@ function Dashboard() { @@ -52,7 +52,7 @@ function Dashboard() {
52 bindToController: { 52 bindToController: {
53 widgets: '=', 53 widgets: '=',
54 widgetLayouts: '=?', 54 widgetLayouts: '=?',
55 - aliasesInfo: '=', 55 + aliasController: '=',
56 stateController: '=', 56 stateController: '=',
57 dashboardTimewindow: '=?', 57 dashboardTimewindow: '=?',
58 columns: '=', 58 columns: '=',
@@ -85,7 +85,7 @@ function Dashboard() { @@ -85,7 +85,7 @@ function Dashboard() {
85 } 85 }
86 86
87 /*@ngInject*/ 87 /*@ngInject*/
88 -function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, timeService, types, utils) { 88 +function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $mdUtil, timeService, types, utils) {
89 89
90 var highlightedMode = false; 90 var highlightedMode = false;
91 var highlightedWidget = null; 91 var highlightedWidget = null;
@@ -329,10 +329,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t @@ -329,10 +329,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
329 $scope.$broadcast('toggleDashboardEditMode', vm.isEdit); 329 $scope.$broadcast('toggleDashboardEditMode', vm.isEdit);
330 }); 330 });
331 331
332 - $scope.$watch('vm.aliasesInfo.entityAliases', function () {  
333 - $scope.$broadcast('entityAliasListChanged', vm.aliasesInfo);  
334 - }, true);  
335 -  
336 $scope.$on('gridster-resized', function (event, sizes, theGridster) { 332 $scope.$on('gridster-resized', function (event, sizes, theGridster) {
337 if (checkIsLocalGridsterElement(theGridster)) { 333 if (checkIsLocalGridsterElement(theGridster)) {
338 vm.gridster = theGridster; 334 vm.gridster = theGridster;
@@ -796,7 +792,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t @@ -796,7 +792,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
796 } 792 }
797 793
798 function dashboardLoaded() { 794 function dashboardLoaded() {
799 - $timeout(function () { 795 + $mdUtil.nextTick(function () {
800 if (vm.dashboardTimewindowWatch) { 796 if (vm.dashboardTimewindowWatch) {
801 vm.dashboardTimewindowWatch(); 797 vm.dashboardTimewindowWatch();
802 vm.dashboardTimewindowWatch = null; 798 vm.dashboardTimewindowWatch = null;
@@ -806,14 +802,27 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t @@ -806,14 +802,27 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
806 }, true); 802 }, true);
807 adoptMaxRows(); 803 adoptMaxRows();
808 vm.dashboardLoading = false; 804 vm.dashboardLoading = false;
809 - $timeout(function () {  
810 - var gridsterScope = gridsterElement.scope();  
811 - vm.gridster = gridsterScope.gridster;  
812 - if (vm.onInit) {  
813 - vm.onInit({dashboard: vm}); 805 + if ($scope.gridsterScopeWatcher) {
  806 + $scope.gridsterScopeWatcher();
  807 + }
  808 + $scope.gridsterScopeWatcher = $scope.$watch(
  809 + function() {
  810 + var hasScope = gridsterElement.scope() ? true : false;
  811 + return hasScope;
  812 + },
  813 + function(hasScope) {
  814 + if (hasScope) {
  815 + $scope.gridsterScopeWatcher();
  816 + $scope.gridsterScopeWatcher = null;
  817 + var gridsterScope = gridsterElement.scope();
  818 + vm.gridster = gridsterScope.gridster;
  819 + if (vm.onInit) {
  820 + vm.onInit({dashboard: vm});
  821 + }
  822 + }
814 } 823 }
815 - }, 0, false);  
816 - }, 0, false); 824 + );
  825 + });
817 } 826 }
818 827
819 function loading() { 828 function loading() {
@@ -89,7 +89,7 @@ @@ -89,7 +89,7 @@
89 <div flex tb-widget 89 <div flex tb-widget
90 locals="{ visibleRect: vm.visibleRect, 90 locals="{ visibleRect: vm.visibleRect,
91 widget: widget, 91 widget: widget,
92 - aliasesInfo: vm.aliasesInfo, 92 + aliasController: vm.aliasController,
93 stateController: vm.stateController, 93 stateController: vm.stateController,
94 isEdit: vm.isEdit, 94 isEdit: vm.isEdit,
95 stDiff: vm.stDiff, 95 stDiff: vm.stDiff,
@@ -20,14 +20,14 @@ export default angular.module('thingsboard.dialogs.datakeyConfigDialog', [things @@ -20,14 +20,14 @@ export default angular.module('thingsboard.dialogs.datakeyConfigDialog', [things
20 .name; 20 .name;
21 21
22 /*@ngInject*/ 22 /*@ngInject*/
23 -function DatakeyConfigDialogController($scope, $mdDialog, entityService, dataKey, dataKeySettingsSchema, entityAlias, entityAliases) { 23 +function DatakeyConfigDialogController($scope, $mdDialog, $q, entityService, dataKey, dataKeySettingsSchema, entityAlias, aliasController) {
24 24
25 var vm = this; 25 var vm = this;
26 26
27 vm.dataKey = dataKey; 27 vm.dataKey = dataKey;
28 vm.dataKeySettingsSchema = dataKeySettingsSchema; 28 vm.dataKeySettingsSchema = dataKeySettingsSchema;
29 vm.entityAlias = entityAlias; 29 vm.entityAlias = entityAlias;
30 - vm.entityAliases = entityAliases; 30 + vm.aliasController = aliasController;
31 31
32 vm.hide = function () { 32 vm.hide = function () {
33 $mdDialog.hide(); 33 $mdDialog.hide();
@@ -38,12 +38,28 @@ function DatakeyConfigDialogController($scope, $mdDialog, entityService, dataKey @@ -38,12 +38,28 @@ function DatakeyConfigDialogController($scope, $mdDialog, entityService, dataKey
38 }; 38 };
39 39
40 vm.fetchEntityKeys = function (entityAliasId, query, type) { 40 vm.fetchEntityKeys = function (entityAliasId, query, type) {
41 - var alias = vm.entityAliases[entityAliasId];  
42 - if (alias) {  
43 - return entityService.getEntityKeys(alias.entityType, alias.entityId, query, type);  
44 - } else {  
45 - return [];  
46 - } 41 + var deferred = $q.defer();
  42 + vm.aliasController.getAliasInfo(entityAliasId).then(
  43 + function success(aliasInfo) {
  44 + var entity = aliasInfo.currentEntity;
  45 + if (entity) {
  46 + entityService.getEntityKeys(entity.entityType, entity.id, query, type).then(
  47 + function success(keys) {
  48 + deferred.resolve(keys);
  49 + },
  50 + function fail() {
  51 + deferred.resolve([]);
  52 + }
  53 + );
  54 + } else {
  55 + deferred.resolve([]);
  56 + }
  57 + },
  58 + function fail() {
  59 + deferred.resolve([]);
  60 + }
  61 + );
  62 + return deferred.promise;
47 }; 63 };
48 64
49 vm.save = function () { 65 vm.save = function () {
@@ -103,10 +103,9 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc @@ -103,10 +103,9 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
103 ngModelCtrl.$render = function () { 103 ngModelCtrl.$render = function () {
104 if (ngModelCtrl.$viewValue) { 104 if (ngModelCtrl.$viewValue) {
105 var entityAliasId = ngModelCtrl.$viewValue.entityAliasId; 105 var entityAliasId = ngModelCtrl.$viewValue.entityAliasId;
106 - if (scope.entityAliases[entityAliasId]) {  
107 - scope.entityAlias = {id: entityAliasId, alias: scope.entityAliases[entityAliasId].alias,  
108 - entityType: scope.entityAliases[entityAliasId].entityType,  
109 - entityId: scope.entityAliases[entityAliasId].entityId}; 106 + var entityAliases = scope.aliasController.getEntityAliases();
  107 + if (entityAliases[entityAliasId]) {
  108 + scope.entityAlias = entityAliases[entityAliasId];
110 } else { 109 } else {
111 scope.entityAlias = null; 110 scope.entityAlias = null;
112 } 111 }
@@ -182,7 +181,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc @@ -182,7 +181,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
182 dataKey: angular.copy(dataKey), 181 dataKey: angular.copy(dataKey),
183 dataKeySettingsSchema: scope.datakeySettingsSchema, 182 dataKeySettingsSchema: scope.datakeySettingsSchema,
184 entityAlias: scope.entityAlias, 183 entityAlias: scope.entityAlias,
185 - entityAliases: scope.entityAliases 184 + aliasController: scope.aliasController
186 }, 185 },
187 parent: angular.element($document[0].body), 186 parent: angular.element($document[0].body),
188 fullscreen: true, 187 fullscreen: true,
@@ -236,7 +235,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc @@ -236,7 +235,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
236 require: "^ngModel", 235 require: "^ngModel",
237 scope: { 236 scope: {
238 widgetType: '=', 237 widgetType: '=',
239 - entityAliases: '=', 238 + aliasController: '=',
240 datakeySettingsSchema: '=', 239 datakeySettingsSchema: '=',
241 generateDataKey: '&', 240 generateDataKey: '&',
242 fetchEntityKeys: '&', 241 fetchEntityKeys: '&',
@@ -18,7 +18,7 @@ @@ -18,7 +18,7 @@
18 <section flex layout='column' layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center"> 18 <section flex layout='column' layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
19 <tb-entity-alias-select 19 <tb-entity-alias-select
20 tb-required="true" 20 tb-required="true"
21 - entity-aliases="entityAliases" 21 + alias-controller="aliasController"
22 ng-model="entityAlias" 22 ng-model="entityAlias"
23 on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})"> 23 on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})">
24 </tb-entity-alias-select> 24 </tb-entity-alias-select>
@@ -71,6 +71,13 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, @@ -71,6 +71,13 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
71 } 71 }
72 }, true); 72 }, true);
73 73
  74 + scope.$watch('datasourceName', function () {
  75 + if (ngModelCtrl.$viewValue) {
  76 + ngModelCtrl.$viewValue.name = scope.datasourceName;
  77 + scope.updateValidity();
  78 + }
  79 + });
  80 +
74 ngModelCtrl.$render = function () { 81 ngModelCtrl.$render = function () {
75 if (ngModelCtrl.$viewValue) { 82 if (ngModelCtrl.$viewValue) {
76 var funcDataKeys = []; 83 var funcDataKeys = [];
@@ -78,6 +85,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, @@ -78,6 +85,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
78 funcDataKeys = funcDataKeys.concat(ngModelCtrl.$viewValue.dataKeys); 85 funcDataKeys = funcDataKeys.concat(ngModelCtrl.$viewValue.dataKeys);
79 } 86 }
80 scope.funcDataKeys = funcDataKeys; 87 scope.funcDataKeys = funcDataKeys;
  88 + scope.datasourceName = ngModelCtrl.$viewValue.name;
81 } 89 }
82 }; 90 };
83 91
@@ -15,23 +15,29 @@ @@ -15,23 +15,29 @@
15 */ 15 */
16 @import '../../scss/constants'; 16 @import '../../scss/constants';
17 17
18 -.tb-func-datakey-autocomplete {  
19 - .tb-not-found {  
20 - display: block;  
21 - line-height: 1.5;  
22 - height: 48px;  
23 - .tb-no-entries {  
24 - line-height: 48px;  
25 - } 18 +.tb-datasource-func {
  19 + @media (min-width: $layout-breakpoint-gt-sm) {
  20 + padding-left: 8px;
26 } 21 }
27 - li {  
28 - height: auto !important;  
29 - white-space: normal !important; 22 +
  23 + md-input-container.tb-datasource-name {
  24 + .md-errors-spacer {
  25 + display: none;
  26 + }
30 } 27 }
31 -}  
32 28
33 -tb-datasource-func {  
34 - @media (min-width: $layout-breakpoint-gt-sm) {  
35 - padding-left: 8px; 29 + .tb-func-datakey-autocomplete {
  30 + .tb-not-found {
  31 + display: block;
  32 + line-height: 1.5;
  33 + height: 48px;
  34 + .tb-no-entries {
  35 + line-height: 48px;
  36 + }
  37 + }
  38 + li {
  39 + height: auto !important;
  40 + white-space: normal !important;
  41 + }
36 } 42 }
37 -}  
  43 +}
@@ -15,59 +15,68 @@ @@ -15,59 +15,68 @@
15 limitations under the License. 15 limitations under the License.
16 16
17 --> 17 -->
18 -<section flex layout='column' style="padding-left: 4px;">  
19 - <md-chips flex  
20 - id="function_datakey_chips"  
21 - ng-required="true"  
22 - ng-model="funcDataKeys" md-autocomplete-snap  
23 - md-transform-chip="transformDataKeyChip($chip)"  
24 - md-require-match="false">  
25 - <md-autocomplete  
26 - md-no-cache="false"  
27 - id="dataKey"  
28 - md-selected-item="selectedDataKey"  
29 - md-search-text="dataKeySearchText"  
30 - md-items="item in dataKeysSearch(dataKeySearchText)"  
31 - md-item-text="item.name"  
32 - md-min-length="0"  
33 - placeholder="{{ 'datakey.function-types' | translate }}"  
34 - md-menu-class="tb-func-datakey-autocomplete">  
35 - <span md-highlight-text="dataKeySearchText" md-highlight-flags="^i">{{item}}</span>  
36 - <md-not-found>  
37 - <div class="tb-not-found">  
38 - <div class="tb-no-entries" ng-if="!textIsNotEmpty(dataKeySearchText)">  
39 - <span translate>device.no-keys-found</span> 18 +<section class="tb-datasource-func" flex layout='column'
  19 + layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
  20 + <md-input-container class="tb-datasource-name" md-no-float style="min-width: 200px;">
  21 + <input name="datasourceName"
  22 + placeholder="{{ 'datasource.name' | translate }}"
  23 + ng-model="datasourceName"
  24 + aria-label="{{ 'datasource.name' | translate }}">
  25 + </md-input-container>
  26 + <section flex layout='column' style="padding-left: 4px;">
  27 + <md-chips flex
  28 + id="function_datakey_chips"
  29 + ng-required="true"
  30 + ng-model="funcDataKeys" md-autocomplete-snap
  31 + md-transform-chip="transformDataKeyChip($chip)"
  32 + md-require-match="false">
  33 + <md-autocomplete
  34 + md-no-cache="false"
  35 + id="dataKey"
  36 + md-selected-item="selectedDataKey"
  37 + md-search-text="dataKeySearchText"
  38 + md-items="item in dataKeysSearch(dataKeySearchText)"
  39 + md-item-text="item.name"
  40 + md-min-length="0"
  41 + placeholder="{{ 'datakey.function-types' | translate }}"
  42 + md-menu-class="tb-func-datakey-autocomplete">
  43 + <span md-highlight-text="dataKeySearchText" md-highlight-flags="^i">{{item}}</span>
  44 + <md-not-found>
  45 + <div class="tb-not-found">
  46 + <div class="tb-no-entries" ng-if="!textIsNotEmpty(dataKeySearchText)">
  47 + <span translate>device.no-keys-found</span>
  48 + </div>
  49 + <div ng-if="textIsNotEmpty(dataKeySearchText)">
  50 + <span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>device.no-key-matching</span>
  51 + <span>
  52 + <a translate ng-click="createKey($event, '#function_datakey_chips')">device.create-new-key</a>
  53 + </span>
  54 + </div>
40 </div> 55 </div>
41 - <div ng-if="textIsNotEmpty(dataKeySearchText)">  
42 - <span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>device.no-key-matching</span>  
43 - <span>  
44 - <a translate ng-click="createKey($event, '#function_datakey_chips')">device.create-new-key</a>  
45 - </span>  
46 - </div>  
47 - </div>  
48 - </md-not-found>  
49 - </md-autocomplete>  
50 - <md-chip-template>  
51 - <div layout="row" layout-align="start center" class="tb-attribute-chip">  
52 - <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">  
53 - <div class="tb-color-result" ng-style="{background: $chip.color}"></div>  
54 - </div>  
55 - <div layout="row" flex>  
56 - <div class="tb-chip-label">  
57 - {{$chip.label}} 56 + </md-not-found>
  57 + </md-autocomplete>
  58 + <md-chip-template>
  59 + <div layout="row" layout-align="start center" class="tb-attribute-chip">
  60 + <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
  61 + <div class="tb-color-result" ng-style="{background: $chip.color}"></div>
58 </div> 62 </div>
59 - <div class="tb-chip-separator">: </div>  
60 - <div class="tb-chip-label">  
61 - <strong>{{$chip.name}}</strong> 63 + <div layout="row" flex>
  64 + <div class="tb-chip-label">
  65 + {{$chip.label}}
  66 + </div>
  67 + <div class="tb-chip-separator">: </div>
  68 + <div class="tb-chip-label">
  69 + <strong>{{$chip.name}}</strong>
  70 + </div>
62 </div> 71 </div>
  72 + <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
  73 + <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
  74 + </md-button>
63 </div> 75 </div>
64 - <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">  
65 - <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>  
66 - </md-button>  
67 - </div>  
68 - </md-chip-template>  
69 - </md-chips>  
70 - <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">  
71 - <div translate ng-message="funcTypes" class="tb-error-message">datakey.function-types-required</div>  
72 - </div> 76 + </md-chip-template>
  77 + </md-chips>
  78 + <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
  79 + <div translate ng-message="funcTypes" class="tb-error-message">datakey.function-types-required</div>
  80 + </div>
  81 + </section>
73 </section> 82 </section>
@@ -76,7 +76,7 @@ function Datasource($compile, $templateCache, types) { @@ -76,7 +76,7 @@ function Datasource($compile, $templateCache, types) {
76 restrict: "E", 76 restrict: "E",
77 require: "^ngModel", 77 require: "^ngModel",
78 scope: { 78 scope: {
79 - entityAliases: '=', 79 + aliasController: '=',
80 widgetType: '=', 80 widgetType: '=',
81 functionsOnly: '=', 81 functionsOnly: '=',
82 datakeySettingsSchema: '=', 82 datakeySettingsSchema: '=',
@@ -37,7 +37,7 @@ @@ -37,7 +37,7 @@
37 ng-switch-when="entity" 37 ng-switch-when="entity"
38 ng-required="model.type === types.datasourceType.entity" 38 ng-required="model.type === types.datasourceType.entity"
39 widget-type="widgetType" 39 widget-type="widgetType"
40 - entity-aliases="entityAliases" 40 + alias-controller="aliasController"
41 generate-data-key="generateDataKey({chip: chip, type: type})" 41 generate-data-key="generateDataKey({chip: chip, type: type})"
42 fetch-entity-keys="fetchEntityKeys({entityAliasId: entityAliasId, query: query, type: type})" 42 fetch-entity-keys="fetchEntityKeys({entityAliasId: entityAliasId, query: query, type: type})"
43 on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})"> 43 on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})">
@@ -31,7 +31,7 @@ export default angular.module('thingsboard.directives.entityAliasSelect', []) @@ -31,7 +31,7 @@ export default angular.module('thingsboard.directives.entityAliasSelect', [])
31 .name; 31 .name;
32 32
33 /*@ngInject*/ 33 /*@ngInject*/
34 -function EntityAliasSelect($compile, $templateCache, $mdConstant) { 34 +function EntityAliasSelect($compile, $templateCache, $mdConstant, entityService) {
35 35
36 var linker = function (scope, element, attrs, ngModelCtrl) { 36 var linker = function (scope, element, attrs, ngModelCtrl) {
37 var template = $templateCache.get(entityAliasSelectTemplate); 37 var template = $templateCache.get(entityAliasSelectTemplate);
@@ -49,19 +49,18 @@ function EntityAliasSelect($compile, $templateCache, $mdConstant) { @@ -49,19 +49,18 @@ function EntityAliasSelect($compile, $templateCache, $mdConstant) {
49 ngModelCtrl.$setValidity('entityAlias', valid); 49 ngModelCtrl.$setValidity('entityAlias', valid);
50 }; 50 };
51 51
52 - scope.$watch('entityAliases', function () { 52 + scope.$watch('aliasController', function () {
53 scope.entityAliasList = []; 53 scope.entityAliasList = [];
54 - for (var aliasId in scope.entityAliases) { 54 + var entityAliases = scope.aliasController.getEntityAliases();
  55 + for (var aliasId in entityAliases) {
55 if (scope.allowedEntityTypes) { 56 if (scope.allowedEntityTypes) {
56 - if (scope.allowedEntityTypes.indexOf(scope.entityAliases[aliasId].entityType) === -1) { 57 + if (!entityService.filterAliasByEntityTypes(entityAliases[aliasId], scope.allowedEntityTypes)) {
57 continue; 58 continue;
58 } 59 }
59 } 60 }
60 - var entityAlias = {id: aliasId, alias: scope.entityAliases[aliasId].alias,  
61 - entityType: scope.entityAliases[aliasId].entityType, entityId: scope.entityAliases[aliasId].entityId};  
62 - scope.entityAliasList.push(entityAlias); 61 + scope.entityAliasList.push(entityAliases[aliasId]);
63 } 62 }
64 - }, true); 63 + });
65 64
66 scope.$watch('entityAlias', function () { 65 scope.$watch('entityAlias', function () {
67 scope.updateView(); 66 scope.updateView();
@@ -141,7 +140,7 @@ function EntityAliasSelect($compile, $templateCache, $mdConstant) { @@ -141,7 +140,7 @@ function EntityAliasSelect($compile, $templateCache, $mdConstant) {
141 link: linker, 140 link: linker,
142 scope: { 141 scope: {
143 tbRequired: '=?', 142 tbRequired: '=?',
144 - entityAliases: '=', 143 + aliasController: '=',
145 allowedEntityTypes: '=?', 144 allowedEntityTypes: '=?',
146 onCreateEntityAlias: '&' 145 onCreateEntityAlias: '&'
147 } 146 }
@@ -128,13 +128,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti @@ -128,13 +128,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
128 } else if (scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) { 128 } else if (scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) {
129 if (config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0) { 129 if (config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0) {
130 var aliasId = config.targetDeviceAliasIds[0]; 130 var aliasId = config.targetDeviceAliasIds[0];
131 - if (scope.entityAliases[aliasId]) {  
132 - scope.targetDeviceAlias.value = {  
133 - id: aliasId,  
134 - alias: scope.entityAliases[aliasId].alias,  
135 - entityType: scope.entityAliases[aliasId].entityType,  
136 - entityId: scope.entityAliases[aliasId].entityId  
137 - }; 131 + var entityAliases = scope.aliasController.getEntityAliases();
  132 + if (entityAliases[aliasId]) {
  133 + scope.targetDeviceAlias.value = entityAliases[aliasId];
138 } else { 134 } else {
139 scope.targetDeviceAlias.value = null; 135 scope.targetDeviceAlias.value = null;
140 } 136 }
@@ -395,7 +391,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti @@ -395,7 +391,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
395 widgetType: '=', 391 widgetType: '=',
396 widgetSettingsSchema: '=', 392 widgetSettingsSchema: '=',
397 datakeySettingsSchema: '=', 393 datakeySettingsSchema: '=',
398 - entityAliases: '=', 394 + aliasController: '=',
399 functionsOnly: '=', 395 functionsOnly: '=',
400 fetchEntityKeys: '&', 396 fetchEntityKeys: '&',
401 onCreateEntityAlias: '&', 397 onCreateEntityAlias: '&',
@@ -60,7 +60,7 @@ @@ -60,7 +60,7 @@
60 style="padding: 0 0 0 10px; margin: 5px;"> 60 style="padding: 0 0 0 10px; margin: 5px;">
61 <tb-datasource flex ng-model="datasource.value" 61 <tb-datasource flex ng-model="datasource.value"
62 widget-type="widgetType" 62 widget-type="widgetType"
63 - entity-aliases="entityAliases" 63 + alias-controller="aliasController"
64 functions-only="functionsOnly" 64 functions-only="functionsOnly"
65 datakey-settings-schema="datakeySettingsSchema" 65 datakey-settings-schema="datakeySettingsSchema"
66 generate-data-key="generateDataKey(chip,type)" 66 generate-data-key="generateDataKey(chip,type)"
@@ -104,7 +104,7 @@ @@ -104,7 +104,7 @@
104 <v-pane-content style="padding: 0 5px;"> 104 <v-pane-content style="padding: 0 5px;">
105 <tb-entity-alias-select flex 105 <tb-entity-alias-select flex
106 tb-required="widgetType === types.widgetType.rpc.value && !widgetEditMode" 106 tb-required="widgetType === types.widgetType.rpc.value && !widgetEditMode"
107 - entity-aliases="entityAliases" 107 + alias-controller="aliasController"
108 allowed-entity-types="[types.entityType.device]" 108 allowed-entity-types="[types.entityType.device]"
109 ng-model="targetDeviceAlias.value" 109 ng-model="targetDeviceAlias.value"
110 on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias, allowedEntityTypes: allowedEntityTypes})"> 110 on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias, allowedEntityTypes: allowedEntityTypes})">
@@ -20,9 +20,9 @@ import Subscription from '../api/subscription'; @@ -20,9 +20,9 @@ import Subscription from '../api/subscription';
20 /* eslint-disable angular/angularelement */ 20 /* eslint-disable angular/angularelement */
21 21
22 /*@ngInject*/ 22 /*@ngInject*/
23 -export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, tbRaf, types, utils, timeService, 23 +export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, $compile, tbRaf, types, utils, timeService,
24 datasourceService, entityService, deviceService, visibleRect, isEdit, stDiff, dashboardTimewindow, 24 datasourceService, entityService, deviceService, visibleRect, isEdit, stDiff, dashboardTimewindow,
25 - dashboardTimewindowApi, widget, aliasesInfo, stateController, widgetType) { 25 + dashboardTimewindowApi, widget, aliasController, stateController, widgetInfo, widgetType) {
26 26
27 var vm = this; 27 var vm = this;
28 28
@@ -37,23 +37,16 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -37,23 +37,16 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
37 $scope.executingRpcRequest = false; 37 $scope.executingRpcRequest = false;
38 38
39 var gridsterItemInited = false; 39 var gridsterItemInited = false;
  40 + var subscriptionInited = false;
  41 + var widgetSizeDetected = false;
40 42
41 var cafs = {}; 43 var cafs = {};
42 44
43 - /*  
44 - * data = array of datasourceData  
45 - * datasourceData = {  
46 - * tbDatasource,  
47 - * dataKey, { name, config }  
48 - * data = array of [time, value]  
49 - * }  
50 - */  
51 -  
52 var widgetContext = { 45 var widgetContext = {
53 inited: false, 46 inited: false,
54 $scope: $scope, 47 $scope: $scope,
55 - $container: $('#container', $element),  
56 - $containerParent: $($element), 48 + $container: null,
  49 + $containerParent: null,
57 width: 0, 50 width: 0,
58 height: 0, 51 height: 0,
59 isEdit: isEdit, 52 isEdit: isEdit,
@@ -80,30 +73,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -80,30 +73,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
80 createSubscription: function(options, subscribe) { 73 createSubscription: function(options, subscribe) {
81 return createSubscription(options, subscribe); 74 return createSubscription(options, subscribe);
82 }, 75 },
83 -  
84 -  
85 - // type: "timeseries" or "latest" or "rpc"  
86 - /* subscriptionInfo = [  
87 - {  
88 - entityType: ""  
89 - entityId: ""  
90 - entityName: ""  
91 - timeseries: [{ name: "", label: "" }, ..]  
92 - attributes: [{ name: "", label: "" }, ..]  
93 - }  
94 - ..  
95 - ]*/  
96 -  
97 - // options = {  
98 - // timeWindowConfig,  
99 - // useDashboardTimewindow,  
100 - // legendConfig,  
101 - // decimals,  
102 - // units,  
103 - // callbacks [ onDataUpdated(subscription, apply) ]  
104 - // }  
105 - //  
106 -  
107 createSubscriptionFromInfo: function (type, subscriptionsInfo, options, useDefaultComponents, subscribe) { 76 createSubscriptionFromInfo: function (type, subscriptionsInfo, options, useDefaultComponents, subscribe) {
108 return createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe); 77 return createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe);
109 }, 78 },
@@ -149,7 +118,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -149,7 +118,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
149 dashboardTimewindowApi: dashboardTimewindowApi, 118 dashboardTimewindowApi: dashboardTimewindowApi,
150 types: types, 119 types: types,
151 stDiff: stDiff, 120 stDiff: stDiff,
152 - aliasesInfo: aliasesInfo 121 + aliasController: aliasController
153 }; 122 };
154 123
155 var widgetTypeInstance; 124 var widgetTypeInstance;
@@ -203,23 +172,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -203,23 +172,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
203 172
204 vm.gridsterItemInitialized = gridsterItemInitialized; 173 vm.gridsterItemInitialized = gridsterItemInitialized;
205 174
206 - initialize();  
207 -  
208 -  
209 - /*  
210 - options = {  
211 - type,  
212 - targetDeviceAliasIds, // RPC  
213 - targetDeviceIds, // RPC  
214 - datasources,  
215 - timeWindowConfig,  
216 - useDashboardTimewindow,  
217 - legendConfig,  
218 - decimals,  
219 - units,  
220 - callbacks  
221 - }  
222 - */ 175 + initialize().then(
  176 + function(){
  177 + onInit();
  178 + }
  179 + );
223 180
224 function createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe) { 181 function createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe) {
225 var deferred = $q.defer(); 182 var deferred = $q.defer();
@@ -233,28 +190,42 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -233,28 +190,42 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
233 } 190 }
234 } 191 }
235 192
236 - entityService.createDatasoucesFromSubscriptionsInfo(subscriptionsInfo).then( 193 + entityService.createDatasourcesFromSubscriptionsInfo(subscriptionsInfo).then(
237 function (datasources) { 194 function (datasources) {
238 options.datasources = datasources; 195 options.datasources = datasources;
239 - var subscription = createSubscription(options, subscribe);  
240 - if (useDefaultComponents) {  
241 - defaultSubscriptionOptions(subscription, options);  
242 - }  
243 - deferred.resolve(subscription); 196 + createSubscription(options, subscribe).then(
  197 + function success(subscription) {
  198 + if (useDefaultComponents) {
  199 + defaultSubscriptionOptions(subscription, options);
  200 + }
  201 + deferred.resolve(subscription);
  202 + },
  203 + function fail() {
  204 + deferred.reject();
  205 + }
  206 + );
244 } 207 }
245 ); 208 );
246 return deferred.promise; 209 return deferred.promise;
247 } 210 }
248 211
249 function createSubscription(options, subscribe) { 212 function createSubscription(options, subscribe) {
  213 + var deferred = $q.defer();
250 options.dashboardTimewindow = dashboardTimewindow; 214 options.dashboardTimewindow = dashboardTimewindow;
251 - var subscription =  
252 - new Subscription(subscriptionContext, options);  
253 - widgetContext.subscriptions[subscription.id] = subscription;  
254 - if (subscribe) {  
255 - subscription.subscribe();  
256 - }  
257 - return subscription; 215 + new Subscription(subscriptionContext, options).then(
  216 + function success(subscription) {
  217 + widgetContext.subscriptions[subscription.id] = subscription;
  218 + if (subscribe) {
  219 + subscription.subscribe();
  220 + }
  221 + deferred.resolve(subscription);
  222 + },
  223 + function fail() {
  224 + deferred.reject();
  225 + }
  226 + );
  227 +
  228 + return deferred.promise;
258 } 229 }
259 230
260 function defaultComponentsOptions(options) { 231 function defaultComponentsOptions(options) {
@@ -310,8 +281,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -310,8 +281,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
310 } 281 }
311 282
312 function createDefaultSubscription() { 283 function createDefaultSubscription() {
313 - var subscription;  
314 var options; 284 var options;
  285 + var deferred = $q.defer();
315 if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) { 286 if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) {
316 options = { 287 options = {
317 type: widget.type, 288 type: widget.type,
@@ -319,16 +290,23 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -319,16 +290,23 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
319 }; 290 };
320 defaultComponentsOptions(options); 291 defaultComponentsOptions(options);
321 292
322 - subscription = createSubscription(options);  
323 -  
324 - defaultSubscriptionOptions(subscription, options); 293 + createSubscription(options).then(
  294 + function success(subscription) {
  295 + defaultSubscriptionOptions(subscription, options);
325 296
326 - // backward compatibility 297 + // backward compatibility
327 298
328 - widgetContext.datasources = subscription.datasources;  
329 - widgetContext.data = subscription.data;  
330 - widgetContext.hiddenData = subscription.hiddenData;  
331 - widgetContext.timeWindow = subscription.timeWindow; 299 + widgetContext.datasources = subscription.datasources;
  300 + widgetContext.data = subscription.data;
  301 + widgetContext.hiddenData = subscription.hiddenData;
  302 + widgetContext.timeWindow = subscription.timeWindow;
  303 + widgetContext.defaultSubscription = subscription;
  304 + deferred.resolve();
  305 + },
  306 + function fail() {
  307 + deferred.reject();
  308 + }
  309 + );
332 310
333 } else if (widget.type === types.widgetType.rpc.value) { 311 } else if (widget.type === types.widgetType.rpc.value) {
334 $scope.loadingData = false; 312 $scope.loadingData = false;
@@ -356,30 +334,126 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -356,30 +334,126 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
356 $scope.rpcRejection = null; 334 $scope.rpcRejection = null;
357 } 335 }
358 } 336 }
359 - subscription = createSubscription(options); 337 + createSubscription(options).then(
  338 + function success(subscription) {
  339 + widgetContext.defaultSubscription = subscription;
  340 + deferred.resolve();
  341 + },
  342 + function fail() {
  343 + deferred.reject();
  344 + }
  345 + );
360 } else if (widget.type === types.widgetType.static.value) { 346 } else if (widget.type === types.widgetType.static.value) {
361 $scope.loadingData = false; 347 $scope.loadingData = false;
  348 + deferred.resolve();
  349 + } else {
  350 + deferred.resolve();
362 } 351 }
363 - if (subscription) {  
364 - widgetContext.defaultSubscription = subscription;  
365 - } 352 + return deferred.promise;
366 } 353 }
367 354
  355 + function configureWidgetElement() {
368 356
369 - function initialize() { 357 + $scope.displayLegend = angular.isDefined(widget.config.showLegend) ?
  358 + widget.config.showLegend : widget.type === types.widgetType.timeseries.value;
370 359
371 - if (!vm.useCustomDatasources) {  
372 - createDefaultSubscription(); 360 + if ($scope.displayLegend) {
  361 + $scope.legendConfig = widget.config.legendConfig ||
  362 + {
  363 + position: types.position.bottom.value,
  364 + showMin: false,
  365 + showMax: false,
  366 + showAvg: widget.type === types.widgetType.timeseries.value,
  367 + showTotal: false
  368 + };
  369 + $scope.legendData = {
  370 + keys: [],
  371 + data: []
  372 + };
  373 + }
  374 +
  375 + var html = '<div class="tb-absolute-fill tb-widget-error" ng-if="widgetErrorData">' +
  376 + '<span>Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}</span>' +
  377 + '</div>' +
  378 + '<div class="tb-absolute-fill tb-widget-loading" ng-show="loadingData" layout="column" layout-align="center center">' +
  379 + '<md-progress-circular md-mode="indeterminate" ng-disabled="!loadingData" class="md-accent" md-diameter="40"></md-progress-circular>' +
  380 + '</div>';
  381 +
  382 + var containerHtml = '<div id="container">' + widgetInfo.templateHtml + '</div>';
  383 + if ($scope.displayLegend) {
  384 + var layoutType;
  385 + if ($scope.legendConfig.position === types.position.top.value ||
  386 + $scope.legendConfig.position === types.position.bottom.value) {
  387 + layoutType = 'column';
  388 + } else {
  389 + layoutType = 'row';
  390 + }
  391 +
  392 + var legendStyle;
  393 + switch($scope.legendConfig.position) {
  394 + case types.position.top.value:
  395 + legendStyle = 'padding-bottom: 8px;';
  396 + break;
  397 + case types.position.bottom.value:
  398 + legendStyle = 'padding-top: 8px;';
  399 + break;
  400 + case types.position.left.value:
  401 + legendStyle = 'padding-right: 0px;';
  402 + break;
  403 + case types.position.right.value:
  404 + legendStyle = 'padding-left: 0px;';
  405 + break;
  406 + }
  407 +
  408 + var legendHtml = '<tb-legend style="'+legendStyle+'" legend-config="legendConfig" legend-data="legendData"></tb-legend>';
  409 + containerHtml = '<div flex id="widget-container">' + containerHtml + '</div>';
  410 + html += '<div class="tb-absolute-fill" layout="'+layoutType+'">';
  411 + if ($scope.legendConfig.position === types.position.top.value ||
  412 + $scope.legendConfig.position === types.position.left.value) {
  413 + html += legendHtml;
  414 + html += containerHtml;
  415 + } else {
  416 + html += containerHtml;
  417 + html += legendHtml;
  418 + }
  419 + html += '</div>';
373 } else { 420 } else {
374 - $scope.loadingData = false; 421 + html += containerHtml;
375 } 422 }
376 423
  424 + //TODO:
  425 + /*if (progressElement) {
  426 + progressScope.$destroy();
  427 + progressScope = null;
  428 +
  429 + progressElement.remove();
  430 + progressElement = null;
  431 + }*/
  432 +
  433 + $element.html(html);
  434 +
  435 + var containerElement = $scope.displayLegend ? angular.element($element[0].querySelector('#widget-container')) : $element;
  436 + widgetContext.$container = $('#container', containerElement);
  437 + widgetContext.$containerParent = $(containerElement);
  438 +
  439 + $compile($element.contents())($scope);
  440 +
  441 + addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef
  442 + }
  443 +
  444 + function destroyWidgetElement() {
  445 + removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef
  446 + $element.html('');
  447 + widgetContext.$container = null;
  448 + widgetContext.$containerParent = null;
  449 + }
  450 +
  451 + function initialize() {
  452 +
377 $scope.$on('toggleDashboardEditMode', function (event, isEdit) { 453 $scope.$on('toggleDashboardEditMode', function (event, isEdit) {
378 onEditModeChanged(isEdit); 454 onEditModeChanged(isEdit);
379 }); 455 });
380 456
381 - addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef  
382 -  
383 $scope.$watch(function () { 457 $scope.$watch(function () {
384 return widget.row + ',' + widget.col + ',' + widget.config.mobileOrder; 458 return widget.row + ',' + widget.col + ',' + widget.config.mobileOrder;
385 }, function () { 459 }, function () {
@@ -398,18 +472,60 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -398,18 +472,60 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
398 onMobileModeChanged(newIsMobile); 472 onMobileModeChanged(newIsMobile);
399 }); 473 });
400 474
401 - $scope.$on('entityAliasListChanged', function (event, aliasesInfo) {  
402 - subscriptionContext.aliasesInfo = aliasesInfo; 475 + $scope.$on('entityAliasesChanged', function (event, aliasIds) {
  476 + var subscriptionChanged = false;
403 for (var id in widgetContext.subscriptions) { 477 for (var id in widgetContext.subscriptions) {
404 var subscription = widgetContext.subscriptions[id]; 478 var subscription = widgetContext.subscriptions[id];
405 - subscription.onAliasesChanged(); 479 + subscriptionChanged = subscriptionChanged || subscription.onAliasesChanged(aliasIds);
  480 + }
  481 + if (subscriptionChanged && !vm.useCustomDatasources) {
  482 + reInit();
406 } 483 }
407 }); 484 });
408 485
409 $scope.$on("$destroy", function () { 486 $scope.$on("$destroy", function () {
410 - removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef  
411 onDestroy(); 487 onDestroy();
412 }); 488 });
  489 +
  490 + configureWidgetElement();
  491 + var deferred = $q.defer();
  492 + if (!vm.useCustomDatasources) {
  493 + createDefaultSubscription().then(
  494 + function success() {
  495 + subscriptionInited = true;
  496 + deferred.resolve();
  497 + },
  498 + function fail() {
  499 + subscriptionInited = true;
  500 + deferred.reject();
  501 + }
  502 + );
  503 + } else {
  504 + $scope.loadingData = false;
  505 + subscriptionInited = true;
  506 + deferred.resolve();
  507 + }
  508 + return deferred.promise;
  509 + }
  510 +
  511 + function reInit() {
  512 + onDestroy();
  513 + configureWidgetElement();
  514 + if (!vm.useCustomDatasources) {
  515 + createDefaultSubscription().then(
  516 + function success() {
  517 + subscriptionInited = true;
  518 + onInit();
  519 + },
  520 + function fail() {
  521 + subscriptionInited = true;
  522 + onInit();
  523 + }
  524 + );
  525 + } else {
  526 + subscriptionInited = true;
  527 + onInit();
  528 + }
413 } 529 }
414 530
415 function handleWidgetException(e) { 531 function handleWidgetException(e) {
@@ -417,8 +533,15 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -417,8 +533,15 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
417 $scope.widgetErrorData = utils.processWidgetException(e); 533 $scope.widgetErrorData = utils.processWidgetException(e);
418 } 534 }
419 535
420 - function onInit() {  
421 - if (!widgetContext.inited) { 536 + function isReady() {
  537 + return subscriptionInited && gridsterItemInited && widgetSizeDetected;
  538 + }
  539 +
  540 + function onInit(skipSizeCheck) {
  541 + if (!skipSizeCheck) {
  542 + checkSize();
  543 + }
  544 + if (!widgetContext.inited && isReady()) {
422 widgetContext.inited = true; 545 widgetContext.inited = true;
423 try { 546 try {
424 widgetTypeInstance.onInit(); 547 widgetTypeInstance.onInit();
@@ -443,6 +566,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -443,6 +566,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
443 widgetContext.width = width; 566 widgetContext.width = width;
444 widgetContext.height = height; 567 widgetContext.height = height;
445 sizeChanged = true; 568 sizeChanged = true;
  569 + widgetSizeDetected = true;
446 } 570 }
447 } 571 }
448 return sizeChanged; 572 return sizeChanged;
@@ -462,8 +586,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -462,8 +586,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
462 handleWidgetException(e); 586 handleWidgetException(e);
463 } 587 }
464 }); 588 });
465 - } else if (gridsterItemInited) {  
466 - onInit(); 589 + } else {
  590 + onInit(true);
467 } 591 }
468 } 592 }
469 } 593 }
@@ -472,9 +596,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -472,9 +596,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
472 if (item && item.gridster) { 596 if (item && item.gridster) {
473 widgetContext.isMobile = item.gridster.isMobile; 597 widgetContext.isMobile = item.gridster.isMobile;
474 gridsterItemInited = true; 598 gridsterItemInited = true;
475 - if (checkSize()) {  
476 - onInit();  
477 - } 599 + onInit();
478 // gridsterItemElement = $(item.$element); 600 // gridsterItemElement = $(item.$element);
479 //updateVisibility(); 601 //updateVisibility();
480 } 602 }
@@ -544,6 +666,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -544,6 +666,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
544 var subscription = widgetContext.subscriptions[id]; 666 var subscription = widgetContext.subscriptions[id];
545 subscription.destroy(); 667 subscription.destroy();
546 } 668 }
  669 + subscriptionInited = false;
547 widgetContext.subscriptions = []; 670 widgetContext.subscriptions = [];
548 if (widgetContext.inited) { 671 if (widgetContext.inited) {
549 widgetContext.inited = false; 672 widgetContext.inited = false;
@@ -559,6 +682,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -559,6 +682,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
559 handleWidgetException(e); 682 handleWidgetException(e);
560 } 683 }
561 } 684 }
  685 + destroyWidgetElement();
562 } 686 }
563 687
564 //TODO: widgets visibility 688 //TODO: widgets visibility
@@ -28,7 +28,7 @@ export default angular.module('thingsboard.directives.widget', [thingsboardLegen @@ -28,7 +28,7 @@ export default angular.module('thingsboard.directives.widget', [thingsboardLegen
28 .name; 28 .name;
29 29
30 /*@ngInject*/ 30 /*@ngInject*/
31 -function Widget($controller, $compile, types, widgetService) { 31 +function Widget($controller, widgetService) {
32 return { 32 return {
33 scope: true, 33 scope: true,
34 link: function (scope, elem, attrs) { 34 link: function (scope, elem, attrs) {
@@ -81,90 +81,9 @@ function Widget($controller, $compile, types, widgetService) { @@ -81,90 +81,9 @@ function Widget($controller, $compile, types, widgetService) {
81 81
82 elem.addClass(widgetNamespace); 82 elem.addClass(widgetNamespace);
83 83
84 - var html = '<div class="tb-absolute-fill tb-widget-error" ng-if="widgetErrorData">' +  
85 - '<span>Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}</span>' +  
86 - '</div>' +  
87 - '<div class="tb-absolute-fill tb-widget-loading" ng-show="loadingData" layout="column" layout-align="center center">' +  
88 - '<md-progress-circular md-mode="indeterminate" ng-disabled="!loadingData" class="md-accent" md-diameter="40"></md-progress-circular>' +  
89 - '</div>';  
90 -  
91 - scope.displayLegend = angular.isDefined(widget.config.showLegend) ?  
92 - widget.config.showLegend : widget.type === types.widgetType.timeseries.value;  
93 -  
94 -  
95 - var containerHtml = '<div id="container">' + widgetInfo.templateHtml + '</div>';  
96 - if (scope.displayLegend) {  
97 - scope.legendConfig = widget.config.legendConfig ||  
98 - {  
99 - position: types.position.bottom.value,  
100 - showMin: false,  
101 - showMax: false,  
102 - showAvg: widget.type === types.widgetType.timeseries.value,  
103 - showTotal: false  
104 - };  
105 - scope.legendData = {  
106 - keys: [],  
107 - data: []  
108 - };  
109 -  
110 - var layoutType;  
111 - if (scope.legendConfig.position === types.position.top.value ||  
112 - scope.legendConfig.position === types.position.bottom.value) {  
113 - layoutType = 'column';  
114 - } else {  
115 - layoutType = 'row';  
116 - }  
117 -  
118 - var legendStyle;  
119 - switch(scope.legendConfig.position) {  
120 - case types.position.top.value:  
121 - legendStyle = 'padding-bottom: 8px;';  
122 - break;  
123 - case types.position.bottom.value:  
124 - legendStyle = 'padding-top: 8px;';  
125 - break;  
126 - case types.position.left.value:  
127 - legendStyle = 'padding-right: 0px;';  
128 - break;  
129 - case types.position.right.value:  
130 - legendStyle = 'padding-left: 0px;';  
131 - break;  
132 - }  
133 -  
134 - var legendHtml = '<tb-legend style="'+legendStyle+'" legend-config="legendConfig" legend-data="legendData"></tb-legend>';  
135 - containerHtml = '<div flex id="widget-container">' + containerHtml + '</div>';  
136 - html += '<div class="tb-absolute-fill" layout="'+layoutType+'">';  
137 - if (scope.legendConfig.position === types.position.top.value ||  
138 - scope.legendConfig.position === types.position.left.value) {  
139 - html += legendHtml;  
140 - html += containerHtml;  
141 - } else {  
142 - html += containerHtml;  
143 - html += legendHtml;  
144 - }  
145 - html += '</div>';  
146 - } else {  
147 - html += containerHtml;  
148 - }  
149 -  
150 - //TODO:  
151 - /*if (progressElement) {  
152 - progressScope.$destroy();  
153 - progressScope = null;  
154 -  
155 - progressElement.remove();  
156 - progressElement = null;  
157 - }*/  
158 -  
159 - elem.html(html);  
160 -  
161 - var containerElement = scope.displayLegend ? angular.element(elem[0].querySelector('#widget-container')) : elem;  
162 -  
163 - $compile(elem.contents())(scope);  
164 -  
165 var widgetType = widgetService.getWidgetTypeFunction(widget.bundleAlias, widget.typeAlias, widget.isSystemType); 84 var widgetType = widgetService.getWidgetTypeFunction(widget.bundleAlias, widget.typeAlias, widget.isSystemType);
166 85
167 - angular.extend(locals, {$scope: scope, $element: containerElement, widgetType: widgetType}); 86 + angular.extend(locals, {$scope: scope, $element: elem, widgetInfo: widgetInfo, widgetType: widgetType});
168 87
169 widgetController = $controller('WidgetController', locals); 88 widgetController = $controller('WidgetController', locals);
170 89
@@ -15,17 +15,18 @@ @@ -15,17 +15,18 @@
15 */ 15 */
16 /* eslint-disable import/no-unresolved, import/default */ 16 /* eslint-disable import/no-unresolved, import/default */
17 17
18 -import entityAliasesTemplate from '../entity/entity-aliases.tpl.html'; 18 +import entityAliasDialogTemplate from '../entity/alias/entity-alias-dialog.tpl.html';
19 19
20 /* eslint-enable import/no-unresolved, import/default */ 20 /* eslint-enable import/no-unresolved, import/default */
21 21
22 /*@ngInject*/ 22 /*@ngInject*/
23 -export default function AddWidgetController($scope, widgetService, entityService, $mdDialog, $q, $document, types, dashboard, aliasesInfo, widget, widgetInfo) { 23 +export default function AddWidgetController($scope, widgetService, entityService, $mdDialog, $q, $document, types, dashboard,
  24 + aliasController, widget, widgetInfo) {
24 25
25 var vm = this; 26 var vm = this;
26 27
27 vm.dashboard = dashboard; 28 vm.dashboard = dashboard;
28 - vm.aliasesInfo = aliasesInfo; 29 + vm.aliasController = aliasController;
29 vm.widget = widget; 30 vm.widget = widget;
30 vm.widgetInfo = widgetInfo; 31 vm.widgetInfo = widgetInfo;
31 32
@@ -85,7 +86,7 @@ export default function AddWidgetController($scope, widgetService, entityService @@ -85,7 +86,7 @@ export default function AddWidgetController($scope, widgetService, entityService
85 } 86 }
86 87
87 function cancel () { 88 function cancel () {
88 - $mdDialog.cancel({aliasesInfo: vm.aliasesInfo}); 89 + $mdDialog.cancel();
89 } 90 }
90 91
91 function add () { 92 function add () {
@@ -94,52 +95,58 @@ export default function AddWidgetController($scope, widgetService, entityService @@ -94,52 +95,58 @@ export default function AddWidgetController($scope, widgetService, entityService
94 vm.widget.config = vm.widgetConfig.config; 95 vm.widget.config = vm.widgetConfig.config;
95 vm.widget.config.mobileOrder = vm.widgetConfig.layout.mobileOrder; 96 vm.widget.config.mobileOrder = vm.widgetConfig.layout.mobileOrder;
96 vm.widget.config.mobileHeight = vm.widgetConfig.layout.mobileHeight; 97 vm.widget.config.mobileHeight = vm.widgetConfig.layout.mobileHeight;
97 - $mdDialog.hide({widget: vm.widget, aliasesInfo: vm.aliasesInfo}); 98 + $mdDialog.hide({widget: vm.widget});
98 } 99 }
99 } 100 }
100 101
101 function fetchEntityKeys (entityAliasId, query, type) { 102 function fetchEntityKeys (entityAliasId, query, type) {
102 - var entityAlias = vm.aliasesInfo.entityAliases[entityAliasId];  
103 - if (entityAlias && entityAlias.entityId) {  
104 - return entityService.getEntityKeys(entityAlias.entityType, entityAlias.entityId, query, type);  
105 - } else {  
106 - return $q.when([]);  
107 - } 103 + var deferred = $q.defer();
  104 + vm.aliasController.getAliasInfo(entityAliasId).then(
  105 + function success(aliasInfo) {
  106 + var entity = aliasInfo.currentEntity;
  107 + if (entity) {
  108 + entityService.getEntityKeys(entity.entityType, entity.id, query, type).then(
  109 + function success(keys) {
  110 + deferred.resolve(keys);
  111 + },
  112 + function fail() {
  113 + deferred.resolve([]);
  114 + }
  115 + );
  116 + } else {
  117 + deferred.resolve([]);
  118 + }
  119 + },
  120 + function fail() {
  121 + deferred.resolve([]);
  122 + }
  123 + );
  124 + return deferred.promise;
108 } 125 }
109 126
110 function createEntityAlias (event, alias, allowedEntityTypes) { 127 function createEntityAlias (event, alias, allowedEntityTypes) {
111 128
112 var deferred = $q.defer(); 129 var deferred = $q.defer();
113 - var singleEntityAlias = {id: null, alias: alias, entityType: types.entityType.device, entityFilter: null}; 130 + var singleEntityAlias = {id: null, alias: alias, filter: {}};
114 131
115 $mdDialog.show({ 132 $mdDialog.show({
116 - controller: 'EntityAliasesController', 133 + controller: 'EntityAliasDialogController',
117 controllerAs: 'vm', 134 controllerAs: 'vm',
118 - templateUrl: entityAliasesTemplate, 135 + templateUrl: entityAliasDialogTemplate,
119 locals: { 136 locals: {
120 - config: {  
121 - entityAliases: angular.copy(vm.dashboard.configuration.entityAliases),  
122 - widgets: null,  
123 - isSingleEntityAlias: true,  
124 - singleEntityAlias: singleEntityAlias,  
125 - allowedEntityTypes: allowedEntityTypes  
126 - } 137 + isAdd: true,
  138 + allowedEntityTypes: allowedEntityTypes,
  139 + entityAliases: vm.dashboard.configuration.entityAliases,
  140 + alias: singleEntityAlias
127 }, 141 },
128 parent: angular.element($document[0].body), 142 parent: angular.element($document[0].body),
129 fullscreen: true, 143 fullscreen: true,
130 skipHide: true, 144 skipHide: true,
131 targetEvent: event 145 targetEvent: event
132 }).then(function (singleEntityAlias) { 146 }).then(function (singleEntityAlias) {
133 - vm.dashboard.configuration.entityAliases[singleEntityAlias.id] =  
134 - { alias: singleEntityAlias.alias, entityType: singleEntityAlias.entityType, entityFilter: singleEntityAlias.entityFilter };  
135 - entityService.processEntityAliases(vm.dashboard.configuration.entityAliases).then(  
136 - function(resolution) {  
137 - if (!resolution.error) {  
138 - vm.aliasesInfo = resolution.aliasesInfo;  
139 - }  
140 - deferred.resolve(singleEntityAlias);  
141 - }  
142 - ); 147 + vm.dashboard.configuration.entityAliases[singleEntityAlias.id] = singleEntityAlias;
  148 + vm.aliasController.updateEntityAliases(vm.dashboard.configuration.entityAliases);
  149 + deferred.resolve(singleEntityAlias);
143 }, function () { 150 }, function () {
144 deferred.reject(); 151 deferred.reject();
145 }); 152 });
@@ -37,7 +37,7 @@ @@ -37,7 +37,7 @@
37 ng-model="vm.widgetConfig" 37 ng-model="vm.widgetConfig"
38 widget-settings-schema="vm.settingsSchema" 38 widget-settings-schema="vm.settingsSchema"
39 datakey-settings-schema="vm.dataKeySettingsSchema" 39 datakey-settings-schema="vm.dataKeySettingsSchema"
40 - entity-aliases="vm.aliasesInfo.entityAliases" 40 + alias-controller="vm.aliasController"
41 functions-only="vm.functionsOnly" 41 functions-only="vm.functionsOnly"
42 fetch-entity-keys="vm.fetchEntityKeys(entityAliasId, query, type)" 42 fetch-entity-keys="vm.fetchEntityKeys(entityAliasId, query, type)"
43 on-create-entity-alias="vm.createEntityAlias(event, alias, allowedEntityTypes)" 43 on-create-entity-alias="vm.createEntityAlias(event, alias, allowedEntityTypes)"
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 */ 15 */
16 /* eslint-disable import/no-unresolved, import/default */ 16 /* eslint-disable import/no-unresolved, import/default */
17 17
18 -import entityAliasesTemplate from '../entity/entity-aliases.tpl.html'; 18 +import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html';
19 import dashboardSettingsTemplate from './dashboard-settings.tpl.html'; 19 import dashboardSettingsTemplate from './dashboard-settings.tpl.html';
20 import manageDashboardLayoutsTemplate from './layouts/manage-dashboard-layouts.tpl.html'; 20 import manageDashboardLayoutsTemplate from './layouts/manage-dashboard-layouts.tpl.html';
21 import manageDashboardStatesTemplate from './states/manage-dashboard-states.tpl.html'; 21 import manageDashboardStatesTemplate from './states/manage-dashboard-states.tpl.html';
@@ -24,8 +24,10 @@ import selectTargetLayoutTemplate from './layouts/select-target-layout.tpl.html' @@ -24,8 +24,10 @@ import selectTargetLayoutTemplate from './layouts/select-target-layout.tpl.html'
24 24
25 /* eslint-enable import/no-unresolved, import/default */ 25 /* eslint-enable import/no-unresolved, import/default */
26 26
  27 +import AliasController from '../api/alias-controller';
  28 +
27 /*@ngInject*/ 29 /*@ngInject*/
28 -export default function DashboardController(types, dashboardUtils, widgetService, userService, 30 +export default function DashboardController(types, utils, dashboardUtils, widgetService, userService,
29 dashboardService, timeService, entityService, itembuffer, importExport, hotkeys, $window, $rootScope, 31 dashboardService, timeService, entityService, itembuffer, importExport, hotkeys, $window, $rootScope,
30 $scope, $element, $state, $stateParams, $mdDialog, $mdMedia, $timeout, $document, $q, $translate, $filter) { 32 $scope, $element, $state, $stateParams, $mdDialog, $mdMedia, $timeout, $document, $q, $translate, $filter) {
31 33
@@ -342,6 +344,8 @@ export default function DashboardController(types, dashboardUtils, widgetService @@ -342,6 +344,8 @@ export default function DashboardController(types, dashboardUtils, widgetService
342 vm.dashboardConfiguration = vm.dashboard.configuration; 344 vm.dashboardConfiguration = vm.dashboard.configuration;
343 vm.dashboardCtx.dashboard = vm.dashboard; 345 vm.dashboardCtx.dashboard = vm.dashboard;
344 vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow; 346 vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow;
  347 + vm.dashboardCtx.aliasController = new AliasController($scope, $q, $filter, utils,
  348 + types, entityService, vm.dashboardCtx.stateController, vm.dashboardConfiguration.entityAliases);
345 var parentScope = $window.parent.angular.element($window.frameElement).scope(); 349 var parentScope = $window.parent.angular.element($window.frameElement).scope();
346 parentScope.$root.$broadcast('widgetEditModeInited'); 350 parentScope.$root.$broadcast('widgetEditModeInited');
347 parentScope.$root.$apply(); 351 parentScope.$root.$apply();
@@ -349,7 +353,13 @@ export default function DashboardController(types, dashboardUtils, widgetService @@ -349,7 +353,13 @@ export default function DashboardController(types, dashboardUtils, widgetService
349 dashboardService.getDashboard($stateParams.dashboardId) 353 dashboardService.getDashboard($stateParams.dashboardId)
350 .then(function success(dashboard) { 354 .then(function success(dashboard) {
351 vm.dashboard = dashboardUtils.validateAndUpdateDashboard(dashboard); 355 vm.dashboard = dashboardUtils.validateAndUpdateDashboard(dashboard);
352 - entityService.processEntityAliases(vm.dashboard.configuration.entityAliases) 356 + vm.dashboardConfiguration = vm.dashboard.configuration;
  357 + vm.dashboardCtx.dashboard = vm.dashboard;
  358 + vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow;
  359 + vm.dashboardCtx.aliasController = new AliasController($scope, $q, $filter, utils,
  360 + types, entityService, vm.dashboardCtx.stateController, vm.dashboardConfiguration.entityAliases);
  361 +
  362 + /* entityService.processEntityAliases(vm.dashboard.configuration.entityAliases)
353 .then( 363 .then(
354 function(resolution) { 364 function(resolution) {
355 if (resolution.error && !isTenantAdmin()) { 365 if (resolution.error && !isTenantAdmin()) {
@@ -362,7 +372,7 @@ export default function DashboardController(types, dashboardUtils, widgetService @@ -362,7 +372,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
362 vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow; 372 vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow;
363 } 373 }
364 } 374 }
365 - ); 375 + );*/
366 }, function fail() { 376 }, function fail() {
367 vm.configurationError = true; 377 vm.configurationError = true;
368 }); 378 });
@@ -373,6 +383,7 @@ export default function DashboardController(types, dashboardUtils, widgetService @@ -373,6 +383,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
373 var layoutsData = dashboardUtils.getStateLayoutsData(vm.dashboard, state); 383 var layoutsData = dashboardUtils.getStateLayoutsData(vm.dashboard, state);
374 if (layoutsData) { 384 if (layoutsData) {
375 vm.dashboardCtx.state = state; 385 vm.dashboardCtx.state = state;
  386 + vm.dashboardCtx.aliasController.dashboardStateChanged();
376 var layoutVisibilityChanged = false; 387 var layoutVisibilityChanged = false;
377 for (var l in vm.layouts) { 388 for (var l in vm.layouts) {
378 var layout = vm.layouts[l]; 389 var layout = vm.layouts[l];
@@ -916,7 +927,7 @@ export default function DashboardController(types, dashboardUtils, widgetService @@ -916,7 +927,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
916 templateUrl: addWidgetTemplate, 927 templateUrl: addWidgetTemplate,
917 locals: { 928 locals: {
918 dashboard: vm.dashboard, 929 dashboard: vm.dashboard,
919 - aliasesInfo: vm.dashboardCtx.aliasesInfo, 930 + aliasController: vm.dashboardCtx.aliasController,
920 widget: newWidget, 931 widget: newWidget,
921 widgetInfo: widgetTypeInfo 932 widgetInfo: widgetTypeInfo
922 }, 933 },
@@ -930,10 +941,8 @@ export default function DashboardController(types, dashboardUtils, widgetService @@ -930,10 +941,8 @@ export default function DashboardController(types, dashboardUtils, widgetService
930 } 941 }
931 }).then(function (result) { 942 }).then(function (result) {
932 var widget = result.widget; 943 var widget = result.widget;
933 - vm.dashboardCtx.aliasesInfo = result.aliasesInfo;  
934 addWidget(widget); 944 addWidget(widget);
935 - }, function (rejection) {  
936 - vm.dashboardCtx.aliasesInfo = rejection.aliasesInfo; 945 + }, function () {
937 }); 946 });
938 } 947 }
939 } 948 }
@@ -1025,7 +1034,7 @@ export default function DashboardController(types, dashboardUtils, widgetService @@ -1025,7 +1034,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
1025 notifyDashboardUpdated(); 1034 notifyDashboardUpdated();
1026 } 1035 }
1027 1036
1028 - function showAliasesResolutionError(error) { 1037 +/* function showAliasesResolutionError(error) {
1029 var alert = $mdDialog.alert() 1038 var alert = $mdDialog.alert()
1030 .parent(angular.element($document[0].body)) 1039 .parent(angular.element($document[0].body))
1031 .clickOutsideToClose(true) 1040 .clickOutsideToClose(true)
@@ -1037,20 +1046,10 @@ export default function DashboardController(types, dashboardUtils, widgetService @@ -1037,20 +1046,10 @@ export default function DashboardController(types, dashboardUtils, widgetService
1037 alert._options.fullscreen = true; 1046 alert._options.fullscreen = true;
1038 1047
1039 $mdDialog.show(alert); 1048 $mdDialog.show(alert);
1040 - } 1049 + }*/
1041 1050
1042 function entityAliasesUpdated() { 1051 function entityAliasesUpdated() {
1043 - var deferred = $q.defer();  
1044 - entityService.processEntityAliases(vm.dashboard.configuration.entityAliases)  
1045 - .then(  
1046 - function(resolution) {  
1047 - if (resolution.aliasesInfo) {  
1048 - vm.dashboardCtx.aliasesInfo = resolution.aliasesInfo;  
1049 - }  
1050 - deferred.resolve();  
1051 - }  
1052 - );  
1053 - return deferred.promise; 1052 + vm.dashboardCtx.aliasController.updateEntityAliases(vm.dashboard.configuration.entityAliases);
1054 } 1053 }
1055 1054
1056 function notifyDashboardUpdated() { 1055 function notifyDashboardUpdated() {
@@ -57,8 +57,7 @@ @@ -57,8 +57,7 @@
57 </tb-timewindow> 57 </tb-timewindow>
58 <tb-aliases-entity-select ng-show="!vm.isEdit && vm.displayEntitiesSelect()" 58 <tb-aliases-entity-select ng-show="!vm.isEdit && vm.displayEntitiesSelect()"
59 tooltip-direction="bottom" 59 tooltip-direction="bottom"
60 - ng-model="vm.dashboardCtx.aliasesInfo.entityAliases"  
61 - entity-aliases-info="vm.dashboardCtx.aliasesInfo.entityAliasesInfo"> 60 + alias-controller="vm.dashboardCtx.aliasController">
62 </tb-aliases-entity-select> 61 </tb-aliases-entity-select>
63 <md-button ng-show="vm.isEdit" aria-label="{{ 'entity.aliases' | translate }}" class="md-icon-button" 62 <md-button ng-show="vm.isEdit" aria-label="{{ 'entity.aliases' | translate }}" class="md-icon-button"
64 ng-click="vm.openEntityAliases($event)"> 63 ng-click="vm.openEntityAliases($event)">
@@ -179,7 +178,7 @@ @@ -179,7 +178,7 @@
179 <form name="vm.widgetForm" ng-if="vm.isEditingWidget"> 178 <form name="vm.widgetForm" ng-if="vm.isEditingWidget">
180 <tb-edit-widget 179 <tb-edit-widget
181 dashboard="vm.dashboard" 180 dashboard="vm.dashboard"
182 - aliases-info="vm.dashboardCtx.aliasesInfo" 181 + alias-controller="vm.dashboardCtx.aliasController"
183 widget="vm.editingWidget" 182 widget="vm.editingWidget"
184 widget-layout="vm.editingWidgetLayout" 183 widget-layout="vm.editingWidgetLayout"
185 the-form="vm.widgetForm"> 184 the-form="vm.widgetForm">
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 */ 15 */
16 /* eslint-disable import/no-unresolved, import/default */ 16 /* eslint-disable import/no-unresolved, import/default */
17 17
18 -import entityAliasesTemplate from '../entity/entity-aliases.tpl.html'; 18 +import entityAliasDialogTemplate from '../entity/alias/entity-alias-dialog.tpl.html';
19 import editWidgetTemplate from './edit-widget.tpl.html'; 19 import editWidgetTemplate from './edit-widget.tpl.html';
20 20
21 /* eslint-enable import/no-unresolved, import/default */ 21 /* eslint-enable import/no-unresolved, import/default */
@@ -68,47 +68,53 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid @@ -68,47 +68,53 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
68 }); 68 });
69 69
70 scope.fetchEntityKeys = function (entityAliasId, query, type) { 70 scope.fetchEntityKeys = function (entityAliasId, query, type) {
71 - var entityAlias = scope.aliasesInfo.entityAliases[entityAliasId];  
72 - if (entityAlias && entityAlias.entityId) {  
73 - return entityService.getEntityKeys(entityAlias.entityType, entityAlias.entityId, query, type);  
74 - } else {  
75 - return $q.when([]);  
76 - } 71 + var deferred = $q.defer();
  72 + scope.aliasController.getAliasInfo(entityAliasId).then(
  73 + function success(aliasInfo) {
  74 + var entity = aliasInfo.currentEntity;
  75 + if (entity) {
  76 + entityService.getEntityKeys(entity.entityType, entity.id, query, type).then(
  77 + function success(keys) {
  78 + deferred.resolve(keys);
  79 + },
  80 + function fail() {
  81 + deferred.resolve([]);
  82 + }
  83 + );
  84 + } else {
  85 + deferred.resolve([]);
  86 + }
  87 + },
  88 + function fail() {
  89 + deferred.resolve([]);
  90 + }
  91 + );
  92 + return deferred.promise;
77 }; 93 };
78 94
79 scope.createEntityAlias = function (event, alias, allowedEntityTypes) { 95 scope.createEntityAlias = function (event, alias, allowedEntityTypes) {
80 96
81 var deferred = $q.defer(); 97 var deferred = $q.defer();
82 - var singleEntityAlias = {id: null, alias: alias, entityType: types.entityType.device, entityFilter: null}; 98 + var singleEntityAlias = {id: null, alias: alias, filter: {}};
83 99
84 $mdDialog.show({ 100 $mdDialog.show({
85 - controller: 'EntityAliasesController', 101 + controller: 'EntityAliasDialogController',
86 controllerAs: 'vm', 102 controllerAs: 'vm',
87 - templateUrl: entityAliasesTemplate, 103 + templateUrl: entityAliasDialogTemplate,
88 locals: { 104 locals: {
89 - config: {  
90 - entityAliases: angular.copy(scope.dashboard.configuration.entityAliases),  
91 - widgets: null,  
92 - isSingleEntityAlias: true,  
93 - singleEntityAlias: singleEntityAlias,  
94 - allowedEntityTypes: allowedEntityTypes  
95 - } 105 + isAdd: true,
  106 + allowedEntityTypes: allowedEntityTypes,
  107 + entityAliases: scope.dashboard.configuration.entityAliases,
  108 + alias: singleEntityAlias
96 }, 109 },
97 parent: angular.element($document[0].body), 110 parent: angular.element($document[0].body),
98 fullscreen: true, 111 fullscreen: true,
99 skipHide: true, 112 skipHide: true,
100 targetEvent: event 113 targetEvent: event
101 }).then(function (singleEntityAlias) { 114 }).then(function (singleEntityAlias) {
102 - scope.dashboard.configuration.entityAliases[singleEntityAlias.id] =  
103 - { alias: singleEntityAlias.alias, entityType: singleEntityAlias.entityType, entityFilter: singleEntityAlias.entityFilter };  
104 - entityService.processEntityAliases(scope.dashboard.configuration.entityAliases).then(  
105 - function(resolution) {  
106 - if (!resolution.error) {  
107 - scope.aliasesInfo = resolution.aliasesInfo;  
108 - }  
109 - deferred.resolve(singleEntityAlias);  
110 - }  
111 - ); 115 + scope.dashboard.configuration.entityAliases[singleEntityAlias.id] = singleEntityAlias;
  116 + scope.aliasController.updateEntityAliases(scope.dashboard.configuration.entityAliases);
  117 + deferred.resolve(singleEntityAlias);
112 }, function () { 118 }, function () {
113 deferred.reject(); 119 deferred.reject();
114 }); 120 });
@@ -124,7 +130,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid @@ -124,7 +130,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
124 link: linker, 130 link: linker,
125 scope: { 131 scope: {
126 dashboard: '=', 132 dashboard: '=',
127 - aliasesInfo: '=', 133 + aliasController: '=',
128 widget: '=', 134 widget: '=',
129 widgetLayout: '=', 135 widgetLayout: '=',
130 theForm: '=' 136 theForm: '='
@@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
21 is-data-enabled="isDataEnabled" 21 is-data-enabled="isDataEnabled"
22 widget-settings-schema="settingsSchema" 22 widget-settings-schema="settingsSchema"
23 datakey-settings-schema="dataKeySettingsSchema" 23 datakey-settings-schema="dataKeySettingsSchema"
24 - entity-aliases="aliasesInfo.entityAliases" 24 + alias-controller="aliasController"
25 functions-only="functionsOnly" 25 functions-only="functionsOnly"
26 fetch-entity-keys="fetchEntityKeys(entityAliasId, query, type)" 26 fetch-entity-keys="fetchEntityKeys(entityAliasId, query, type)"
27 on-create-entity-alias="createEntityAlias(event, alias, allowedEntityTypes)" 27 on-create-entity-alias="createEntityAlias(event, alias, allowedEntityTypes)"
@@ -45,7 +45,7 @@ @@ -45,7 +45,7 @@
45 widget-layouts="vm.layoutCtx.widgetLayouts" 45 widget-layouts="vm.layoutCtx.widgetLayouts"
46 columns="vm.layoutCtx.gridSettings.columns" 46 columns="vm.layoutCtx.gridSettings.columns"
47 margins="vm.layoutCtx.gridSettings.margins" 47 margins="vm.layoutCtx.gridSettings.margins"
48 - aliases-info="vm.dashboardCtx.aliasesInfo" 48 + alias-controller="vm.dashboardCtx.aliasController"
49 state-controller="vm.dashboardCtx.stateController" 49 state-controller="vm.dashboardCtx.stateController"
50 dashboard-timewindow="vm.dashboardCtx.dashboardTimewindow" 50 dashboard-timewindow="vm.dashboardCtx.dashboardTimewindow"
51 is-edit="vm.isEdit" 51 is-edit="vm.isEdit"
ui/src/app/entity/alias/aliases-entity-select-button.tpl.html renamed from ui/src/app/entity/aliases-entity-select-button.tpl.html
ui/src/app/entity/alias/aliases-entity-select-panel.controller.js renamed from ui/src/app/entity/aliases-entity-select-panel.controller.js
@@ -15,17 +15,35 @@ @@ -15,17 +15,35 @@
15 */ 15 */
16 16
17 /*@ngInject*/ 17 /*@ngInject*/
18 -export default function AliasesEntitySelectPanelController(mdPanelRef, $scope, types, entityAliases, entityAliasesInfo, onEntityAliasesUpdate) { 18 +export default function AliasesEntitySelectPanelController(mdPanelRef, $scope, $filter, types, aliasController, onEntityAliasesUpdate) {
19 19
20 var vm = this; 20 var vm = this;
21 vm._mdPanelRef = mdPanelRef; 21 vm._mdPanelRef = mdPanelRef;
22 - vm.entityAliases = entityAliases;  
23 - vm.entityAliasesInfo = entityAliasesInfo; 22 + vm.aliasController = aliasController;
24 vm.onEntityAliasesUpdate = onEntityAliasesUpdate; 23 vm.onEntityAliasesUpdate = onEntityAliasesUpdate;
  24 + vm.entityAliases = {};
  25 + vm.entityAliasesInfo = {};
25 26
26 - $scope.$watch('vm.entityAliases', function () {  
27 - if (onEntityAliasesUpdate) {  
28 - onEntityAliasesUpdate(vm.entityAliases); 27 + vm.currentAliasEntityChanged = currentAliasEntityChanged;
  28 +
  29 + var allEntityAliases = vm.aliasController.getEntityAliases();
  30 + for (var aliasId in allEntityAliases) {
  31 + var aliasInfo = vm.aliasController.getInstantAliasInfo(aliasId);
  32 + if (aliasInfo && !aliasInfo.resolveMultiple && aliasInfo.currentEntity) {
  33 + vm.entityAliasesInfo[aliasId] = angular.copy(aliasInfo);
  34 + vm.entityAliasesInfo[aliasId].selectedId = aliasInfo.currentEntity.id;
  35 + }
  36 + }
  37 +
  38 + function currentAliasEntityChanged(aliasId, selectedId) {
  39 + var resolvedEntities = vm.entityAliasesInfo[aliasId].resolvedEntities;
  40 + var selected = $filter('filter')(resolvedEntities, {id: selectedId});
  41 + if (selected && selected.length) {
  42 + vm.aliasController.updateCurrentAliasEntity(aliasId, selected[0]);
  43 + if (onEntityAliasesUpdate) {
  44 + onEntityAliasesUpdate();
  45 + }
29 } 46 }
30 - }, true); 47 + }
  48 +
31 } 49 }
ui/src/app/entity/alias/aliases-entity-select-panel.tpl.html renamed from ui/src/app/entity/aliases-entity-select-panel.tpl.html
@@ -18,12 +18,12 @@ @@ -18,12 +18,12 @@
18 <md-content flex layout="column"> 18 <md-content flex layout="column">
19 <section flex layout="column"> 19 <section flex layout="column">
20 <md-content flex class="md-padding" layout="column"> 20 <md-content flex class="md-padding" layout="column">
21 - <div flex layout="row" ng-repeat="(aliasId, entityAlias) in vm.entityAliases"> 21 + <div flex layout="row" ng-repeat="(aliasId, entityAliasInfo) in vm.entityAliasesInfo">
22 <md-input-container flex> 22 <md-input-container flex>
23 - <label>{{entityAlias.alias}}</label>  
24 - <md-select ng-model="vm.entityAliases[aliasId].entityId">  
25 - <md-option ng-repeat="entityInfo in vm.entityAliasesInfo[aliasId]" ng-value="entityInfo.id">  
26 - {{entityInfo.name}} 23 + <label>{{entityAliasInfo.alias}}</label>
  24 + <md-select ng-model="entityAliasInfo.selectedId" ng-change="vm.currentAliasEntityChanged(aliasId, entityAliasInfo.selectedId)">
  25 + <md-option ng-repeat="resolvedEntity in entityAliasInfo.resolvedEntities" ng-value="resolvedEntity.id">
  26 + {{resolvedEntity.name}}
27 </md-option> 27 </md-option>
28 </md-select> 28 </md-select>
29 </md-input-container> 29 </md-input-container>
ui/src/app/entity/alias/aliases-entity-select.directive.js renamed from ui/src/app/entity/aliases-entity-select.directive.js
@@ -29,7 +29,7 @@ import aliasesEntitySelectPanelTemplate from './aliases-entity-select-panel.tpl. @@ -29,7 +29,7 @@ import aliasesEntitySelectPanelTemplate from './aliases-entity-select-panel.tpl.
29 /*@ngInject*/ 29 /*@ngInject*/
30 export default function AliasesEntitySelectDirective($compile, $templateCache, $mdMedia, types, $mdPanel, $document, $translate) { 30 export default function AliasesEntitySelectDirective($compile, $templateCache, $mdMedia, types, $mdPanel, $document, $translate) {
31 31
32 - var linker = function (scope, element, attrs, ngModelCtrl) { 32 + var linker = function (scope, element, attrs) {
33 33
34 /* tbAliasesEntitySelect (ng-model) 34 /* tbAliasesEntitySelect (ng-model)
35 * { 35 * {
@@ -81,10 +81,8 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $ @@ -81,10 +81,8 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $
81 position: position, 81 position: position,
82 fullscreen: false, 82 fullscreen: false,
83 locals: { 83 locals: {
84 - 'entityAliases': angular.copy(scope.model),  
85 - 'entityAliasesInfo': scope.entityAliasesInfo,  
86 - 'onEntityAliasesUpdate': function (entityAliases) {  
87 - scope.model = entityAliases; 84 + 'aliasController': scope.aliasController,
  85 + 'onEntityAliasesUpdate': function () {
88 scope.updateView(); 86 scope.updateView();
89 } 87 }
90 }, 88 },
@@ -96,41 +94,40 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $ @@ -96,41 +94,40 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $
96 $mdPanel.open(config); 94 $mdPanel.open(config);
97 } 95 }
98 96
  97 + scope.$on('entityAliasesChanged', function() {
  98 + scope.updateView();
  99 + });
  100 +
  101 + scope.$on('entityAliasResolved', function() {
  102 + scope.updateView();
  103 + });
  104 +
99 scope.updateView = function () { 105 scope.updateView = function () {
100 - var value = angular.copy(scope.model);  
101 - ngModelCtrl.$setViewValue(value);  
102 updateDisplayValue(); 106 updateDisplayValue();
103 } 107 }
104 108
105 - ngModelCtrl.$render = function () {  
106 - if (ngModelCtrl.$viewValue) {  
107 - var value = ngModelCtrl.$viewValue;  
108 - scope.model = angular.copy(value);  
109 - updateDisplayValue();  
110 - }  
111 - }  
112 -  
113 function updateDisplayValue() { 109 function updateDisplayValue() {
114 var displayValue; 110 var displayValue;
115 var singleValue = true; 111 var singleValue = true;
116 var currentAliasId; 112 var currentAliasId;
117 - for (var aliasId in scope.model) {  
118 - if (!currentAliasId) {  
119 - currentAliasId = aliasId;  
120 - } else {  
121 - singleValue = false;  
122 - break; 113 + var entityAliases = scope.aliasController.getEntityAliases();
  114 + for (var aliasId in entityAliases) {
  115 + var entityAlias = entityAliases[aliasId];
  116 + if (!entityAlias.filter.resolveMultiple) {
  117 + var resolvedAlias = scope.aliasController.getInstantAliasInfo(aliasId);
  118 + if (resolvedAlias && resolvedAlias.currentEntity) {
  119 + if (!currentAliasId) {
  120 + currentAliasId = aliasId;
  121 + } else {
  122 + singleValue = false;
  123 + break;
  124 + }
  125 + }
123 } 126 }
124 } 127 }
125 if (singleValue && currentAliasId) { 128 if (singleValue && currentAliasId) {
126 - var entityId = scope.model[currentAliasId].entityId;  
127 - var entitiesInfo = scope.entityAliasesInfo[currentAliasId];  
128 - for (var i=0;i<entitiesInfo.length;i++) {  
129 - if (entitiesInfo[i].id === entityId) {  
130 - displayValue = entitiesInfo[i].name;  
131 - break;  
132 - }  
133 - } 129 + var aliasInfo = scope.aliasController.getInstantAliasInfo(currentAliasId);
  130 + displayValue = aliasInfo.currentEntity.name;
134 } else { 131 } else {
135 displayValue = $translate.instant('entity.entities'); 132 displayValue = $translate.instant('entity.entities');
136 } 133 }
@@ -142,9 +139,8 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $ @@ -142,9 +139,8 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $
142 139
143 return { 140 return {
144 restrict: "E", 141 restrict: "E",
145 - require: "^ngModel",  
146 scope: { 142 scope: {
147 - entityAliasesInfo:'=' 143 + aliasController:'='
148 }, 144 },
149 link: linker 145 link: linker
150 }; 146 };
ui/src/app/entity/alias/aliases-entity-select.scss renamed from ui/src/app/entity/aliases-entity-select.scss
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +import './entity-alias-dialog.scss';
  18 +
  19 +/*@ngInject*/
  20 +export default function EntityAliasDialogController($scope, $mdDialog, $q, $filter, utils, entityService, types, isAdd, allowedEntityTypes, entityAliases, alias) {
  21 +
  22 + var vm = this;
  23 +
  24 + vm.types = types;
  25 + vm.isAdd = isAdd;
  26 + vm.allowedEntityTypes = allowedEntityTypes;
  27 + if (angular.isArray(entityAliases)) {
  28 + vm.entityAliases = entityAliases;
  29 + } else {
  30 + vm.entityAliases = [];
  31 + for (var aliasId in entityAliases) {
  32 + vm.entityAliases.push(entityAliases[aliasId]);
  33 + }
  34 + }
  35 + if (vm.isAdd && !alias) {
  36 + vm.alias = {
  37 + alias: '',
  38 + filter: {
  39 + resolveMultiple: false
  40 + }
  41 + };
  42 + } else {
  43 + vm.alias = alias;
  44 + }
  45 +
  46 + vm.cancel = cancel;
  47 + vm.save = save;
  48 +
  49 + $scope.$watch('vm.alias.alias', function (newAlias) {
  50 + if (newAlias) {
  51 + var valid = true;
  52 + var result = $filter('filter')(vm.entityAliases, {alias: newAlias}, true);
  53 + if (result && result.length) {
  54 + if (vm.isAdd || vm.alias.id != result[0].id) {
  55 + valid = false;
  56 + }
  57 + }
  58 + $scope.theForm.aliasName.$setValidity('duplicateAliasName', valid);
  59 + }
  60 + });
  61 +
  62 + $scope.$watch('theForm.$pristine', function() {
  63 + if ($scope.theForm && !$scope.theForm.$pristine) {
  64 + $scope.theForm.$setValidity('entityFilter', true);
  65 + }
  66 + });
  67 +
  68 + function validate() {
  69 + var deferred = $q.defer();
  70 + var validationResult = {
  71 + entity: null,
  72 + stateEntity: false
  73 + }
  74 + entityService.resolveAliasFilter(vm.alias.filter, null, 1).then(
  75 + function success(result) {
  76 + validationResult.stateEntity = result.stateEntity;
  77 + var entities = result.entities;
  78 + if (entities.length) {
  79 + validationResult.entity = entities[0];
  80 + }
  81 + deferred.resolve(validationResult);
  82 + },
  83 + function fail() {
  84 + deferred.reject();
  85 + }
  86 + );
  87 + return deferred.promise;
  88 + }
  89 +
  90 + function cancel() {
  91 + $mdDialog.cancel();
  92 + }
  93 +
  94 + function save() {
  95 + $scope.theForm.$setPristine();
  96 + validate().then(
  97 + function success() {
  98 + if (vm.isAdd) {
  99 + vm.alias.id = utils.guid();
  100 + }
  101 + $mdDialog.hide(vm.alias);
  102 + },
  103 + function fail() {
  104 + $scope.theForm.$setValidity('entityFilter', false);
  105 + }
  106 + )
  107 + }
  108 +
  109 +}
ui/src/app/entity/alias/entity-alias-dialog.scss renamed from ui/src/app/entity/entity-aliases.scss
@@ -14,15 +14,14 @@ @@ -14,15 +14,14 @@
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 16
17 -.tb-aliases-dialog {  
18 - .md-dialog-content {  
19 - padding-bottom: 0px;  
20 - }  
21 - .tb-alias {  
22 - padding: 10px 0 0 10px;  
23 - margin: 5px;  
24 - md-select.tb-entity-type-select {  
25 - padding-bottom: 24px; 17 +.tb-entity-alias-dialog {
  18 + .tb-resolve-multiple-switch {
  19 + padding-left: 10px;
  20 + .resolve-multiple-switch {
  21 + margin: 0;
  22 + }
  23 + .resolve-multiple-label {
  24 + margin: 5px 0;
26 } 25 }
27 } 26 }
28 -} 27 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2017 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<md-dialog class="tb-entity-alias-dialog" style="width: 600px;" aria-label="{{ (vm.isAdd ? 'alias.add' : 'alias.edit') | translate }}">
  19 + <form name="theForm" ng-submit="vm.save()">
  20 + <md-toolbar>
  21 + <div class="md-toolbar-tools">
  22 + <h2>{{ (vm.isAdd ? 'alias.add' : 'alias.edit') | translate }}</h2>
  23 + <span flex></span>
  24 + <md-button class="md-icon-button" ng-click="vm.cancel()">
  25 + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
  26 + </md-button>
  27 + </div>
  28 + </md-toolbar>
  29 + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
  30 + <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
  31 + <md-dialog-content>
  32 + <div class="md-dialog-content">
  33 + <fieldset ng-disabled="loading">
  34 + <div flex layout="column">
  35 + <div layout="row">
  36 + <md-input-container flex class="md-block">
  37 + <label translate>alias.name</label>
  38 + <input required name="aliasName"
  39 + ng-model="vm.alias.alias"
  40 + aria-label="{{ 'alias.name' | translate }}">
  41 + <div ng-messages="theForm.aliasName.$error">
  42 + <div ng-message="required" translate>alias.name-required</div>
  43 + <div ng-message="duplicateAliasName" translate>alias.duplicate-alias</div>
  44 + </div>
  45 + </md-input-container>
  46 + <section class="tb-resolve-multiple-switch" layout="column" layout-align="start center">
  47 + <label class="tb-small resolve-multiple-label" translate>alias.resolve-multiple</label>
  48 + <md-switch class="resolve-multiple-switch" ng-model="vm.alias.filter.resolveMultiple"
  49 + aria-label="{{ 'alias.resolve-multiple' | translate }}">
  50 + </md-switch>
  51 + </section>
  52 + </div>
  53 + <tb-entity-filter
  54 + ng-model="vm.alias.filter"
  55 + allowed-entity-types="vm.allowedEntityTypes"
  56 + the-form="theForm">
  57 + </tb-entity-filter>
  58 + <div class="tb-error-messages" ng-messages="theForm.$error" role="alert">
  59 + <div translate ng-message="entityFilter" class="tb-error-message">alias.entity-filter-no-entity-matched</div>
  60 + </div>
  61 + </div>
  62 + </fieldset>
  63 + </div>
  64 + </md-dialog-content>
  65 + <md-dialog-actions layout="row">
  66 + <span flex></span>
  67 + <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary">
  68 + {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }}
  69 + </md-button>
  70 + <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button>
  71 + </md-dialog-actions>
  72 + </form>
  73 +</md-dialog>
ui/src/app/entity/alias/entity-aliases.controller.js renamed from ui/src/app/entity/entity-aliases.controller.js
@@ -15,22 +15,27 @@ @@ -15,22 +15,27 @@
15 */ 15 */
16 import './entity-aliases.scss'; 16 import './entity-aliases.scss';
17 17
  18 +/* eslint-disable import/no-unresolved, import/default */
  19 +
  20 +import entityAliasDialogTemplate from './entity-alias-dialog.tpl.html';
  21 +
  22 +/* eslint-enable import/no-unresolved, import/default */
  23 +
18 /*@ngInject*/ 24 /*@ngInject*/
19 export default function EntityAliasesController(utils, entityService, toast, $scope, $mdDialog, $document, $q, $translate, 25 export default function EntityAliasesController(utils, entityService, toast, $scope, $mdDialog, $document, $q, $translate,
20 types, config) { 26 types, config) {
21 27
22 var vm = this; 28 var vm = this;
23 29
24 - vm.isSingleEntityAlias = config.isSingleEntityAlias;  
25 - vm.singleEntityAlias = config.singleEntityAlias; 30 + vm.types = types;
26 vm.entityAliases = []; 31 vm.entityAliases = [];
27 vm.title = config.customTitle ? config.customTitle : 'entity.aliases'; 32 vm.title = config.customTitle ? config.customTitle : 'entity.aliases';
28 vm.disableAdd = config.disableAdd; 33 vm.disableAdd = config.disableAdd;
29 vm.aliasToWidgetsMap = {}; 34 vm.aliasToWidgetsMap = {};
30 vm.allowedEntityTypes = config.allowedEntityTypes; 35 vm.allowedEntityTypes = config.allowedEntityTypes;
31 36
32 - vm.onFilterEntityChanged = onFilterEntityChanged;  
33 vm.addAlias = addAlias; 37 vm.addAlias = addAlias;
  38 + vm.editAlias = editAlias;
34 vm.removeAlias = removeAlias; 39 vm.removeAlias = removeAlias;
35 40
36 vm.cancel = cancel; 41 vm.cancel = cancel;
@@ -79,51 +84,61 @@ export default function EntityAliasesController(utils, entityService, toast, $sc @@ -79,51 +84,61 @@ export default function EntityAliasesController(utils, entityService, toast, $sc
79 } 84 }
80 } 85 }
81 86
82 - if (vm.isSingleEntityAlias) {  
83 - checkEntityAlias(vm.singleEntityAlias);  
84 - }  
85 -  
86 for (aliasId in config.entityAliases) { 87 for (aliasId in config.entityAliases) {
87 var entityAlias = config.entityAliases[aliasId]; 88 var entityAlias = config.entityAliases[aliasId];
88 - var result = {id: aliasId, alias: entityAlias.alias, entityType: entityAlias.entityType, entityFilter: entityAlias.entityFilter, changed: true};  
89 - checkEntityAlias(result); 89 + var filter = entityAlias.filter;
  90 + if (!filter) {
  91 + filter = {
  92 + resolveMultiple: false
  93 + };
  94 + }
  95 + if (!filter.resolveMultiple) {
  96 + filter.resolveMultiple = false;
  97 + }
  98 + var result = {id: aliasId, alias: entityAlias.alias, filter: filter};
90 vm.entityAliases.push(result); 99 vm.entityAliases.push(result);
91 } 100 }
92 } 101 }
93 102
94 - function checkEntityAlias(entityAlias) {  
95 - if (!entityAlias.entityType) {  
96 - entityAlias.entityType = types.entityType.device;  
97 - }  
98 - if (!entityAlias.entityFilter || entityAlias.entityFilter == null) {  
99 - entityAlias.entityFilter = {  
100 - useFilter: false,  
101 - entityNameFilter: '',  
102 - entityList: [],  
103 - };  
104 - } 103 + function addAlias($event) {
  104 + openAliasDialog($event);
105 } 105 }
106 106
107 - function onFilterEntityChanged(entity, entityAlias) {  
108 - if (entityAlias) {  
109 - if (!entityAlias.alias || entityAlias.alias.length == 0) {  
110 - entityAlias.changed = false;  
111 - }  
112 - if (!entityAlias.changed && entity && entityAlias.entityType) {  
113 - entityAlias.alias = entity.name;  
114 - }  
115 - } 107 + function editAlias($event, entityAlias) {
  108 + openAliasDialog($event, entityAlias);
116 } 109 }
117 110
118 - function addAlias() {  
119 - var aliasId = 0;  
120 - for (var a in vm.entityAliases) {  
121 - aliasId = Math.max(vm.entityAliases[a].id, aliasId); 111 + function openAliasDialog($event, entityAlias) {
  112 + var isAdd = entityAlias ? false : true;
  113 + var aliasIndex;
  114 + if (!isAdd) {
  115 + aliasIndex = vm.entityAliases.indexOf(entityAlias);
122 } 116 }
123 - aliasId++;  
124 - var entityAlias = {id: aliasId, alias: '', entityType: types.entityType.device,  
125 - entityFilter: {useFilter: false, entityNameFilter: '', entityList: []}, changed: false};  
126 - vm.entityAliases.push(entityAlias); 117 + $mdDialog.show({
  118 + controller: 'EntityAliasDialogController',
  119 + controllerAs: 'vm',
  120 + templateUrl: entityAliasDialogTemplate,
  121 + locals: {
  122 + isAdd: isAdd,
  123 + allowedEntityTypes: vm.allowedEntityTypes,
  124 + entityAliases: vm.entityAliases,
  125 + alias: isAdd ? null : angular.copy(entityAlias)
  126 + },
  127 + parent: angular.element($document[0].body),
  128 + fullscreen: true,
  129 + skipHide: true,
  130 + targetEvent: $event
  131 + }).then(function (alias) {
  132 + if (isAdd) {
  133 + vm.entityAliases.push(alias);
  134 + } else {
  135 + vm.entityAliases[aliasIndex] = alias;
  136 + }
  137 + if ($scope.theForm) {
  138 + $scope.theForm.$setDirty();
  139 + }
  140 + }, function () {
  141 + });
127 } 142 }
128 143
129 function removeAlias($event, entityAlias) { 144 function removeAlias($event, entityAlias) {
@@ -160,61 +175,36 @@ export default function EntityAliasesController(utils, entityService, toast, $sc @@ -160,61 +175,36 @@ export default function EntityAliasesController(utils, entityService, toast, $sc
160 $mdDialog.cancel(); 175 $mdDialog.cancel();
161 } 176 }
162 177
163 - function cleanupEntityFilter(entityFilter) {  
164 - if (entityFilter.useFilter) {  
165 - entityFilter.entityList = [];  
166 - } else {  
167 - entityFilter.entityNameFilter = '';  
168 - }  
169 - return entityFilter;  
170 - }  
171 -  
172 function save() { 178 function save() {
173 179
174 var entityAliases = {}; 180 var entityAliases = {};
175 var uniqueAliasList = {}; 181 var uniqueAliasList = {};
176 182
177 var valid = true; 183 var valid = true;
178 - var aliasId, maxAliasId;  
179 - var alias;  
180 - var i;  
181 -  
182 - if (vm.isSingleEntityAlias) {  
183 - maxAliasId = 0;  
184 - vm.singleEntityAlias.entityFilter = cleanupEntityFilter(vm.singleEntityAlias.entityFilter);  
185 - for (i = 0; i < vm.entityAliases.length; i ++) {  
186 - aliasId = vm.entityAliases[i].id;  
187 - alias = vm.entityAliases[i].alias;  
188 - if (alias === vm.singleEntityAlias.alias) {  
189 - valid = false;  
190 - break;  
191 - }  
192 - maxAliasId = Math.max(aliasId, maxAliasId);  
193 - }  
194 - maxAliasId++;  
195 - vm.singleEntityAlias.id = maxAliasId;  
196 - } else {  
197 - for (i = 0; i < vm.entityAliases.length; i++) {  
198 - aliasId = vm.entityAliases[i].id;  
199 - alias = vm.entityAliases[i].alias;  
200 - if (!uniqueAliasList[alias]) {  
201 - uniqueAliasList[alias] = alias;  
202 - entityAliases[aliasId] = {alias: alias, entityType: vm.entityAliases[i].entityType, entityFilter: cleanupEntityFilter(vm.entityAliases[i].entityFilter)};  
203 - } else {  
204 - valid = false;  
205 - break;  
206 - } 184 + var message, aliasId, alias, filter;
  185 +
  186 + for (var i = 0; i < vm.entityAliases.length; i++) {
  187 + aliasId = vm.entityAliases[i].id;
  188 + alias = vm.entityAliases[i].alias;
  189 + filter = vm.entityAliases[i].filter;
  190 + if (uniqueAliasList[alias]) {
  191 + valid = false;
  192 + message = $translate.instant('entity.duplicate-alias-error', {alias: alias});
  193 + break;
  194 + } else if (!filter || !filter.type) {
  195 + valid = false;
  196 + message = $translate.instant('entity.missing-entity-filter-error', {alias: alias});
  197 + break;
  198 + } else {
  199 + uniqueAliasList[alias] = alias;
  200 + entityAliases[aliasId] = {id: aliasId, alias: alias, filter: filter};
207 } 201 }
208 } 202 }
209 if (valid) { 203 if (valid) {
210 $scope.theForm.$setPristine(); 204 $scope.theForm.$setPristine();
211 - if (vm.isSingleEntityAlias) {  
212 - $mdDialog.hide(vm.singleEntityAlias);  
213 - } else {  
214 - $mdDialog.hide(entityAliases);  
215 - } 205 + $mdDialog.hide(entityAliases);
216 } else { 206 } else {
217 - toast.showError($translate.instant('entity.duplicate-alias-error', {alias: alias})); 207 + toast.showError(message);
218 } 208 }
219 } 209 }
220 210
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +.tb-aliases-dialog {
  18 + .md-dialog-content {
  19 + padding-bottom: 0px;
  20 + padding-top: 0px;
  21 + }
  22 + .tb-aliases-header {
  23 + min-height: 40px;
  24 + padding: 0 34px 0 34px;
  25 + margin: 5px;
  26 + .tb-header-label {
  27 + font-size: 14px;
  28 + color: rgba(0, 0, 0, 0.570588);
  29 + }
  30 + }
  31 + .tb-alias {
  32 + padding: 0 0 0 10px;
  33 + margin: 5px;
  34 + md-input-container {
  35 + margin: 0px;
  36 + }
  37 + .tb-resolve-multiple-switch {
  38 + padding-left: 10px;
  39 + .resolve-multiple-switch {
  40 + margin: 0;
  41 + }
  42 + }
  43 + .md-button {
  44 + &.md-icon-button {
  45 + margin: 0px;
  46 + }
  47 + }
  48 + }
  49 +}
ui/src/app/entity/alias/entity-aliases.tpl.html renamed from ui/src/app/entity/entity-aliases.tpl.html
@@ -19,7 +19,7 @@ @@ -19,7 +19,7 @@
19 <form name="theForm" ng-submit="vm.save()"> 19 <form name="theForm" ng-submit="vm.save()">
20 <md-toolbar> 20 <md-toolbar>
21 <div class="md-toolbar-tools"> 21 <div class="md-toolbar-tools">
22 - <h2>{{ vm.isSingleEntityAlias ? ('entity.configure-alias' | translate:vm.singleEntityAlias ) : (vm.title | translate) }}</h2> 22 + <h2>{{ vm.title | translate }}</h2>
23 <span flex></span> 23 <span flex></span>
24 <md-button class="md-icon-button" ng-click="vm.cancel()"> 24 <md-button class="md-icon-button" ng-click="vm.cancel()">
25 <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> 25 <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
@@ -28,74 +28,73 @@ @@ -28,74 +28,73 @@
28 </md-toolbar> 28 </md-toolbar>
29 <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear> 29 <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
30 <span style="min-height: 5px;" flex="" ng-show="!loading"></span> 30 <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
  31 + <div class="tb-aliases-header" flex layout="row" layout-align="start center">
  32 + <span flex="5"></span>
  33 + <div flex layout="row" layout-align="start center">
  34 + <span class="tb-header-label" translate flex="20" style="min-width: 150px;">alias.name</span>
  35 + <span class="tb-header-label" translate flex="70" style="padding-left: 10px;">alias.entity-filter</span>
  36 + <span class="tb-header-label" translate flex="10" style="padding-left: 10px; min-width: 120px;">alias.resolve-multiple</span>
  37 + <span style="min-width: 80px;"></span>
  38 + </div>
  39 + </div>
  40 + <md-divider></md-divider>
31 <md-dialog-content> 41 <md-dialog-content>
32 <div class="md-dialog-content"> 42 <div class="md-dialog-content">
33 <fieldset ng-disabled="loading"> 43 <fieldset ng-disabled="loading">
34 - <div ng-show="vm.isSingleEntityAlias" layout="row">  
35 - <tb-entity-type-select style="min-width: 100px;"  
36 - ng-model="vm.singleEntityAlias.entityType"  
37 - allowed-entity-types="vm.allowedEntityTypes">  
38 - </tb-entity-type-select>  
39 - <tb-entity-filter flex entity-type="vm.singleEntityAlias.entityType" ng-model="vm.singleEntityAlias.entityFilter">  
40 - </tb-entity-filter>  
41 - </div>  
42 - <div ng-show="!vm.isSingleEntityAlias" flex layout="row" layout-align="start center">  
43 - <span flex="5"></span>  
44 - <div flex layout="row" layout-align="start center"  
45 - style="padding: 0 0 0 10px; margin: 5px;">  
46 - <span translate flex="20" style="min-width: 100px;">entity.alias</span>  
47 - <span translate flex="20" style="min-width: 100px;">entity.type</span>  
48 - <span translate flex="60" style="min-width: 190px; padding-left: 10px;">entity.entities</span>  
49 - <span style="min-width: 40px;"></span>  
50 - </div>  
51 - </div>  
52 - <div ng-show="!vm.isSingleEntityAlias" style="max-height: 500px; overflow: auto; padding-bottom: 20px;">  
53 - <div ng-form name="aliasForm" flex layout="row" layout-align="start center" ng-repeat="entityAlias in vm.entityAliases track by $index">  
54 - <span flex="5">{{$index + 1}}.</span>  
55 - <div class="md-whiteframe-4dp tb-alias" flex layout="row" layout-align="start center">  
56 - <md-input-container flex="20" style="min-width: 100px;" md-no-float class="md-block">  
57 - <input required ng-change="entityAlias.changed=true" name="alias" placeholder="{{ 'entity.alias' | translate }}" ng-model="entityAlias.alias">  
58 - <div ng-messages="aliasForm.alias.$error">  
59 - <div translate ng-message="required">entity.alias-required</div>  
60 - </div>  
61 - </md-input-container>  
62 - <section flex="20" layout="column" style="min-width: 100px;" >  
63 - <tb-entity-type-select hide-label style="padding-left: 10px;"  
64 - ng-model="entityAlias.entityType"  
65 - allowed-entity-types="vm.allowedEntityTypes">  
66 - </tb-entity-type-select>  
67 - </section>  
68 - <section flex="60" layout="column">  
69 - <tb-entity-filter style="padding-left: 10px;"  
70 - entity-type="entityAlias.entityType"  
71 - ng-model="entityAlias.entityFilter"  
72 - on-matching-entity-change="vm.onFilterEntityChanged(entity, entityAlias)">  
73 - </tb-entity-filter>  
74 - </section>  
75 - <md-button ng-disabled="loading" class="md-icon-button md-primary" style="min-width: 40px;"  
76 - ng-click="vm.removeAlias($event, entityAlias)" aria-label="{{ 'action.remove' | translate }}">  
77 - <md-tooltip md-direction="top">  
78 - {{ 'entity.remove-alias' | translate }}  
79 - </md-tooltip>  
80 - <md-icon aria-label="{{ 'action.delete' | translate }}" class="material-icons">  
81 - close  
82 - </md-icon>  
83 - </md-button>  
84 - </div>  
85 - </div>  
86 - </div>  
87 - <div ng-show="!vm.isSingleEntityAlias && !vm.disableAdd" style="padding-bottom: 10px;">  
88 - <md-button ng-disabled="loading" class="md-primary md-raised" ng-click="vm.addAlias($event)" aria-label="{{ 'action.add' | translate }}">  
89 - <md-tooltip md-direction="top">  
90 - {{ 'entity.add-alias' | translate }}  
91 - </md-tooltip>  
92 - <span translate>action.add</span>  
93 - </md-button> 44 + <div ng-form name="aliasForm" flex layout="row" layout-align="start center" ng-repeat="entityAlias in vm.entityAliases track by $index">
  45 + <span flex="5">{{$index + 1}}.</span>
  46 + <di class="md-whiteframe-4dp tb-alias" flex layout="row" layout-align="start center">
  47 + <md-input-container flex="20" style="min-width: 150px;" md-no-float class="md-block">
  48 + <input required name="alias" placeholder="{{ 'entity.alias' | translate }}" ng-model="entityAlias.alias">
  49 + <div ng-messages="aliasForm.alias.$error">
  50 + <div translate ng-message="required">entity.alias-required</div>
  51 + </div>
  52 + </md-input-container>
  53 + <tb-entity-filter-view
  54 + flex="70" style="padding-left: 10px;"
  55 + ng-model="entityAlias.filter">
  56 + </tb-entity-filter-view>
  57 + <section flex="10" style="padding-left: 10px; min-width: 120px;"
  58 + class="tb-resolve-multiple-switch"
  59 + layout="column"
  60 + layout-align="center center">
  61 + <md-switch class="resolve-multiple-switch"
  62 + ng-model="entityAlias.filter.resolveMultiple"
  63 + aria-label="resolve-multiple-switcher">
  64 + </md-switch>
  65 + </section>
  66 + <md-button ng-disabled="loading" class="md-icon-button md-primary" style="min-width: 40px;"
  67 + ng-click="vm.editAlias($event, entityAlias)" aria-label="{{ 'action.edit' | translate }}">
  68 + <md-tooltip md-direction="top">
  69 + {{ 'alias.edit' | translate }}
  70 + </md-tooltip>
  71 + <md-icon aria-label="{{ 'alias.edit' | translate }}" class="material-icons">
  72 + edit
  73 + </md-icon>
  74 + </md-button>
  75 + <md-button ng-disabled="loading" class="md-icon-button md-primary" style="min-width: 40px;"
  76 + ng-click="vm.removeAlias($event, entityAlias)" aria-label="{{ 'action.remove' | translate }}">
  77 + <md-tooltip md-direction="top">
  78 + {{ 'entity.remove-alias' | translate }}
  79 + </md-tooltip>
  80 + <md-icon aria-label="{{ 'action.delete' | translate }}" class="material-icons">
  81 + close
  82 + </md-icon>
  83 + </md-button>
  84 + </di>
94 </div> 85 </div>
95 </fieldset> 86 </fieldset>
96 </div> 87 </div>
97 </md-dialog-content> 88 </md-dialog-content>
98 <md-dialog-actions layout="row"> 89 <md-dialog-actions layout="row">
  90 + <md-button ng-show="!vm.disableAdd" ng-disabled="loading" class="md-primary md-raised"
  91 + ng-click="vm.addAlias($event)"
  92 + aria-label="{{ 'alias.add' | translate }}">
  93 + <md-tooltip md-direction="top">
  94 + {{ 'alias.add' | translate }}
  95 + </md-tooltip>
  96 + <span translate>alias.add</span>
  97 + </md-button>
99 <span flex></span> 98 <span flex></span>
100 <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary"> 99 <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary">
101 {{ 'action.save' | translate }} 100 {{ 'action.save' | translate }}
@@ -22,7 +22,8 @@ import selectTargetLayoutTemplate from '../../dashboard/layouts/select-target-la @@ -22,7 +22,8 @@ import selectTargetLayoutTemplate from '../../dashboard/layouts/select-target-la
22 /* eslint-enable import/no-unresolved, import/default */ 22 /* eslint-enable import/no-unresolved, import/default */
23 23
24 /*@ngInject*/ 24 /*@ngInject*/
25 -export default function AddWidgetToDashboardDialogController($scope, $mdDialog, $state, $q, $document, itembuffer, dashboardService, entityId, entityType, entityName, widget) { 25 +export default function AddWidgetToDashboardDialogController($scope, $mdDialog, $state, $q, $document, dashboardUtils,
  26 + types, itembuffer, dashboardService, entityId, entityType, entityName, widget) {
26 27
27 var vm = this; 28 var vm = this;
28 29
@@ -125,13 +126,8 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog, @@ -125,13 +126,8 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog,
125 targetDeviceAliases: {} 126 targetDeviceAliases: {}
126 }; 127 };
127 aliasesInfo.datasourceAliases[0] = { 128 aliasesInfo.datasourceAliases[0] = {
128 - aliasName: entityName,  
129 - entityType: entityType,  
130 - entityFilter: {  
131 - useFilter: false,  
132 - entityNameFilter: '',  
133 - entityList: [entityId]  
134 - } 129 + alias: entityName,
  130 + filter: dashboardUtils.createSingleEntityFilter(entityType, entityId)
135 }; 131 };
136 itembuffer.addWidgetToDashboard(theDashboard, targetState, targetLayout, vm.widget, aliasesInfo, null, 48, null, -1, -1).then( 132 itembuffer.addWidgetToDashboard(theDashboard, targetState, targetLayout, vm.widget, aliasesInfo, null, 48, null, -1, -1).then(
137 function(theDashboard) { 133 function(theDashboard) {
@@ -26,10 +26,12 @@ import editAttributeValueTemplate from './edit-attribute-value.tpl.html'; @@ -26,10 +26,12 @@ import editAttributeValueTemplate from './edit-attribute-value.tpl.html';
26 /* eslint-enable import/no-unresolved, import/default */ 26 /* eslint-enable import/no-unresolved, import/default */
27 27
28 import EditAttributeValueController from './edit-attribute-value.controller'; 28 import EditAttributeValueController from './edit-attribute-value.controller';
  29 +import AliasController from '../../api/alias-controller';
29 30
30 /*@ngInject*/ 31 /*@ngInject*/
31 export default function AttributeTableDirective($compile, $templateCache, $rootScope, $q, $mdEditDialog, $mdDialog, 32 export default function AttributeTableDirective($compile, $templateCache, $rootScope, $q, $mdEditDialog, $mdDialog,
32 - $document, $translate, $filter, utils, types, dashboardService, attributeService, widgetService) { 33 + $mdUtil, $document, $translate, $filter, utils, types, dashboardUtils,
  34 + dashboardService, entityService, attributeService, widgetService) {
33 35
34 var linker = function (scope, element, attrs) { 36 var linker = function (scope, element, attrs) {
35 37
@@ -246,15 +248,19 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -246,15 +248,19 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
246 } 248 }
247 249
248 scope.nextWidget = function() { 250 scope.nextWidget = function() {
249 - if (scope.widgetsCarousel.index < scope.widgetsList.length-1) {  
250 - scope.widgetsCarousel.index++;  
251 - } 251 + $mdUtil.nextTick(function () {
  252 + if (scope.widgetsCarousel.index < scope.widgetsList.length - 1) {
  253 + scope.widgetsCarousel.index++;
  254 + }
  255 + });
252 } 256 }
253 257
254 scope.prevWidget = function() { 258 scope.prevWidget = function() {
255 - if (scope.widgetsCarousel.index > 0) {  
256 - scope.widgetsCarousel.index--;  
257 - } 259 + $mdUtil.nextTick(function () {
  260 + if (scope.widgetsCarousel.index > 0) {
  261 + scope.widgetsCarousel.index--;
  262 + }
  263 + });
258 } 264 }
259 265
260 scope.enterWidgetMode = function() { 266 scope.enterWidgetMode = function() {
@@ -281,23 +287,28 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -281,23 +287,28 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
281 scope.firstBundle = true; 287 scope.firstBundle = true;
282 scope.selectedWidgetsBundleAlias = types.systemBundleAlias.cards; 288 scope.selectedWidgetsBundleAlias = types.systemBundleAlias.cards;
283 289
284 - scope.aliasesInfo = {  
285 - entityAliases: {  
286 - '1': {alias: scope.entityName, entityType: scope.entityType, entityId: scope.entityId}  
287 - },  
288 - entityAliasesInfo: {  
289 - '1': [  
290 - {name: scope.entityName, entityType: scope.entityType, id: scope.entityId}  
291 - ] 290 + var entityAlias = {
  291 + id: utils.guid(),
  292 + alias: scope.entityName,
  293 + filter: dashboardUtils.createSingleEntityFilter(scope.entityType, scope.entityId)
  294 + };
  295 + var entitiAliases = {};
  296 + entitiAliases[entityAlias.id] = entityAlias;
  297 +
  298 + var stateController = {
  299 + getStateParams: function() {
  300 + return {};
292 } 301 }
293 }; 302 };
  303 + scope.aliasController = new AliasController(scope, $q, $filter, utils,
  304 + types, entityService, stateController, entitiAliases);
294 305
295 var dataKeyType = scope.attributeScope === types.latestTelemetry ? 306 var dataKeyType = scope.attributeScope === types.latestTelemetry ?
296 types.dataKeyType.timeseries : types.dataKeyType.attribute; 307 types.dataKeyType.timeseries : types.dataKeyType.attribute;
297 308
298 var datasource = { 309 var datasource = {
299 type: types.datasourceType.entity, 310 type: types.datasourceType.entity,
300 - entityAliasId: '1', 311 + entityAliasId: entityAlias.id,
301 dataKeys: [] 312 dataKeys: []
302 } 313 }
303 var i = 0; 314 var i = 0;
@@ -156,7 +156,7 @@ @@ -156,7 +156,7 @@
156 rn-swipe-disabled="true"> 156 rn-swipe-disabled="true">
157 <li ng-repeat="widgets in widgetsList"> 157 <li ng-repeat="widgets in widgetsList">
158 <tb-dashboard 158 <tb-dashboard
159 - aliases-info="aliasesInfo" 159 + alias-controller="aliasController"
160 widgets="widgets" 160 widgets="widgets"
161 get-st-diff="getServerTimeDiff()" 161 get-st-diff="getServerTimeDiff()"
162 columns="20" 162 columns="20"
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +/* eslint-disable import/no-unresolved, import/default */
  18 +
  19 +import entityFilterViewTemplate from './entity-filter-view.tpl.html';
  20 +
  21 +/* eslint-enable import/no-unresolved, import/default */
  22 +
  23 +import './entity-filter-view.scss';
  24 +
  25 +/*@ngInject*/
  26 +export default function EntityFilterViewDirective($compile, $templateCache, $q, $document, $mdDialog, $translate, types/*, entityService*/) {
  27 +
  28 + var linker = function (scope, element, attrs, ngModelCtrl) {
  29 +
  30 + var template = $templateCache.get(entityFilterViewTemplate);
  31 + element.html(template);
  32 +
  33 + scope.ngModelCtrl = ngModelCtrl;
  34 + scope.types = types;
  35 + scope.filterDisplayValue = '';
  36 +
  37 + scope.$watch('filter', function () {
  38 + scope.updateDisplayValue();
  39 + });
  40 +
  41 + scope.updateDisplayValue = function() {
  42 + if (scope.filter && scope.filter.type) {
  43 + var entityType;
  44 + var prefix;
  45 + switch (scope.filter.type) {
  46 + case types.aliasFilterType.entityList.value:
  47 + entityType = scope.filter.entityType;
  48 + var count = scope.filter.entityList.length;
  49 + scope.filterDisplayValue = $translate.instant(types.entityTypeTranslations[entityType].list, {count: count}, 'messageformat');
  50 + break;
  51 + case types.aliasFilterType.entityName.value:
  52 + entityType = scope.filter.entityType;
  53 + prefix = scope.filter.entityNameFilter;
  54 + scope.filterDisplayValue = $translate.instant(types.entityTypeTranslations[entityType].nameStartsWith, {prefix: prefix});
  55 + break;
  56 + case types.aliasFilterType.stateEntity.value:
  57 + scope.filterDisplayValue = $translate.instant('alias.filter-type-state-entity-description');
  58 + break;
  59 + case types.aliasFilterType.assetType.value:
  60 + var assetType = scope.filter.assetType;
  61 + prefix = scope.filter.assetNameFilter;
  62 + if (prefix && prefix.length) {
  63 + scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-type-and-name-description', {assetType: assetType, prefix: prefix});
  64 + } else {
  65 + scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-type-description', {assetType: assetType});
  66 + }
  67 + break;
  68 + case types.aliasFilterType.deviceType.value:
  69 + var deviceType = scope.filter.deviceType;
  70 + prefix = scope.filter.deviceNameFilter;
  71 + if (prefix && prefix.length) {
  72 + scope.filterDisplayValue = $translate.instant('alias.filter-type-device-type-and-name-description', {deviceType: deviceType, prefix: prefix});
  73 + } else {
  74 + scope.filterDisplayValue = $translate.instant('alias.filter-type-device-type-description', {deviceType: deviceType});
  75 + }
  76 + break;
  77 + case types.aliasFilterType.relationsQuery.value:
  78 + var rootEntityText;
  79 + var directionText;
  80 + var allEntitiesText = $translate.instant('alias.all-entities');
  81 + var anyRelationText = $translate.instant('alias.any-relation');
  82 + if (scope.filter.rootStateEntity) {
  83 + rootEntityText = $translate.instant('alias.state-entity');
  84 + } else {
  85 + rootEntityText = $translate.instant(types.entityTypeTranslations[scope.filter.rootEntity.entityType].type);
  86 + }
  87 + directionText = $translate.instant('relation.direction-type.' + scope.filter.direction);
  88 + var relationFilters = scope.filter.filters;
  89 + if (relationFilters && relationFilters.length) {
  90 + var relationFiltersDisplayValues = [];
  91 + relationFilters.forEach(function(relationFilter) {
  92 + var entitiesText;
  93 + if (relationFilter.entityTypes && relationFilter.entityTypes.length) {
  94 + var entitiesNamesList = [];
  95 + relationFilter.entityTypes.forEach(function(entityType) {
  96 + entitiesNamesList.push(
  97 + $translate.instant(types.entityTypeTranslations[entityType].typePlural)
  98 + );
  99 + });
  100 + entitiesText = entitiesNamesList.join(', ');
  101 + } else {
  102 + entitiesText = allEntitiesText;
  103 + }
  104 + var relationTypeText;
  105 + if (relationFilter.relationType && relationFilter.relationType.length) {
  106 + relationTypeText = "'" + relationFilter.relationType + "'";
  107 + } else {
  108 + relationTypeText = anyRelationText;
  109 + }
  110 + var relationFilterDisplayValue = $translate.instant('alias.filter-type-relations-query-description',
  111 + {
  112 + entities: entitiesText,
  113 + relationType: relationTypeText,
  114 + direction: directionText,
  115 + rootEntity: rootEntityText
  116 + }
  117 + );
  118 + relationFiltersDisplayValues.push(relationFilterDisplayValue);
  119 + });
  120 + scope.filterDisplayValue = relationFiltersDisplayValues.join(', ');
  121 + } else {
  122 + scope.filterDisplayValue = $translate.instant('alias.filter-type-relations-query-description',
  123 + {
  124 + entities: allEntitiesText,
  125 + relationType: anyRelationText,
  126 + direction: directionText,
  127 + rootEntity: rootEntityText
  128 + }
  129 + );
  130 + }
  131 + break;
  132 + case types.aliasFilterType.assetSearchQuery.value:
  133 + case types.aliasFilterType.deviceSearchQuery.value:
  134 + allEntitiesText = $translate.instant('alias.all-entities');
  135 + anyRelationText = $translate.instant('alias.any-relation');
  136 + if (scope.filter.rootStateEntity) {
  137 + rootEntityText = $translate.instant('alias.state-entity');
  138 + } else {
  139 + rootEntityText = $translate.instant(types.entityTypeTranslations[scope.filter.rootEntity.entityType].type);
  140 + }
  141 + directionText = $translate.instant('relation.direction-type.' + scope.filter.direction);
  142 + var relationTypeText;
  143 + if (scope.filter.relationType && scope.filter.relationType.length) {
  144 + relationTypeText = "'" + scope.filter.relationType + "'";
  145 + } else {
  146 + relationTypeText = anyRelationText;
  147 + }
  148 +
  149 + var translationValues = {
  150 + relationType: relationTypeText,
  151 + direction: directionText,
  152 + rootEntity: rootEntityText
  153 + }
  154 +
  155 + if (scope.filter.type == types.aliasFilterType.assetSearchQuery.value) {
  156 + var assetTypesQuoted = [];
  157 + scope.filter.assetTypes.forEach(function(assetType) {
  158 + assetTypesQuoted.push("'"+assetType+"'");
  159 + });
  160 + var assetTypesText = assetTypesQuoted.join(', ');
  161 + translationValues.assetTypes = assetTypesText;
  162 + scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-search-query-description',
  163 + translationValues
  164 + );
  165 + } else {
  166 + var deviceTypesQuoted = [];
  167 + scope.filter.deviceTypes.forEach(function(deviceType) {
  168 + deviceTypesQuoted.push("'"+deviceType+"'");
  169 + });
  170 + var deviceTypesText = deviceTypesQuoted.join(', ');
  171 + translationValues.deviceTypes = deviceTypesText;
  172 + scope.filterDisplayValue = $translate.instant('alias.filter-type-device-search-query-description',
  173 + translationValues
  174 + );
  175 + }
  176 + break;
  177 + default:
  178 + scope.filterDisplayValue = scope.filter.type;
  179 + break;
  180 + }
  181 + } else {
  182 + scope.filterDisplayValue = '';
  183 + }
  184 + }
  185 +
  186 + ngModelCtrl.$render = function () {
  187 + if (ngModelCtrl.$viewValue) {
  188 + scope.filter = ngModelCtrl.$viewValue;
  189 + } else {
  190 + scope.filter = null;
  191 + }
  192 + }
  193 +
  194 + $compile(element.contents())(scope);
  195 +
  196 + }
  197 +
  198 + return {
  199 + restrict: "E",
  200 + require: "^ngModel",
  201 + link: linker,
  202 + scope: true
  203 + };
  204 +
  205 +}
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +.tb-entity-filter-view {
  18 + .entity-filter-empty {
  19 + color: rgba(221, 44, 0, 0.87);
  20 + font-size: 14px;
  21 + line-height: 16px;
  22 + }
  23 + .entity-filter-type {
  24 + font-size: 14px;
  25 + line-height: 16px;
  26 + color: rgba(0, 0, 0, 0.570588);
  27 + }
  28 + .entity-filter-value {
  29 + font-size: 14px;
  30 + line-height: 16px;
  31 + color: rgba(0, 0, 0, 0.570588);
  32 + }
  33 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2017 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +
  19 +<div layout='column' class="tb-entity-filter-view">
  20 + <div ng-if="!filter || !filter.type" class="entity-filter-empty" translate>alias.no-entity-filter-specified</div>
  21 + <div ng-if="filter && filter.type" layout="column">
  22 + <div class="entity-filter-value">{{ filterDisplayValue }}</div>
  23 + </div>
  24 +</div>
@@ -23,7 +23,7 @@ import entityFilterTemplate from './entity-filter.tpl.html'; @@ -23,7 +23,7 @@ import entityFilterTemplate from './entity-filter.tpl.html';
23 import './entity-filter.scss'; 23 import './entity-filter.scss';
24 24
25 /*@ngInject*/ 25 /*@ngInject*/
26 -export default function EntityFilterDirective($compile, $templateCache, $q, entityService) { 26 +export default function EntityFilterDirective($compile, $templateCache, $q, $document, $mdDialog, types, entityService) {
27 27
28 var linker = function (scope, element, attrs, ngModelCtrl) { 28 var linker = function (scope, element, attrs, ngModelCtrl) {
29 29
@@ -31,181 +31,76 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti @@ -31,181 +31,76 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti
31 element.html(template); 31 element.html(template);
32 32
33 scope.ngModelCtrl = ngModelCtrl; 33 scope.ngModelCtrl = ngModelCtrl;
  34 + scope.types = types;
  35 + scope.aliasFilterTypes = entityService.getAliasFilterTypesByEntityTypes(scope.allowedEntityTypes);
34 36
35 - scope.fetchEntities = function(searchText, limit) {  
36 - var deferred = $q.defer();  
37 - entityService.getEntitiesByNameFilter(scope.entityType, searchText, limit).then(function success(result) {  
38 - if (result) {  
39 - deferred.resolve(result);  
40 - } else {  
41 - deferred.resolve([]);  
42 - }  
43 - }, function fail() {  
44 - deferred.reject();  
45 - });  
46 - return deferred.promise;  
47 - }  
48 -  
49 - scope.updateValidity = function() {  
50 - if (ngModelCtrl.$viewValue) {  
51 - var value = ngModelCtrl.$viewValue;  
52 - var valid;  
53 - if (value.useFilter) {  
54 - ngModelCtrl.$setValidity('entityList', true);  
55 - if (angular.isDefined(value.entityNameFilter) && value.entityNameFilter.length > 0) {  
56 - ngModelCtrl.$setValidity('entityNameFilter', true);  
57 - valid = angular.isDefined(scope.model.matchingFilterEntity) && scope.model.matchingFilterEntity != null;  
58 - ngModelCtrl.$setValidity('entityNameFilterEntityMatch', valid);  
59 - } else {  
60 - ngModelCtrl.$setValidity('entityNameFilter', false);  
61 - }  
62 - } else {  
63 - ngModelCtrl.$setValidity('entityNameFilter', true);  
64 - ngModelCtrl.$setValidity('entityNameFilterDeviceMatch', true);  
65 - valid = angular.isDefined(value.entityList) && value.entityList.length > 0;  
66 - ngModelCtrl.$setValidity('entityList', valid);  
67 - }  
68 - }  
69 - }  
70 -  
71 - ngModelCtrl.$render = function () {  
72 - destroyWatchers();  
73 - scope.model = {  
74 - useFilter: false,  
75 - entityList: [],  
76 - entityNameFilter: '' 37 + scope.$watch('filter.type', function (newType, prevType) {
  38 + if (newType && newType != prevType) {
  39 + updateFilter();
77 } 40 }
78 - if (ngModelCtrl.$viewValue) {  
79 - var value = ngModelCtrl.$viewValue;  
80 - var model = scope.model;  
81 - model.useFilter = value.useFilter === true ? true: false;  
82 - model.entityList = [];  
83 - model.entityNameFilter = value.entityNameFilter || '';  
84 - processEntityNameFilter(model.entityNameFilter).then(  
85 - function(entity) {  
86 - scope.model.matchingFilterEntity = entity;  
87 - if (value.entityList && value.entityList.length > 0) {  
88 - entityService.getEntities(scope.entityType, value.entityList).then(function (entities) {  
89 - model.entityList = entities;  
90 - updateMatchingEntity();  
91 - initWatchers();  
92 - });  
93 - } else {  
94 - updateMatchingEntity();  
95 - initWatchers();  
96 - } 41 + });
  42 +
  43 + function updateFilter() {
  44 + var filter = {};
  45 + filter.type = scope.filter.type;
  46 + filter.resolveMultiple = scope.filter.resolveMultiple;
  47 + switch (filter.type) {
  48 + case types.aliasFilterType.entityList.value:
  49 + filter.entityType = null;
  50 + filter.entityList = [];
  51 + break;
  52 + case types.aliasFilterType.entityName.value:
  53 + filter.entityType = null;
  54 + filter.entityNameFilter = '';
  55 + break;
  56 + case types.aliasFilterType.stateEntity.value:
  57 + break;
  58 + case types.aliasFilterType.assetType.value:
  59 + filter.assetType = null;
  60 + filter.assetNameFilter = '';
  61 + break;
  62 + case types.aliasFilterType.deviceType.value:
  63 + filter.deviceType = null;
  64 + filter.deviceNameFilter = '';
  65 + break;
  66 + case types.aliasFilterType.relationsQuery.value:
  67 + case types.aliasFilterType.assetSearchQuery.value:
  68 + case types.aliasFilterType.deviceSearchQuery.value:
  69 + filter.rootStateEntity = false;
  70 + filter.rootEntity = null;
  71 + filter.direction = types.entitySearchDirection.from;
  72 + filter.maxLevel = 1;
  73 + if (filter.type === types.aliasFilterType.relationsQuery.value) {
  74 + filter.filters = [];
  75 + } else if (filter.type === types.aliasFilterType.assetSearchQuery.value) {
  76 + filter.relationType = null;
  77 + filter.assetTypes = [];
  78 + } else if (filter.type === types.aliasFilterType.deviceSearchQuery.value) {
  79 + filter.relationType = null;
  80 + filter.deviceTypes = [];
97 } 81 }
98 - ) 82 + break;
99 } 83 }
  84 + scope.filter = filter;
100 } 85 }
101 86
102 - function updateMatchingEntity() {  
103 - if (scope.model.useFilter) {  
104 - scope.model.matchingEntity = scope.model.matchingFilterEntity;  
105 - } else {  
106 - if (scope.model.entityList && scope.model.entityList.length > 0) {  
107 - scope.model.matchingEntity = scope.model.entityList[0];  
108 - } else {  
109 - scope.model.matchingEntity = null;  
110 - }  
111 - }  
112 - }  
113 -  
114 - function processEntityNameFilter(entityNameFilter) {  
115 - var deferred = $q.defer();  
116 - if (angular.isDefined(entityNameFilter) && entityNameFilter.length > 0) {  
117 - scope.fetchEntities(entityNameFilter, 1).then(function (entities) {  
118 - if (entities && entities.length > 0) {  
119 - deferred.resolve(entities[0]);  
120 - } else {  
121 - deferred.resolve(null);  
122 - }  
123 - });  
124 - } else {  
125 - deferred.resolve(null);  
126 - }  
127 - return deferred.promise;  
128 - } 87 + scope.$watch('filter', function () {
  88 + scope.updateView();
  89 + });
129 90
130 - function destroyWatchers() {  
131 - if (scope.entityTypeDeregistration) {  
132 - scope.entityTypeDeregistration();  
133 - scope.entityTypeDeregistration = null;  
134 - }  
135 - if (scope.entityListDeregistration) {  
136 - scope.entityListDeregistration();  
137 - scope.entityListDeregistration = null;  
138 - }  
139 - if (scope.useFilterDeregistration) {  
140 - scope.useFilterDeregistration();  
141 - scope.useFilterDeregistration = null;  
142 - }  
143 - if (scope.entityNameFilterDeregistration) {  
144 - scope.entityNameFilterDeregistration();  
145 - scope.entityNameFilterDeregistration = null;  
146 - }  
147 - if (scope.matchingEntityDeregistration) {  
148 - scope.matchingEntityDeregistration();  
149 - scope.matchingEntityDeregistration = null;  
150 - } 91 + scope.updateView = function() {
  92 + ngModelCtrl.$setViewValue(scope.filter);
151 } 93 }
152 94
153 - function initWatchers() {  
154 -  
155 - scope.entityTypeDeregistration = scope.$watch('entityType', function (newEntityType, prevEntityType) {  
156 - if (!angular.equals(newEntityType, prevEntityType)) {  
157 - scope.model.entityList = [];  
158 - scope.model.entityNameFilter = '';  
159 - }  
160 - });  
161 -  
162 - scope.entityListDeregistration = scope.$watch('model.entityList', function () {  
163 - if (ngModelCtrl.$viewValue) {  
164 - var value = ngModelCtrl.$viewValue;  
165 - value.entityList = [];  
166 - if (scope.model.entityList && scope.model.entityList.length > 0) {  
167 - for (var i=0;i<scope.model.entityList.length;i++) {  
168 - value.entityList.push(scope.model.entityList[i].id.id);  
169 - }  
170 - }  
171 - updateMatchingEntity();  
172 - ngModelCtrl.$setViewValue(value);  
173 - scope.updateValidity();  
174 - }  
175 - }, true);  
176 - scope.useFilterDeregistration = scope.$watch('model.useFilter', function () {  
177 - if (ngModelCtrl.$viewValue) {  
178 - var value = ngModelCtrl.$viewValue;  
179 - value.useFilter = scope.model.useFilter;  
180 - updateMatchingEntity();  
181 - ngModelCtrl.$setViewValue(value);  
182 - scope.updateValidity();  
183 - }  
184 - });  
185 - scope.entityNameFilterDeregistration = scope.$watch('model.entityNameFilter', function (newNameFilter, prevNameFilter) {  
186 - if (ngModelCtrl.$viewValue) {  
187 - if (!angular.equals(newNameFilter, prevNameFilter)) {  
188 - var value = ngModelCtrl.$viewValue;  
189 - value.entityNameFilter = scope.model.entityNameFilter;  
190 - processEntityNameFilter(value.entityNameFilter).then(  
191 - function(entity) {  
192 - scope.model.matchingFilterEntity = entity;  
193 - updateMatchingEntity();  
194 - ngModelCtrl.$setViewValue(value);  
195 - scope.updateValidity();  
196 - }  
197 - );  
198 - }  
199 - }  
200 - });  
201 -  
202 - scope.matchingEntityDeregistration = scope.$watch('model.matchingEntity', function (newMatchingEntity, prevMatchingEntity) {  
203 - if (!angular.equals(newMatchingEntity, prevMatchingEntity)) {  
204 - if (scope.onMatchingEntityChange) {  
205 - scope.onMatchingEntityChange({entity: newMatchingEntity});  
206 - } 95 + ngModelCtrl.$render = function () {
  96 + if (ngModelCtrl.$viewValue) {
  97 + scope.filter = ngModelCtrl.$viewValue;
  98 + } else {
  99 + scope.filter = {
  100 + type: null,
  101 + resolveMultiple: false
207 } 102 }
208 - }); 103 + }
209 } 104 }
210 105
211 $compile(element.contents())(scope); 106 $compile(element.contents())(scope);
@@ -217,9 +112,8 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti @@ -217,9 +112,8 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti
217 require: "^ngModel", 112 require: "^ngModel",
218 link: linker, 113 link: linker,
219 scope: { 114 scope: {
220 - entityType: '=',  
221 - isEdit: '=',  
222 - onMatchingEntityChange: '&' 115 + theForm: '=',
  116 + allowedEntityTypes: '=?'
223 } 117 }
224 }; 118 };
225 119
@@ -13,33 +13,24 @@ @@ -13,33 +13,24 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
  16 +
16 .tb-entity-filter { 17 .tb-entity-filter {
17 - #entity_list_chips {  
18 - .md-chips {  
19 - padding-bottom: 1px; 18 +
  19 + #relationsQueryFilter {
  20 + padding-top: 20px;
  21 + tb-entity-select {
  22 + min-height: 92px;
20 } 23 }
21 } 24 }
22 - .entity-name-filter-input {  
23 - margin-top: 10px;  
24 - margin-bottom: 0px;  
25 - .md-errors-spacer {  
26 - min-height: 0px;  
27 - }  
28 - }  
29 - .tb-filter-switch { 25 +
  26 + .tb-root-state-entity-switch {
30 padding-left: 10px; 27 padding-left: 10px;
31 - .filter-switch { 28 + .root-state-entity-switch {
32 margin: 0; 29 margin: 0;
33 } 30 }
34 - .filter-label { 31 + .root-state-entity-label {
35 margin: 5px 0; 32 margin: 5px 0;
36 } 33 }
37 } 34 }
38 - .tb-error-messages {  
39 - margin-top: -11px;  
40 - height: 35px;  
41 - .tb-error-message {  
42 - padding-left: 1px;  
43 - }  
44 - } 35 +
45 } 36 }
@@ -15,53 +15,219 @@ @@ -15,53 +15,219 @@
15 limitations under the License. 15 limitations under the License.
16 16
17 --> 17 -->
18 -<section layout='column' class="tb-entity-filter">  
19 - <section layout='row'>  
20 - <section layout="column" flex ng-show="!model.useFilter">  
21 - <md-chips flex  
22 - id="entity_list_chips"  
23 - ng-required="!useFilter"  
24 - ng-model="model.entityList" md-autocomplete-snap  
25 - md-require-match="true">  
26 - <md-autocomplete  
27 - md-no-cache="true"  
28 - id="entity"  
29 - md-selected-item="selectedEntity"  
30 - md-search-text="entitySearchText"  
31 - md-items="item in fetchEntities(entitySearchText, 10)"  
32 - md-item-text="item.name"  
33 - md-min-length="0"  
34 - placeholder="{{ 'entity.entity-list' | translate }}">  
35 - <md-item-template>  
36 - <span md-highlight-text="entitySearchText" md-highlight-flags="^i">{{item.name}}</span>  
37 - </md-item-template>  
38 - <md-not-found>  
39 - <span translate translate-values='{ entity: entitySearchText }'>entity.no-entities-matching</span>  
40 - </md-not-found>  
41 - </md-autocomplete>  
42 - <md-chip-template>  
43 - <span>  
44 - <strong>{{$chip.name}}</strong>  
45 - </span>  
46 - </md-chip-template>  
47 - </md-chips>  
48 - </section>  
49 - <section layout="row" flex ng-show="model.useFilter">  
50 - <md-input-container flex class="entity-name-filter-input">  
51 - <label translate>entity.name-starts-with</label>  
52 - <input ng-model="model.entityNameFilter" aria-label="{{ 'entity.name-starts-with' | translate }}"> 18 +<div layout='column' class="tb-entity-filter">
  19 + <md-input-container class="md-block">
  20 + <label>{{ 'alias.filter-type' | translate }}</label>
  21 + <md-select required name="filterType"
  22 + ng-model="filter.type" aria-label="{{ 'alias.filter-type' | translate }}">
  23 + <md-option ng-repeat="type in aliasFilterTypes" ng-value="type.value">
  24 + {{type.name | translate}}
  25 + </md-option>
  26 + </md-select>
  27 + <div ng-messages="theForm.filterType.$error">
  28 + <div ng-message="required" translate>alias.filter-type-required</div>
  29 + </div>
  30 + </md-input-container>
  31 + <section layout="column" ng-if="filter.type == types.aliasFilterType.entityList.value" id="entityListFilter">
  32 + <tb-entity-type-select
  33 + ng-model="filter.entityType"
  34 + the-form="theForm"
  35 + tb-required="true"
  36 + allowed-entity-types="allowedEntityTypes">
  37 + </tb-entity-type-select>
  38 + <tb-entity-list
  39 + ng-model="filter.entityList"
  40 + tb-required="true"
  41 + entity-type="filter.entityType">
  42 + </tb-entity-list>
  43 + </section>
  44 + <section flex layout="column" ng-if="filter.type == types.aliasFilterType.entityName.value" id="entityNameFilter">
  45 + <tb-entity-type-select
  46 + ng-model="filter.entityType"
  47 + the-form="theForm"
  48 + tb-required="true"
  49 + allowed-entity-types="allowedEntityTypes">
  50 + </tb-entity-type-select>
  51 + <md-input-container class="md-block">
  52 + <label translate>entity.name-starts-with</label>
  53 + <input required name="entityNameFilter"
  54 + ng-model="filter.entityNameFilter"
  55 + aria-label="{{ 'entity.name-starts-with' | translate }}">
  56 + <div ng-messages="theForm.entityNameFilter.$error">
  57 + <div ng-message="required" translate>entity.entity-name-filter-required</div>
  58 + </div>
  59 + </md-input-container>
  60 + </section>
  61 + <section layout="column" ng-if="filter.type == types.aliasFilterType.stateEntity.value" id="stateEntityFilter">
  62 + </section>
  63 + <section layout="column" ng-if="filter.type == types.aliasFilterType.assetType.value" id="assetTypeFilter">
  64 + <tb-entity-subtype-autocomplete
  65 + tb-required="true"
  66 + the-form="theForm"
  67 + ng-model="filter.assetType"
  68 + entity-type="types.entityType.asset">
  69 + </tb-entity-subtype-autocomplete>
  70 + <md-input-container class="md-block">
  71 + <label translate>asset.name-starts-with</label>
  72 + <input name="assetNameFilter"
  73 + ng-model="filter.assetNameFilter"
  74 + aria-label="{{ 'asset.name-starts-with' | translate }}">
  75 + </md-input-container>
  76 + </section>
  77 + <section layout="column" ng-if="filter.type == types.aliasFilterType.deviceType.value" id="deviceTypeFilter">
  78 + <tb-entity-subtype-autocomplete
  79 + tb-required="true"
  80 + the-form="theForm"
  81 + ng-model="filter.deviceType"
  82 + entity-type="types.entityType.device">
  83 + </tb-entity-subtype-autocomplete>
  84 + <md-input-container class="md-block">
  85 + <label translate>device.name-starts-with</label>
  86 + <input name="deviceNameFilter"
  87 + ng-model="filter.deviceNameFilter"
  88 + aria-label="{{ 'device.name-starts-with' | translate }}">
  89 + </md-input-container>
  90 + </section>
  91 + <section layout="column" ng-if="filter.type == types.aliasFilterType.relationsQuery.value" id="relationsQueryFilter">
  92 + <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
  93 + <div flex layout="row">
  94 + <tb-entity-select flex
  95 + the-form="theForm"
  96 + tb-required="!filter.rootStateEntity"
  97 + ng-disabled="filter.rootStateEntity"
  98 + ng-model="filter.rootEntity">
  99 + </tb-entity-select>
  100 + <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
  101 + <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
  102 + <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
  103 + aria-label="{{ 'alias.root-state-entity' | translate }}">
  104 + </md-switch>
  105 + </section>
  106 + </div>
  107 + <div flex layout="row">
  108 + <md-input-container class="md-block" style="min-width: 100px;">
  109 + <label translate>relation.direction</label>
  110 + <md-select required ng-model="filter.direction">
  111 + <md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction">
  112 + {{ ('relation.search-direction.' + direction) | translate}}
  113 + </md-option>
  114 + </md-select>
  115 + </md-input-container>
  116 + <md-input-container flex class="md-block">
  117 + <label translate>alias.max-relation-level</label>
  118 + <input name="maxRelationLevel"
  119 + type="number"
  120 + min="1"
  121 + step="1"
  122 + placeholder="{{ 'alias.unlimited-level' | translate }}"
  123 + ng-model="filter.maxLevel"
  124 + aria-label="{{ 'alias.max-relation-level' | translate }}">
  125 + </md-input-container>
  126 + </div>
  127 + <div class="md-caption" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>relation.relation-filters</div>
  128 + <tb-relation-filters
  129 + ng-model="filter.filters"
  130 + allowed-entity-types="allowedEntityTypes">
  131 + </tb-relation-filters>
  132 + </section>
  133 + <section layout="column" ng-if="filter.type == types.aliasFilterType.assetSearchQuery.value" id="assetSearchQueryFilter">
  134 + <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
  135 + <div flex layout="row">
  136 + <tb-entity-select flex
  137 + the-form="theForm"
  138 + tb-required="!filter.rootStateEntity"
  139 + ng-disabled="filter.rootStateEntity"
  140 + ng-model="filter.rootEntity">
  141 + </tb-entity-select>
  142 + <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
  143 + <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
  144 + <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
  145 + aria-label="{{ 'alias.root-state-entity' | translate }}">
  146 + </md-switch>
  147 + </section>
  148 + </div>
  149 + <div flex layout="row">
  150 + <md-input-container class="md-block" style="min-width: 100px;">
  151 + <label translate>relation.direction</label>
  152 + <md-select required ng-model="filter.direction">
  153 + <md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction">
  154 + {{ ('relation.search-direction.' + direction) | translate}}
  155 + </md-option>
  156 + </md-select>
  157 + </md-input-container>
  158 + <md-input-container flex class="md-block">
  159 + <label translate>alias.max-relation-level</label>
  160 + <input name="maxRelationLevel"
  161 + type="number"
  162 + min="1"
  163 + step="1"
  164 + placeholder="{{ 'alias.unlimited-level' | translate }}"
  165 + ng-model="filter.maxLevel"
  166 + aria-label="{{ 'alias.max-relation-level' | translate }}">
  167 + </md-input-container>
  168 + </div>
  169 + <div class="md-caption" style="color: rgba(0,0,0,0.57);" translate>relation.relation-type</div>
  170 + <tb-relation-type-autocomplete flex
  171 + hide-label
  172 + the-form="theForm"
  173 + ng-model="filter.relationType"
  174 + tb-required="false">
  175 + </tb-relation-type-autocomplete>
  176 + <div class="md-caption tb-required" style="color: rgba(0,0,0,0.57);" translate>asset.asset-types</div>
  177 + <tb-entity-subtype-list
  178 + tb-required="true"
  179 + entity-type="types.entityType.asset"
  180 + ng-model="filter.assetTypes">
  181 + </tb-entity-subtype-list>
  182 + </section>
  183 + <section layout="column" ng-if="filter.type == types.aliasFilterType.deviceSearchQuery.value" id="deviceSearchQueryFilter">
  184 + <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
  185 + <div flex layout="row">
  186 + <tb-entity-select flex
  187 + the-form="theForm"
  188 + tb-required="!filter.rootStateEntity"
  189 + ng-disabled="filter.rootStateEntity"
  190 + ng-model="filter.rootEntity">
  191 + </tb-entity-select>
  192 + <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
  193 + <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
  194 + <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
  195 + aria-label="{{ 'alias.root-state-entity' | translate }}">
  196 + </md-switch>
  197 + </section>
  198 + </div>
  199 + <div flex layout="row">
  200 + <md-input-container class="md-block" style="min-width: 100px;">
  201 + <label translate>relation.direction</label>
  202 + <md-select required ng-model="filter.direction">
  203 + <md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction">
  204 + {{ ('relation.search-direction.' + direction) | translate}}
  205 + </md-option>
  206 + </md-select>
  207 + </md-input-container>
  208 + <md-input-container flex class="md-block">
  209 + <label translate>alias.max-relation-level</label>
  210 + <input name="maxRelationLevel"
  211 + type="number"
  212 + min="1"
  213 + step="1"
  214 + placeholder="{{ 'alias.unlimited-level' | translate }}"
  215 + ng-model="filter.maxLevel"
  216 + aria-label="{{ 'alias.max-relation-level' | translate }}">
53 </md-input-container> 217 </md-input-container>
54 - </section>  
55 - <section class="tb-filter-switch" layout="column" layout-align="center center">  
56 - <label class="tb-small filter-label" translate>entity.use-entity-name-filter</label>  
57 - <md-switch class="filter-switch" ng-model="model.useFilter" aria-label="use-filter-switcher">  
58 - </md-switch>  
59 - </section> 218 + </div>
  219 + <div class="md-caption" style="color: rgba(0,0,0,0.57);" translate>relation.relation-type</div>
  220 + <tb-relation-type-autocomplete flex
  221 + hide-label
  222 + the-form="theForm"
  223 + ng-model="filter.relationType"
  224 + tb-required="false">
  225 + </tb-relation-type-autocomplete>
  226 + <div class="md-caption tb-required" style="color: rgba(0,0,0,0.57);" translate>device.device-types</div>
  227 + <tb-entity-subtype-list
  228 + tb-required="true"
  229 + entity-type="types.entityType.device"
  230 + ng-model="filter.deviceTypes">
  231 + </tb-entity-subtype-list>
60 </section> 232 </section>
61 - <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">  
62 - <div translate ng-message="entityList" class="tb-error-message">entity.entity-list-empty</div>  
63 - <div translate ng-message="entityNameFilter" class="tb-error-message">entity.entity-name-filter-required</div>  
64 - <div translate translate-values='{ entity: model.entityNameFilter }' ng-message="entityNameFilterEntityMatch"  
65 - class="tb-error-message">entity.entity-name-filter-no-entity-matched</div>  
66 - </div>  
67 -</section>  
  233 +</div>