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 250 }
251 251 }
252 252
  253 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
  254 + @RequestMapping(value = "/relations/info", method = RequestMethod.POST)
  255 + @ResponseBody
  256 + public List<EntityRelationInfo> findInfoByQuery(@RequestBody EntityRelationsQuery query) throws ThingsboardException {
  257 + checkNotNull(query);
  258 + checkNotNull(query.getParameters());
  259 + checkNotNull(query.getFilters());
  260 + checkEntityId(query.getParameters().getEntityId());
  261 + try {
  262 + return checkNotNull(relationService.findInfoByQuery(query).get());
  263 + } catch (Exception e) {
  264 + throw handleException(e);
  265 + }
  266 + }
  267 +
253 268 private RelationTypeGroup parseRelationTypeGroup(String strRelationTypeGroup, RelationTypeGroup defaultValue) {
254 269 RelationTypeGroup result = defaultValue;
255 270 if (strRelationTypeGroup != null && strRelationTypeGroup.trim().length()>0) {
... ...
... ... @@ -191,7 +191,7 @@ public class BaseRelationService implements RelationService {
191 191
192 192 @Override
193 193 public ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query) {
194   - log.trace("Executing findByQuery [{}][{}]", query);
  194 + log.trace("Executing findByQuery [{}]", query);
195 195 RelationsSearchParameters params = query.getParameters();
196 196 final List<EntityTypeFilter> filters = query.getFilters();
197 197 if (filters == null || filters.isEmpty()) {
... ... @@ -224,6 +224,30 @@ public class BaseRelationService implements RelationService {
224 224 }
225 225 }
226 226
  227 + @Override
  228 + public ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(EntityRelationsQuery query) {
  229 + log.trace("Executing findInfoByQuery [{}]", query);
  230 + ListenableFuture<List<EntityRelation>> relations = findByQuery(query);
  231 + EntitySearchDirection direction = query.getParameters().getDirection();
  232 + ListenableFuture<List<EntityRelationInfo>> relationsInfo = Futures.transform(relations,
  233 + (AsyncFunction<List<EntityRelation>, List<EntityRelationInfo>>) relations1 -> {
  234 + List<ListenableFuture<EntityRelationInfo>> futures = new ArrayList<>();
  235 + relations1.stream().forEach(relation ->
  236 + futures.add(fetchRelationInfoAsync(relation,
  237 + relation2 -> direction == EntitySearchDirection.FROM ? relation2.getTo() : relation2.getFrom(),
  238 + (EntityRelationInfo relationInfo, String entityName) -> {
  239 + if (direction == EntitySearchDirection.FROM) {
  240 + relationInfo.setToName(entityName);
  241 + } else {
  242 + relationInfo.setFromName(entityName);
  243 + }
  244 + }))
  245 + );
  246 + return Futures.successfulAsList(futures);
  247 + });
  248 + return relationsInfo;
  249 + }
  250 +
227 251 protected void validate(EntityRelation relation) {
228 252 if (relation == null) {
229 253 throw new DataValidationException("Relation type should be specified!");
... ...
... ... @@ -52,6 +52,8 @@ public interface RelationService {
52 52
53 53 ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query);
54 54
  55 + ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(EntityRelationsQuery query);
  56 +
55 57 // TODO: This method may be useful for some validations in the future
56 58 // ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to);
57 59
... ...
  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 +}
\ No newline at end of file
... ...
... ... @@ -30,7 +30,8 @@ function EntityRelationService($http, $q) {
30 30 findByTo: findByTo,
31 31 findInfoByTo: findInfoByTo,
32 32 findByToAndType: findByToAndType,
33   - findByQuery: findByQuery
  33 + findByQuery: findByQuery,
  34 + findInfoByQuery: findInfoByQuery
34 35 }
35 36
36 37 return service;
... ... @@ -159,4 +160,15 @@ function EntityRelationService($http, $q) {
159 160 return deferred.promise;
160 161 }
161 162
  163 + function findInfoByQuery(query) {
  164 + var deferred = $q.defer();
  165 + var url = '/api/relations/info';
  166 + $http.post(url, query).then(function success(response) {
  167 + deferred.resolve(response.data);
  168 + }, function fail() {
  169 + deferred.reject();
  170 + });
  171 + return deferred.promise;
  172 + }
  173 +
162 174 }
... ...
... ... @@ -27,10 +27,14 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
27 27 getEntity: getEntity,
28 28 getEntities: getEntities,
29 29 getEntitiesByNameFilter: getEntitiesByNameFilter,
30   - processEntityAliases: processEntityAliases,
31   - getEntityKeys: getEntityKeys,
  30 + resolveAlias: resolveAlias,
  31 + resolveAliasFilter: resolveAliasFilter,
32 32 checkEntityAlias: checkEntityAlias,
33   - createDatasoucesFromSubscriptionsInfo: createDatasoucesFromSubscriptionsInfo,
  33 + filterAliasByEntityTypes: filterAliasByEntityTypes,
  34 + getAliasFilterTypesByEntityTypes: getAliasFilterTypesByEntityTypes,
  35 + prepareAllowedEntityTypesList: prepareAllowedEntityTypesList,
  36 + getEntityKeys: getEntityKeys,
  37 + createDatasourcesFromSubscriptionsInfo: createDatasourcesFromSubscriptionsInfo,
34 38 getRelatedEntities: getRelatedEntities,
35 39 saveRelatedEntity: saveRelatedEntity,
36 40 getRelatedEntity: getRelatedEntity,
... ... @@ -173,6 +177,54 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
173 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 228 function getEntitiesByPageLinkPromise(entityType, pageLink, config, subType) {
177 229 var promise;
178 230 var user = userService.getCurrentUser();
... ... @@ -193,10 +245,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
193 245 }
194 246 break;
195 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 253 break;
198 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 260 break;
201 261 case types.entityType.rule:
202 262 promise = ruleService.getAllRules(pageLink);
... ... @@ -221,17 +281,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
221 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 285 var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config, subType);
228 286 if (promise) {
229 287 promise.then(
230 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 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 301 function fail() {
... ... @@ -241,92 +305,418 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
241 305 } else {
242 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 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 354 var entitiesInfo = [];
253 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 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 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 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 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 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 720 return deferred.promise;
331 721 }
332 722
... ... @@ -354,40 +744,13 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
354 744 }
355 745 }
356 746 deferred.resolve(result);
357   - }, function fail(response) {
358   - deferred.reject(response.data);
  747 + }, function fail() {
  748 + deferred.reject();
359 749 });
360 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 754 var deferred = $q.defer();
392 755 var datasources = [];
393 756 processSubscriptionsInfo(0, subscriptionsInfo, datasources, deferred);
... ... @@ -822,4 +1185,4 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
822 1185 }
823 1186 }
824 1187
825   -}
\ No newline at end of file
  1188 +}
... ...
... ... @@ -39,6 +39,9 @@ export default class Subscription {
39 39 this.cafs = {};
40 40 this.registrations = [];
41 41
  42 + var subscription = this;
  43 + var deferred = this.ctx.$q.defer();
  44 +
42 45 if (this.type === this.ctx.types.widgetType.rpc.value) {
43 46 this.callbacks.rpcStateChanged = this.callbacks.rpcStateChanged || function(){};
44 47 this.callbacks.onRpcSuccess = this.callbacks.onRpcSuccess || function(){};
... ... @@ -56,7 +59,11 @@ export default class Subscription {
56 59 this.rpcEnabled = false;
57 60 this.executingRpcRequest = false;
58 61 this.executingPromises = [];
59   - this.initRpc();
  62 + this.initRpc().then(
  63 + function() {
  64 + deferred.resolve(subscription);
  65 + }
  66 + );
60 67 } else {
61 68 this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || function(){};
62 69 this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || function(){};
... ... @@ -66,6 +73,15 @@ export default class Subscription {
66 73
67 74 this.datasources = this.ctx.utils.validateDatasources(options.datasources);
68 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 85 this.data = [];
70 86 this.hiddenData = [];
71 87 this.originalTimewindow = null;
... ... @@ -103,11 +119,41 @@ export default class Subscription {
103 119 this.legendConfig.showMax === true ||
104 120 this.legendConfig.showAvg === true ||
105 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 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 157 var dataIndex = 0;
112 158 for (var i = 0; i < this.datasources.length; i++) {
113 159 var datasource = this.datasources[i];
... ... @@ -199,21 +245,46 @@ export default class Subscription {
199 245 }
200 246
201 247 initRpc() {
  248 + var deferred = this.ctx.$q.defer();
202 249 if (this.targetDeviceAliasIds && this.targetDeviceAliasIds.length > 0) {
203 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 290 clearRpcError() {
... ... @@ -319,11 +390,11 @@ export default class Subscription {
319 390 this.onDataUpdated();
320 391 }
321 392
322   - onAliasesChanged() {
  393 + onAliasesChanged(aliasIds) {
323 394 if (this.type === this.ctx.types.widgetType.rpc.value) {
324   - this.checkRpcTarget();
  395 + return this.checkRpcTarget(aliasIds);
325 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 552 var datasource = this.datasources[i];
482 553 if (angular.isFunction(datasource))
483 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 556 var subscription = this;
519 557
... ... @@ -521,8 +559,8 @@ export default class Subscription {
521 559 subscriptionType: this.type,
522 560 subscriptionTimewindow: this.subscriptionTimewindow,
523 561 datasource: datasource,
524   - entityType: entityType,
525   - entityId: entityId,
  562 + entityType: datasource.entityType,
  563 + entityId: datasource.entityId,
526 564 dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) {
527 565 subscription.dataUpdated(data, datasourceIndex, dataKeyIndex, apply);
528 566 },
... ... @@ -544,6 +582,10 @@ export default class Subscription {
544 582
545 583 this.datasourceListeners.push(listener);
546 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 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 611 var subscriptionsChanged = false;
578 612 for (var i = 0; i < this.datasourceListeners.length; i++) {
579 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 616 subscriptionsChanged = true;
594 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 624 destroy() {
... ... @@ -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 640 function calculateMin(data) {
644 641 if (data.length > 0) {
645 642 var result = Number(data[0][1]);
... ...
... ... @@ -23,8 +23,10 @@ function DashboardUtils(types, utils, timeService) {
23 23
24 24 var service = {
25 25 validateAndUpdateDashboard: validateAndUpdateDashboard,
  26 + validateAndUpdateWidget: validateAndUpdateWidget,
26 27 getRootStateId: getRootStateId,
27 28 createSingleWidgetDashboard: createSingleWidgetDashboard,
  29 + createSingleEntityFilter: createSingleEntityFilter,
28 30 getStateLayoutsData: getStateLayoutsData,
29 31 createDefaultState: createDefaultState,
30 32 createDefaultLayoutData: createDefaultLayoutData,
... ... @@ -39,40 +41,104 @@ function DashboardUtils(types, utils, timeService) {
39 41
40 42 return service;
41 43
42   - function validateAndUpdateEntityAliases(configuration) {
  44 + function validateAndUpdateEntityAliases(configuration, datasourcesByAliasId, targetDevicesByAliasId) {
  45 + var aliasId, entityAlias;
43 46 if (angular.isUndefined(configuration.entityAliases)) {
44 47 configuration.entityAliases = {};
45 48 if (configuration.deviceAliases) {
46 49 var deviceAliases = configuration.deviceAliases;
47   - for (var aliasId in deviceAliases) {
  50 + for (aliasId in deviceAliases) {
48 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 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 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 142 function validateAndUpdateWidget(widget) {
77 143 if (!widget.config) {
78 144 widget.config = {};
... ... @@ -166,7 +232,34 @@ function DashboardUtils(types, utils, timeService) {
166 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 264 if (angular.isUndefined(dashboard.configuration.timewindow)) {
172 265 dashboard.configuration.timewindow = timeService.defaultTimewindow();
... ... @@ -243,6 +336,15 @@ function DashboardUtils(types, utils, timeService) {
243 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 348 function getStateLayoutsData(dashboard, targetState) {
247 349 var dashboardConfiguration = dashboard.configuration;
248 350 var states = dashboardConfiguration.states;
... ...
... ... @@ -65,6 +65,40 @@ export default angular.module('thingsboard.types', [])
65 65 clearedUnack: "CLEARED_UNACK",
66 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 102 position: {
69 103 top: {
70 104 value: "top",
... ... @@ -109,6 +143,62 @@ export default angular.module('thingsboard.types', [])
109 143 dashboard: "DASHBOARD",
110 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 202 entitySearchDirection: {
113 203 from: "FROM",
114 204 to: "TO"
... ...
... ... @@ -106,10 +106,10 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
106 106 isDescriptorSchemaNotEmpty: isDescriptorSchemaNotEmpty,
107 107 filterSearchTextEntities: filterSearchTextEntities,
108 108 guid: guid,
  109 + cleanCopy: cleanCopy,
109 110 isLocalUrl: isLocalUrl,
110 111 validateDatasources: validateDatasources,
111   - createKey: createKey,
112   - entityTypeName: entityTypeName
  112 + createKey: createKey
113 113 }
114 114
115 115 return service;
... ... @@ -291,6 +291,16 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
291 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 304 function genNextColor(datasources) {
295 305 var index = 0;
296 306 if (datasources) {
... ... @@ -347,27 +357,4 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
347 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 52 bindToController: {
53 53 widgets: '=',
54 54 widgetLayouts: '=?',
55   - aliasesInfo: '=',
  55 + aliasController: '=',
56 56 stateController: '=',
57 57 dashboardTimewindow: '=?',
58 58 columns: '=',
... ... @@ -85,7 +85,7 @@ function Dashboard() {
85 85 }
86 86
87 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 90 var highlightedMode = false;
91 91 var highlightedWidget = null;
... ... @@ -329,10 +329,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
329 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 332 $scope.$on('gridster-resized', function (event, sizes, theGridster) {
337 333 if (checkIsLocalGridsterElement(theGridster)) {
338 334 vm.gridster = theGridster;
... ... @@ -796,7 +792,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
796 792 }
797 793
798 794 function dashboardLoaded() {
799   - $timeout(function () {
  795 + $mdUtil.nextTick(function () {
800 796 if (vm.dashboardTimewindowWatch) {
801 797 vm.dashboardTimewindowWatch();
802 798 vm.dashboardTimewindowWatch = null;
... ... @@ -806,14 +802,27 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
806 802 }, true);
807 803 adoptMaxRows();
808 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 828 function loading() {
... ...
... ... @@ -89,7 +89,7 @@
89 89 <div flex tb-widget
90 90 locals="{ visibleRect: vm.visibleRect,
91 91 widget: widget,
92   - aliasesInfo: vm.aliasesInfo,
  92 + aliasController: vm.aliasController,
93 93 stateController: vm.stateController,
94 94 isEdit: vm.isEdit,
95 95 stDiff: vm.stDiff,
... ...
... ... @@ -20,14 +20,14 @@ export default angular.module('thingsboard.dialogs.datakeyConfigDialog', [things
20 20 .name;
21 21
22 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 25 var vm = this;
26 26
27 27 vm.dataKey = dataKey;
28 28 vm.dataKeySettingsSchema = dataKeySettingsSchema;
29 29 vm.entityAlias = entityAlias;
30   - vm.entityAliases = entityAliases;
  30 + vm.aliasController = aliasController;
31 31
32 32 vm.hide = function () {
33 33 $mdDialog.hide();
... ... @@ -38,12 +38,28 @@ function DatakeyConfigDialogController($scope, $mdDialog, entityService, dataKey
38 38 };
39 39
40 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 65 vm.save = function () {
... ...
... ... @@ -103,10 +103,9 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
103 103 ngModelCtrl.$render = function () {
104 104 if (ngModelCtrl.$viewValue) {
105 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 109 } else {
111 110 scope.entityAlias = null;
112 111 }
... ... @@ -182,7 +181,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
182 181 dataKey: angular.copy(dataKey),
183 182 dataKeySettingsSchema: scope.datakeySettingsSchema,
184 183 entityAlias: scope.entityAlias,
185   - entityAliases: scope.entityAliases
  184 + aliasController: scope.aliasController
186 185 },
187 186 parent: angular.element($document[0].body),
188 187 fullscreen: true,
... ... @@ -236,7 +235,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
236 235 require: "^ngModel",
237 236 scope: {
238 237 widgetType: '=',
239   - entityAliases: '=',
  238 + aliasController: '=',
240 239 datakeySettingsSchema: '=',
241 240 generateDataKey: '&',
242 241 fetchEntityKeys: '&',
... ...
... ... @@ -18,7 +18,7 @@
18 18 <section flex layout='column' layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
19 19 <tb-entity-alias-select
20 20 tb-required="true"
21   - entity-aliases="entityAliases"
  21 + alias-controller="aliasController"
22 22 ng-model="entityAlias"
23 23 on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})">
24 24 </tb-entity-alias-select>
... ...
... ... @@ -71,6 +71,13 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
71 71 }
72 72 }, true);
73 73
  74 + scope.$watch('datasourceName', function () {
  75 + if (ngModelCtrl.$viewValue) {
  76 + ngModelCtrl.$viewValue.name = scope.datasourceName;
  77 + scope.updateValidity();
  78 + }
  79 + });
  80 +
74 81 ngModelCtrl.$render = function () {
75 82 if (ngModelCtrl.$viewValue) {
76 83 var funcDataKeys = [];
... ... @@ -78,6 +85,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
78 85 funcDataKeys = funcDataKeys.concat(ngModelCtrl.$viewValue.dataKeys);
79 86 }
80 87 scope.funcDataKeys = funcDataKeys;
  88 + scope.datasourceName = ngModelCtrl.$viewValue.name;
81 89 }
82 90 };
83 91
... ...
... ... @@ -15,23 +15,29 @@
15 15 */
16 16 @import '../../scss/constants';
17 17
18   -.tb-func-datakey-autocomplete {
19   - .tb-not-found {
20   - display: block;
21   - line-height: 1.5;
22   - height: 48px;
23   - .tb-no-entries {
24   - line-height: 48px;
25   - }
  18 +.tb-datasource-func {
  19 + @media (min-width: $layout-breakpoint-gt-sm) {
  20 + padding-left: 8px;
26 21 }
27   - li {
28   - height: auto !important;
29   - white-space: normal !important;
  22 +
  23 + md-input-container.tb-datasource-name {
  24 + .md-errors-spacer {
  25 + display: none;
  26 + }
30 27 }
31   -}
32 28
33   -tb-datasource-func {
34   - @media (min-width: $layout-breakpoint-gt-sm) {
35   - padding-left: 8px;
  29 + .tb-func-datakey-autocomplete {
  30 + .tb-not-found {
  31 + display: block;
  32 + line-height: 1.5;
  33 + height: 48px;
  34 + .tb-no-entries {
  35 + line-height: 48px;
  36 + }
  37 + }
  38 + li {
  39 + height: auto !important;
  40 + white-space: normal !important;
  41 + }
36 42 }
37   -}
\ No newline at end of file
  43 +}
... ...
... ... @@ -15,59 +15,68 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<section flex layout='column' style="padding-left: 4px;">
19   - <md-chips flex
20   - id="function_datakey_chips"
21   - ng-required="true"
22   - ng-model="funcDataKeys" md-autocomplete-snap
23   - md-transform-chip="transformDataKeyChip($chip)"
24   - md-require-match="false">
25   - <md-autocomplete
26   - md-no-cache="false"
27   - id="dataKey"
28   - md-selected-item="selectedDataKey"
29   - md-search-text="dataKeySearchText"
30   - md-items="item in dataKeysSearch(dataKeySearchText)"
31   - md-item-text="item.name"
32   - md-min-length="0"
33   - placeholder="{{ 'datakey.function-types' | translate }}"
34   - md-menu-class="tb-func-datakey-autocomplete">
35   - <span md-highlight-text="dataKeySearchText" md-highlight-flags="^i">{{item}}</span>
36   - <md-not-found>
37   - <div class="tb-not-found">
38   - <div class="tb-no-entries" ng-if="!textIsNotEmpty(dataKeySearchText)">
39   - <span translate>device.no-keys-found</span>
  18 +<section class="tb-datasource-func" flex layout='column'
  19 + layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
  20 + <md-input-container class="tb-datasource-name" md-no-float style="min-width: 200px;">
  21 + <input name="datasourceName"
  22 + placeholder="{{ 'datasource.name' | translate }}"
  23 + ng-model="datasourceName"
  24 + aria-label="{{ 'datasource.name' | translate }}">
  25 + </md-input-container>
  26 + <section flex layout='column' style="padding-left: 4px;">
  27 + <md-chips flex
  28 + id="function_datakey_chips"
  29 + ng-required="true"
  30 + ng-model="funcDataKeys" md-autocomplete-snap
  31 + md-transform-chip="transformDataKeyChip($chip)"
  32 + md-require-match="false">
  33 + <md-autocomplete
  34 + md-no-cache="false"
  35 + id="dataKey"
  36 + md-selected-item="selectedDataKey"
  37 + md-search-text="dataKeySearchText"
  38 + md-items="item in dataKeysSearch(dataKeySearchText)"
  39 + md-item-text="item.name"
  40 + md-min-length="0"
  41 + placeholder="{{ 'datakey.function-types' | translate }}"
  42 + md-menu-class="tb-func-datakey-autocomplete">
  43 + <span md-highlight-text="dataKeySearchText" md-highlight-flags="^i">{{item}}</span>
  44 + <md-not-found>
  45 + <div class="tb-not-found">
  46 + <div class="tb-no-entries" ng-if="!textIsNotEmpty(dataKeySearchText)">
  47 + <span translate>device.no-keys-found</span>
  48 + </div>
  49 + <div ng-if="textIsNotEmpty(dataKeySearchText)">
  50 + <span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:&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 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 62 </div>
59   - <div class="tb-chip-separator">: </div>
60   - <div class="tb-chip-label">
61   - <strong>{{$chip.name}}</strong>
  63 + <div layout="row" flex>
  64 + <div class="tb-chip-label">
  65 + {{$chip.label}}
  66 + </div>
  67 + <div class="tb-chip-separator">: </div>
  68 + <div class="tb-chip-label">
  69 + <strong>{{$chip.name}}</strong>
  70 + </div>
62 71 </div>
  72 + <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
  73 + <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
  74 + </md-button>
63 75 </div>
64   - <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
65   - <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
66   - </md-button>
67   - </div>
68   - </md-chip-template>
69   - </md-chips>
70   - <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
71   - <div translate ng-message="funcTypes" class="tb-error-message">datakey.function-types-required</div>
72   - </div>
  76 + </md-chip-template>
  77 + </md-chips>
  78 + <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
  79 + <div translate ng-message="funcTypes" class="tb-error-message">datakey.function-types-required</div>
  80 + </div>
  81 + </section>
73 82 </section>
... ...
... ... @@ -76,7 +76,7 @@ function Datasource($compile, $templateCache, types) {
76 76 restrict: "E",
77 77 require: "^ngModel",
78 78 scope: {
79   - entityAliases: '=',
  79 + aliasController: '=',
80 80 widgetType: '=',
81 81 functionsOnly: '=',
82 82 datakeySettingsSchema: '=',
... ...
... ... @@ -37,7 +37,7 @@
37 37 ng-switch-when="entity"
38 38 ng-required="model.type === types.datasourceType.entity"
39 39 widget-type="widgetType"
40   - entity-aliases="entityAliases"
  40 + alias-controller="aliasController"
41 41 generate-data-key="generateDataKey({chip: chip, type: type})"
42 42 fetch-entity-keys="fetchEntityKeys({entityAliasId: entityAliasId, query: query, type: type})"
43 43 on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})">
... ...
... ... @@ -31,7 +31,7 @@ export default angular.module('thingsboard.directives.entityAliasSelect', [])
31 31 .name;
32 32
33 33 /*@ngInject*/
34   -function EntityAliasSelect($compile, $templateCache, $mdConstant) {
  34 +function EntityAliasSelect($compile, $templateCache, $mdConstant, entityService) {
35 35
36 36 var linker = function (scope, element, attrs, ngModelCtrl) {
37 37 var template = $templateCache.get(entityAliasSelectTemplate);
... ... @@ -49,19 +49,18 @@ function EntityAliasSelect($compile, $templateCache, $mdConstant) {
49 49 ngModelCtrl.$setValidity('entityAlias', valid);
50 50 };
51 51
52   - scope.$watch('entityAliases', function () {
  52 + scope.$watch('aliasController', function () {
53 53 scope.entityAliasList = [];
54   - for (var aliasId in scope.entityAliases) {
  54 + var entityAliases = scope.aliasController.getEntityAliases();
  55 + for (var aliasId in entityAliases) {
55 56 if (scope.allowedEntityTypes) {
56   - if (scope.allowedEntityTypes.indexOf(scope.entityAliases[aliasId].entityType) === -1) {
  57 + if (!entityService.filterAliasByEntityTypes(entityAliases[aliasId], scope.allowedEntityTypes)) {
57 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 65 scope.$watch('entityAlias', function () {
67 66 scope.updateView();
... ... @@ -141,7 +140,7 @@ function EntityAliasSelect($compile, $templateCache, $mdConstant) {
141 140 link: linker,
142 141 scope: {
143 142 tbRequired: '=?',
144   - entityAliases: '=',
  143 + aliasController: '=',
145 144 allowedEntityTypes: '=?',
146 145 onCreateEntityAlias: '&'
147 146 }
... ...
... ... @@ -128,13 +128,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
128 128 } else if (scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) {
129 129 if (config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0) {
130 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 134 } else {
139 135 scope.targetDeviceAlias.value = null;
140 136 }
... ... @@ -395,7 +391,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
395 391 widgetType: '=',
396 392 widgetSettingsSchema: '=',
397 393 datakeySettingsSchema: '=',
398   - entityAliases: '=',
  394 + aliasController: '=',
399 395 functionsOnly: '=',
400 396 fetchEntityKeys: '&',
401 397 onCreateEntityAlias: '&',
... ...
... ... @@ -60,7 +60,7 @@
60 60 style="padding: 0 0 0 10px; margin: 5px;">
61 61 <tb-datasource flex ng-model="datasource.value"
62 62 widget-type="widgetType"
63   - entity-aliases="entityAliases"
  63 + alias-controller="aliasController"
64 64 functions-only="functionsOnly"
65 65 datakey-settings-schema="datakeySettingsSchema"
66 66 generate-data-key="generateDataKey(chip,type)"
... ... @@ -104,7 +104,7 @@
104 104 <v-pane-content style="padding: 0 5px;">
105 105 <tb-entity-alias-select flex
106 106 tb-required="widgetType === types.widgetType.rpc.value && !widgetEditMode"
107   - entity-aliases="entityAliases"
  107 + alias-controller="aliasController"
108 108 allowed-entity-types="[types.entityType.device]"
109 109 ng-model="targetDeviceAlias.value"
110 110 on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias, allowedEntityTypes: allowedEntityTypes})">
... ...
... ... @@ -20,9 +20,9 @@ import Subscription from '../api/subscription';
20 20 /* eslint-disable angular/angularelement */
21 21
22 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 24 datasourceService, entityService, deviceService, visibleRect, isEdit, stDiff, dashboardTimewindow,
25   - dashboardTimewindowApi, widget, aliasesInfo, stateController, widgetType) {
  25 + dashboardTimewindowApi, widget, aliasController, stateController, widgetInfo, widgetType) {
26 26
27 27 var vm = this;
28 28
... ... @@ -37,23 +37,16 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
37 37 $scope.executingRpcRequest = false;
38 38
39 39 var gridsterItemInited = false;
  40 + var subscriptionInited = false;
  41 + var widgetSizeDetected = false;
40 42
41 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 45 var widgetContext = {
53 46 inited: false,
54 47 $scope: $scope,
55   - $container: $('#container', $element),
56   - $containerParent: $($element),
  48 + $container: null,
  49 + $containerParent: null,
57 50 width: 0,
58 51 height: 0,
59 52 isEdit: isEdit,
... ... @@ -80,30 +73,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
80 73 createSubscription: function(options, subscribe) {
81 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 76 createSubscriptionFromInfo: function (type, subscriptionsInfo, options, useDefaultComponents, subscribe) {
108 77 return createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe);
109 78 },
... ... @@ -149,7 +118,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
149 118 dashboardTimewindowApi: dashboardTimewindowApi,
150 119 types: types,
151 120 stDiff: stDiff,
152   - aliasesInfo: aliasesInfo
  121 + aliasController: aliasController
153 122 };
154 123
155 124 var widgetTypeInstance;
... ... @@ -203,23 +172,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
203 172
204 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 181 function createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe) {
225 182 var deferred = $q.defer();
... ... @@ -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 194 function (datasources) {
238 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 209 return deferred.promise;
247 210 }
248 211
249 212 function createSubscription(options, subscribe) {
  213 + var deferred = $q.defer();
250 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 231 function defaultComponentsOptions(options) {
... ... @@ -310,8 +281,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
310 281 }
311 282
312 283 function createDefaultSubscription() {
313   - var subscription;
314 284 var options;
  285 + var deferred = $q.defer();
315 286 if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) {
316 287 options = {
317 288 type: widget.type,
... ... @@ -319,16 +290,23 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
319 290 };
320 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 311 } else if (widget.type === types.widgetType.rpc.value) {
334 312 $scope.loadingData = false;
... ... @@ -356,30 +334,126 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
356 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 346 } else if (widget.type === types.widgetType.static.value) {
361 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 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 453 $scope.$on('toggleDashboardEditMode', function (event, isEdit) {
378 454 onEditModeChanged(isEdit);
379 455 });
380 456
381   - addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef
382   -
383 457 $scope.$watch(function () {
384 458 return widget.row + ',' + widget.col + ',' + widget.config.mobileOrder;
385 459 }, function () {
... ... @@ -398,18 +472,60 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
398 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 477 for (var id in widgetContext.subscriptions) {
404 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 486 $scope.$on("$destroy", function () {
410   - removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef
411 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 531 function handleWidgetException(e) {
... ... @@ -417,8 +533,15 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
417 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 545 widgetContext.inited = true;
423 546 try {
424 547 widgetTypeInstance.onInit();
... ... @@ -443,6 +566,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
443 566 widgetContext.width = width;
444 567 widgetContext.height = height;
445 568 sizeChanged = true;
  569 + widgetSizeDetected = true;
446 570 }
447 571 }
448 572 return sizeChanged;
... ... @@ -462,8 +586,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
462 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 596 if (item && item.gridster) {
473 597 widgetContext.isMobile = item.gridster.isMobile;
474 598 gridsterItemInited = true;
475   - if (checkSize()) {
476   - onInit();
477   - }
  599 + onInit();
478 600 // gridsterItemElement = $(item.$element);
479 601 //updateVisibility();
480 602 }
... ... @@ -544,6 +666,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
544 666 var subscription = widgetContext.subscriptions[id];
545 667 subscription.destroy();
546 668 }
  669 + subscriptionInited = false;
547 670 widgetContext.subscriptions = [];
548 671 if (widgetContext.inited) {
549 672 widgetContext.inited = false;
... ... @@ -559,6 +682,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
559 682 handleWidgetException(e);
560 683 }
561 684 }
  685 + destroyWidgetElement();
562 686 }
563 687
564 688 //TODO: widgets visibility
... ...
... ... @@ -28,7 +28,7 @@ export default angular.module('thingsboard.directives.widget', [thingsboardLegen
28 28 .name;
29 29
30 30 /*@ngInject*/
31   -function Widget($controller, $compile, types, widgetService) {
  31 +function Widget($controller, widgetService) {
32 32 return {
33 33 scope: true,
34 34 link: function (scope, elem, attrs) {
... ... @@ -81,90 +81,9 @@ function Widget($controller, $compile, types, widgetService) {
81 81
82 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 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 88 widgetController = $controller('WidgetController', locals);
170 89
... ...
... ... @@ -15,17 +15,18 @@
15 15 */
16 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 20 /* eslint-enable import/no-unresolved, import/default */
21 21
22 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 26 var vm = this;
26 27
27 28 vm.dashboard = dashboard;
28   - vm.aliasesInfo = aliasesInfo;
  29 + vm.aliasController = aliasController;
29 30 vm.widget = widget;
30 31 vm.widgetInfo = widgetInfo;
31 32
... ... @@ -85,7 +86,7 @@ export default function AddWidgetController($scope, widgetService, entityService
85 86 }
86 87
87 88 function cancel () {
88   - $mdDialog.cancel({aliasesInfo: vm.aliasesInfo});
  89 + $mdDialog.cancel();
89 90 }
90 91
91 92 function add () {
... ... @@ -94,52 +95,58 @@ export default function AddWidgetController($scope, widgetService, entityService
94 95 vm.widget.config = vm.widgetConfig.config;
95 96 vm.widget.config.mobileOrder = vm.widgetConfig.layout.mobileOrder;
96 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 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 127 function createEntityAlias (event, alias, allowedEntityTypes) {
111 128
112 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 132 $mdDialog.show({
116   - controller: 'EntityAliasesController',
  133 + controller: 'EntityAliasDialogController',
117 134 controllerAs: 'vm',
118   - templateUrl: entityAliasesTemplate,
  135 + templateUrl: entityAliasDialogTemplate,
119 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 142 parent: angular.element($document[0].body),
129 143 fullscreen: true,
130 144 skipHide: true,
131 145 targetEvent: event
132 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 150 }, function () {
144 151 deferred.reject();
145 152 });
... ...
... ... @@ -37,7 +37,7 @@
37 37 ng-model="vm.widgetConfig"
38 38 widget-settings-schema="vm.settingsSchema"
39 39 datakey-settings-schema="vm.dataKeySettingsSchema"
40   - entity-aliases="vm.aliasesInfo.entityAliases"
  40 + alias-controller="vm.aliasController"
41 41 functions-only="vm.functionsOnly"
42 42 fetch-entity-keys="vm.fetchEntityKeys(entityAliasId, query, type)"
43 43 on-create-entity-alias="vm.createEntityAlias(event, alias, allowedEntityTypes)"
... ...
... ... @@ -15,7 +15,7 @@
15 15 */
16 16 /* eslint-disable import/no-unresolved, import/default */
17 17
18   -import entityAliasesTemplate from '../entity/entity-aliases.tpl.html';
  18 +import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html';
19 19 import dashboardSettingsTemplate from './dashboard-settings.tpl.html';
20 20 import manageDashboardLayoutsTemplate from './layouts/manage-dashboard-layouts.tpl.html';
21 21 import manageDashboardStatesTemplate from './states/manage-dashboard-states.tpl.html';
... ... @@ -24,8 +24,10 @@ import selectTargetLayoutTemplate from './layouts/select-target-layout.tpl.html'
24 24
25 25 /* eslint-enable import/no-unresolved, import/default */
26 26
  27 +import AliasController from '../api/alias-controller';
  28 +
27 29 /*@ngInject*/
28   -export default function DashboardController(types, dashboardUtils, widgetService, userService,
  30 +export default function DashboardController(types, utils, dashboardUtils, widgetService, userService,
29 31 dashboardService, timeService, entityService, itembuffer, importExport, hotkeys, $window, $rootScope,
30 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 344 vm.dashboardConfiguration = vm.dashboard.configuration;
343 345 vm.dashboardCtx.dashboard = vm.dashboard;
344 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 349 var parentScope = $window.parent.angular.element($window.frameElement).scope();
346 350 parentScope.$root.$broadcast('widgetEditModeInited');
347 351 parentScope.$root.$apply();
... ... @@ -349,7 +353,13 @@ export default function DashboardController(types, dashboardUtils, widgetService
349 353 dashboardService.getDashboard($stateParams.dashboardId)
350 354 .then(function success(dashboard) {
351 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 363 .then(
354 364 function(resolution) {
355 365 if (resolution.error && !isTenantAdmin()) {
... ... @@ -362,7 +372,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
362 372 vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow;
363 373 }
364 374 }
365   - );
  375 + );*/
366 376 }, function fail() {
367 377 vm.configurationError = true;
368 378 });
... ... @@ -373,6 +383,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
373 383 var layoutsData = dashboardUtils.getStateLayoutsData(vm.dashboard, state);
374 384 if (layoutsData) {
375 385 vm.dashboardCtx.state = state;
  386 + vm.dashboardCtx.aliasController.dashboardStateChanged();
376 387 var layoutVisibilityChanged = false;
377 388 for (var l in vm.layouts) {
378 389 var layout = vm.layouts[l];
... ... @@ -916,7 +927,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
916 927 templateUrl: addWidgetTemplate,
917 928 locals: {
918 929 dashboard: vm.dashboard,
919   - aliasesInfo: vm.dashboardCtx.aliasesInfo,
  930 + aliasController: vm.dashboardCtx.aliasController,
920 931 widget: newWidget,
921 932 widgetInfo: widgetTypeInfo
922 933 },
... ... @@ -930,10 +941,8 @@ export default function DashboardController(types, dashboardUtils, widgetService
930 941 }
931 942 }).then(function (result) {
932 943 var widget = result.widget;
933   - vm.dashboardCtx.aliasesInfo = result.aliasesInfo;
934 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 1034 notifyDashboardUpdated();
1026 1035 }
1027 1036
1028   - function showAliasesResolutionError(error) {
  1037 +/* function showAliasesResolutionError(error) {
1029 1038 var alert = $mdDialog.alert()
1030 1039 .parent(angular.element($document[0].body))
1031 1040 .clickOutsideToClose(true)
... ... @@ -1037,20 +1046,10 @@ export default function DashboardController(types, dashboardUtils, widgetService
1037 1046 alert._options.fullscreen = true;
1038 1047
1039 1048 $mdDialog.show(alert);
1040   - }
  1049 + }*/
1041 1050
1042 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 1055 function notifyDashboardUpdated() {
... ...
... ... @@ -57,8 +57,7 @@
57 57 </tb-timewindow>
58 58 <tb-aliases-entity-select ng-show="!vm.isEdit && vm.displayEntitiesSelect()"
59 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 61 </tb-aliases-entity-select>
63 62 <md-button ng-show="vm.isEdit" aria-label="{{ 'entity.aliases' | translate }}" class="md-icon-button"
64 63 ng-click="vm.openEntityAliases($event)">
... ... @@ -179,7 +178,7 @@
179 178 <form name="vm.widgetForm" ng-if="vm.isEditingWidget">
180 179 <tb-edit-widget
181 180 dashboard="vm.dashboard"
182   - aliases-info="vm.dashboardCtx.aliasesInfo"
  181 + alias-controller="vm.dashboardCtx.aliasController"
183 182 widget="vm.editingWidget"
184 183 widget-layout="vm.editingWidgetLayout"
185 184 the-form="vm.widgetForm">
... ...
... ... @@ -15,7 +15,7 @@
15 15 */
16 16 /* eslint-disable import/no-unresolved, import/default */
17 17
18   -import entityAliasesTemplate from '../entity/entity-aliases.tpl.html';
  18 +import entityAliasDialogTemplate from '../entity/alias/entity-alias-dialog.tpl.html';
19 19 import editWidgetTemplate from './edit-widget.tpl.html';
20 20
21 21 /* eslint-enable import/no-unresolved, import/default */
... ... @@ -68,47 +68,53 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
68 68 });
69 69
70 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 95 scope.createEntityAlias = function (event, alias, allowedEntityTypes) {
80 96
81 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 100 $mdDialog.show({
85   - controller: 'EntityAliasesController',
  101 + controller: 'EntityAliasDialogController',
86 102 controllerAs: 'vm',
87   - templateUrl: entityAliasesTemplate,
  103 + templateUrl: entityAliasDialogTemplate,
88 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 110 parent: angular.element($document[0].body),
98 111 fullscreen: true,
99 112 skipHide: true,
100 113 targetEvent: event
101 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 118 }, function () {
113 119 deferred.reject();
114 120 });
... ... @@ -124,7 +130,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
124 130 link: linker,
125 131 scope: {
126 132 dashboard: '=',
127   - aliasesInfo: '=',
  133 + aliasController: '=',
128 134 widget: '=',
129 135 widgetLayout: '=',
130 136 theForm: '='
... ...
... ... @@ -21,7 +21,7 @@
21 21 is-data-enabled="isDataEnabled"
22 22 widget-settings-schema="settingsSchema"
23 23 datakey-settings-schema="dataKeySettingsSchema"
24   - entity-aliases="aliasesInfo.entityAliases"
  24 + alias-controller="aliasController"
25 25 functions-only="functionsOnly"
26 26 fetch-entity-keys="fetchEntityKeys(entityAliasId, query, type)"
27 27 on-create-entity-alias="createEntityAlias(event, alias, allowedEntityTypes)"
... ...
... ... @@ -45,7 +45,7 @@
45 45 widget-layouts="vm.layoutCtx.widgetLayouts"
46 46 columns="vm.layoutCtx.gridSettings.columns"
47 47 margins="vm.layoutCtx.gridSettings.margins"
48   - aliases-info="vm.dashboardCtx.aliasesInfo"
  48 + alias-controller="vm.dashboardCtx.aliasController"
49 49 state-controller="vm.dashboardCtx.stateController"
50 50 dashboard-timewindow="vm.dashboardCtx.dashboardTimewindow"
51 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 15 */
16 16
17 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 20 var vm = this;
21 21 vm._mdPanelRef = mdPanelRef;
22   - vm.entityAliases = entityAliases;
23   - vm.entityAliasesInfo = entityAliasesInfo;
  22 + vm.aliasController = aliasController;
24 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 18 <md-content flex layout="column">
19 19 <section flex layout="column">
20 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 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 27 </md-option>
28 28 </md-select>
29 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 29 /*@ngInject*/
30 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 34 /* tbAliasesEntitySelect (ng-model)
35 35 * {
... ... @@ -81,10 +81,8 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $
81 81 position: position,
82 82 fullscreen: false,
83 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 86 scope.updateView();
89 87 }
90 88 },
... ... @@ -96,41 +94,40 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $
96 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 105 scope.updateView = function () {
100   - var value = angular.copy(scope.model);
101   - ngModelCtrl.$setViewValue(value);
102 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 109 function updateDisplayValue() {
114 110 var displayValue;
115 111 var singleValue = true;
116 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 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 131 } else {
135 132 displayValue = $translate.instant('entity.entities');
136 133 }
... ... @@ -142,9 +139,8 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $
142 139
143 140 return {
144 141 restrict: "E",
145   - require: "^ngModel",
146 142 scope: {
147   - entityAliasesInfo:'='
  143 + aliasController:'='
148 144 },
149 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 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 +}
\ No newline at end of file
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2017 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<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 15 */
16 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 24 /*@ngInject*/
19 25 export default function EntityAliasesController(utils, entityService, toast, $scope, $mdDialog, $document, $q, $translate,
20 26 types, config) {
21 27
22 28 var vm = this;
23 29
24   - vm.isSingleEntityAlias = config.isSingleEntityAlias;
25   - vm.singleEntityAlias = config.singleEntityAlias;
  30 + vm.types = types;
26 31 vm.entityAliases = [];
27 32 vm.title = config.customTitle ? config.customTitle : 'entity.aliases';
28 33 vm.disableAdd = config.disableAdd;
29 34 vm.aliasToWidgetsMap = {};
30 35 vm.allowedEntityTypes = config.allowedEntityTypes;
31 36
32   - vm.onFilterEntityChanged = onFilterEntityChanged;
33 37 vm.addAlias = addAlias;
  38 + vm.editAlias = editAlias;
34 39 vm.removeAlias = removeAlias;
35 40
36 41 vm.cancel = cancel;
... ... @@ -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 87 for (aliasId in config.entityAliases) {
87 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 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 144 function removeAlias($event, entityAlias) {
... ... @@ -160,61 +175,36 @@ export default function EntityAliasesController(utils, entityService, toast, $sc
160 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 178 function save() {
173 179
174 180 var entityAliases = {};
175 181 var uniqueAliasList = {};
176 182
177 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 203 if (valid) {
210 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 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 19 <form name="theForm" ng-submit="vm.save()">
20 20 <md-toolbar>
21 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 23 <span flex></span>
24 24 <md-button class="md-icon-button" ng-click="vm.cancel()">
25 25 <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
... ... @@ -28,74 +28,73 @@
28 28 </md-toolbar>
29 29 <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
30 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 41 <md-dialog-content>
32 42 <div class="md-dialog-content">
33 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 85 </div>
95 86 </fieldset>
96 87 </div>
97 88 </md-dialog-content>
98 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 98 <span flex></span>
100 99 <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary">
101 100 {{ 'action.save' | translate }}
... ...
... ... @@ -22,7 +22,8 @@ import selectTargetLayoutTemplate from '../../dashboard/layouts/select-target-la
22 22 /* eslint-enable import/no-unresolved, import/default */
23 23
24 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 28 var vm = this;
28 29
... ... @@ -125,13 +126,8 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog,
125 126 targetDeviceAliases: {}
126 127 };
127 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 132 itembuffer.addWidgetToDashboard(theDashboard, targetState, targetLayout, vm.widget, aliasesInfo, null, 48, null, -1, -1).then(
137 133 function(theDashboard) {
... ...
... ... @@ -26,10 +26,12 @@ import editAttributeValueTemplate from './edit-attribute-value.tpl.html';
26 26 /* eslint-enable import/no-unresolved, import/default */
27 27
28 28 import EditAttributeValueController from './edit-attribute-value.controller';
  29 +import AliasController from '../../api/alias-controller';
29 30
30 31 /*@ngInject*/
31 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 36 var linker = function (scope, element, attrs) {
35 37
... ... @@ -246,15 +248,19 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
246 248 }
247 249
248 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 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 266 scope.enterWidgetMode = function() {
... ... @@ -281,23 +287,28 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
281 287 scope.firstBundle = true;
282 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 306 var dataKeyType = scope.attributeScope === types.latestTelemetry ?
296 307 types.dataKeyType.timeseries : types.dataKeyType.attribute;
297 308
298 309 var datasource = {
299 310 type: types.datasourceType.entity,
300   - entityAliasId: '1',
  311 + entityAliasId: entityAlias.id,
301 312 dataKeys: []
302 313 }
303 314 var i = 0;
... ...
... ... @@ -156,7 +156,7 @@
156 156 rn-swipe-disabled="true">
157 157 <li ng-repeat="widgets in widgetsList">
158 158 <tb-dashboard
159   - aliases-info="aliasesInfo"
  159 + alias-controller="aliasController"
160 160 widgets="widgets"
161 161 get-st-diff="getServerTimeDiff()"
162 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 +}
\ No newline at end of file
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2017 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +
  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 23 import './entity-filter.scss';
24 24
25 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 28 var linker = function (scope, element, attrs, ngModelCtrl) {
29 29
... ... @@ -31,181 +31,76 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti
31 31 element.html(template);
32 32
33 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 106 $compile(element.contents())(scope);
... ... @@ -217,9 +112,8 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti
217 112 require: "^ngModel",
218 113 link: linker,
219 114 scope: {
220   - entityType: '=',
221   - isEdit: '=',
222   - onMatchingEntityChange: '&'
  115 + theForm: '=',
  116 + allowedEntityTypes: '=?'
223 117 }
224 118 };
225 119
... ...
... ... @@ -13,33 +13,24 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 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 27 padding-left: 10px;
31   - .filter-switch {
  28 + .root-state-entity-switch {
32 29 margin: 0;
33 30 }
34   - .filter-label {
  31 + .root-state-entity-label {
35 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 }
\ No newline at end of file
... ...
... ... @@ -15,53 +15,219 @@
15 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 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 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>
\ No newline at end of file
  233 +</div>
... ...