Browse Source

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

TB-61: Improve Alias Filter
pull/165/head
Igor Kulikov 9 years ago
committed by GitHub
parent
commit
2cb71c15b2
  1. 15
      application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
  2. 26
      dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
  3. 2
      dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java
  4. 279
      ui/src/app/api/alias-controller.js
  5. 14
      ui/src/app/api/entity-relation.service.js
  6. 563
      ui/src/app/api/entity.service.js
  7. 207
      ui/src/app/api/subscription.js
  8. 148
      ui/src/app/common/dashboard-utils.service.js
  9. 90
      ui/src/app/common/types.constant.js
  10. 37
      ui/src/app/common/utils.service.js
  11. 37
      ui/src/app/components/dashboard.directive.js
  12. 2
      ui/src/app/components/dashboard.tpl.html
  13. 32
      ui/src/app/components/datakey-config-dialog.controller.js
  14. 11
      ui/src/app/components/datasource-entity.directive.js
  15. 2
      ui/src/app/components/datasource-entity.tpl.html
  16. 8
      ui/src/app/components/datasource-func.directive.js
  17. 38
      ui/src/app/components/datasource-func.scss
  18. 111
      ui/src/app/components/datasource-func.tpl.html
  19. 2
      ui/src/app/components/datasource.directive.js
  20. 2
      ui/src/app/components/datasource.tpl.html
  21. 17
      ui/src/app/components/entity-alias-select.directive.js
  22. 12
      ui/src/app/components/widget-config.directive.js
  23. 4
      ui/src/app/components/widget-config.tpl.html
  24. 320
      ui/src/app/components/widget.controller.js
  25. 85
      ui/src/app/components/widget.directive.js
  26. 69
      ui/src/app/dashboard/add-widget.controller.js
  27. 2
      ui/src/app/dashboard/add-widget.tpl.html
  28. 41
      ui/src/app/dashboard/dashboard.controller.js
  29. 5
      ui/src/app/dashboard/dashboard.tpl.html
  30. 62
      ui/src/app/dashboard/edit-widget.directive.js
  31. 2
      ui/src/app/dashboard/edit-widget.tpl.html
  32. 2
      ui/src/app/dashboard/layouts/dashboard-layout.tpl.html
  33. 0
      ui/src/app/entity/alias/aliases-entity-select-button.tpl.html
  34. 49
      ui/src/app/entity/alias/aliases-entity-select-panel.controller.js
  35. 10
      ui/src/app/entity/alias/aliases-entity-select-panel.tpl.html
  36. 58
      ui/src/app/entity/alias/aliases-entity-select.directive.js
  37. 0
      ui/src/app/entity/alias/aliases-entity-select.scss
  38. 109
      ui/src/app/entity/alias/entity-alias-dialog.controller.js
  39. 19
      ui/src/app/entity/alias/entity-alias-dialog.scss
  40. 73
      ui/src/app/entity/alias/entity-alias-dialog.tpl.html
  41. 152
      ui/src/app/entity/alias/entity-aliases.controller.js
  42. 49
      ui/src/app/entity/alias/entity-aliases.scss
  43. 105
      ui/src/app/entity/alias/entity-aliases.tpl.html
  44. 12
      ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js
  45. 43
      ui/src/app/entity/attribute/attribute-table.directive.js
  46. 2
      ui/src/app/entity/attribute/attribute-table.tpl.html
  47. 106
      ui/src/app/entity/entity-aliases.tpl.html
  48. 205
      ui/src/app/entity/entity-filter-view.directive.js
  49. 34
      ui/src/app/entity/entity-filter-view.scss
  50. 24
      ui/src/app/entity/entity-filter-view.tpl.html
  51. 232
      ui/src/app/entity/entity-filter.directive.js
  52. 31
      ui/src/app/entity/entity-filter.scss
  53. 262
      ui/src/app/entity/entity-filter.tpl.html
  54. 130
      ui/src/app/entity/entity-list.directive.js
  55. 30
      ui/src/app/entity/entity-list.scss
  56. 52
      ui/src/app/entity/entity-list.tpl.html
  57. 3
      ui/src/app/entity/entity-subtype-autocomplete.directive.js
  58. 146
      ui/src/app/entity/entity-subtype-list.directive.js
  59. 30
      ui/src/app/entity/entity-subtype-list.scss
  60. 54
      ui/src/app/entity/entity-subtype-list.tpl.html
  61. 111
      ui/src/app/entity/entity-type-list.directive.js
  62. 30
      ui/src/app/entity/entity-type-list.scss
  63. 54
      ui/src/app/entity/entity-type-list.tpl.html
  64. 35
      ui/src/app/entity/entity-type-select.directive.js
  65. 18
      ui/src/app/entity/index.js
  66. 85
      ui/src/app/entity/relation/relation-filters.directive.js
  67. 77
      ui/src/app/entity/relation/relation-filters.scss
  68. 67
      ui/src/app/entity/relation/relation-filters.tpl.html
  69. 4
      ui/src/app/entity/relation/relation-table.directive.js
  70. 2
      ui/src/app/entity/relation/relation-type-autocomplete.directive.js
  71. 2
      ui/src/app/entity/relation/relation-type-autocomplete.tpl.html
  72. 76
      ui/src/app/import-export/import-export.service.js
  73. 112
      ui/src/app/locale/locale.constant.js
  74. 37
      ui/src/app/services/item-buffer.service.js
  75. 9
      ui/src/scss/main.scss

15
application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java

@ -250,6 +250,21 @@ public class EntityRelationController extends BaseController {
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations/info", method = RequestMethod.POST)
@ResponseBody
public List<EntityRelationInfo> findInfoByQuery(@RequestBody EntityRelationsQuery query) throws ThingsboardException {
checkNotNull(query);
checkNotNull(query.getParameters());
checkNotNull(query.getFilters());
checkEntityId(query.getParameters().getEntityId());
try {
return checkNotNull(relationService.findInfoByQuery(query).get());
} catch (Exception e) {
throw handleException(e);
}
}
private RelationTypeGroup parseRelationTypeGroup(String strRelationTypeGroup, RelationTypeGroup defaultValue) {
RelationTypeGroup result = defaultValue;
if (strRelationTypeGroup != null && strRelationTypeGroup.trim().length()>0) {

26
dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java

@ -191,7 +191,7 @@ public class BaseRelationService implements RelationService {
@Override
public ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query) {
log.trace("Executing findByQuery [{}][{}]", query);
log.trace("Executing findByQuery [{}]", query);
RelationsSearchParameters params = query.getParameters();
final List<EntityTypeFilter> filters = query.getFilters();
if (filters == null || filters.isEmpty()) {
@ -224,6 +224,30 @@ public class BaseRelationService implements RelationService {
}
}
@Override
public ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(EntityRelationsQuery query) {
log.trace("Executing findInfoByQuery [{}]", query);
ListenableFuture<List<EntityRelation>> relations = findByQuery(query);
EntitySearchDirection direction = query.getParameters().getDirection();
ListenableFuture<List<EntityRelationInfo>> relationsInfo = Futures.transform(relations,
(AsyncFunction<List<EntityRelation>, List<EntityRelationInfo>>) relations1 -> {
List<ListenableFuture<EntityRelationInfo>> futures = new ArrayList<>();
relations1.stream().forEach(relation ->
futures.add(fetchRelationInfoAsync(relation,
relation2 -> direction == EntitySearchDirection.FROM ? relation2.getTo() : relation2.getFrom(),
(EntityRelationInfo relationInfo, String entityName) -> {
if (direction == EntitySearchDirection.FROM) {
relationInfo.setToName(entityName);
} else {
relationInfo.setFromName(entityName);
}
}))
);
return Futures.successfulAsList(futures);
});
return relationsInfo;
}
protected void validate(EntityRelation relation) {
if (relation == null) {
throw new DataValidationException("Relation type should be specified!");

2
dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java

@ -52,6 +52,8 @@ public interface RelationService {
ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query);
ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(EntityRelationsQuery query);
// TODO: This method may be useful for some validations in the future
// ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to);

279
ui/src/app/api/alias-controller.js

@ -0,0 +1,279 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const varsRegex = /\$\{([^\}]*)\}/g;
export default class AliasController {
constructor($scope, $q, $filter, utils, types, entityService, stateController, entityAliases) {
this.$scope = $scope;
this.$q = $q;
this.$filter = $filter;
this.utils = utils;
this.types = types;
this.entityService = entityService;
this.stateController = stateController;
this.entityAliases = angular.copy(entityAliases);
this.resolvedAliases = {};
this.resolvedAliasesPromise = {};
this.resolvedAliasesToStateEntities = {};
}
updateEntityAliases(newEntityAliases) {
var changedAliasIds = [];
for (var aliasId in newEntityAliases) {
var newEntityAlias = newEntityAliases[aliasId];
var prevEntityAlias = this.entityAliases[aliasId];
if (!angular.equals(newEntityAlias, prevEntityAlias)) {
changedAliasIds.push(aliasId);
this.setAliasUnresolved(aliasId);
}
}
for (aliasId in this.entityAliases) {
if (!newEntityAliases[aliasId]) {
changedAliasIds.push(aliasId);
this.setAliasUnresolved(aliasId);
}
}
this.entityAliases = angular.copy(newEntityAliases);
if (changedAliasIds.length) {
this.$scope.$broadcast('entityAliasesChanged', changedAliasIds);
}
}
dashboardStateChanged() {
var newEntityId = this.stateController.getStateParams().entityId;
var changedAliasIds = [];
for (var aliasId in this.resolvedAliasesToStateEntities) {
var prevEntityId = this.resolvedAliasesToStateEntities[aliasId];
if (!angular.equals(newEntityId, prevEntityId)) {
changedAliasIds.push(aliasId);
this.setAliasUnresolved(aliasId);
}
}
if (changedAliasIds.length) {
this.$scope.$broadcast('entityAliasesChanged', changedAliasIds);
}
}
setAliasUnresolved(aliasId) {
delete this.resolvedAliases[aliasId];
delete this.resolvedAliasesPromise[aliasId];
delete this.resolvedAliasesToStateEntities[aliasId];
}
getEntityAliases() {
return this.entityAliases;
}
getAliasInfo(aliasId) {
var deferred = this.$q.defer();
var aliasInfo = this.resolvedAliases[aliasId];
if (aliasInfo) {
deferred.resolve(aliasInfo);
return deferred.promise;
} else if (this.resolvedAliasesPromise[aliasId]) {
return this.resolvedAliasesPromise[aliasId];
} else {
this.resolvedAliasesPromise[aliasId] = deferred.promise;
var aliasCtrl = this;
var entityAlias = this.entityAliases[aliasId];
if (entityAlias) {
this.entityService.resolveAlias(entityAlias, this.stateController.getStateParams()).then(
function success(aliasInfo) {
aliasCtrl.resolvedAliases[aliasId] = aliasInfo;
if (aliasInfo.stateEntity) {
aliasCtrl.resolvedAliasesToStateEntities[aliasId] =
aliasCtrl.stateController.getStateParams().entityId;
}
aliasCtrl.$scope.$broadcast('entityAliasResolved', aliasId);
deferred.resolve(aliasInfo);
},
function fail() {
deferred.reject();
}
);
} else {
deferred.reject();
}
return this.resolvedAliasesPromise[aliasId];
}
}
resolveDatasource(datasource) {
var deferred = this.$q.defer();
if (datasource.type === this.types.datasourceType.entity) {
if (datasource.entityAliasId) {
this.getAliasInfo(datasource.entityAliasId).then(
function success(aliasInfo) {
datasource.aliasName = aliasInfo.alias;
if (aliasInfo.resolveMultiple) {
var newDatasource;
var resolvedEntities = aliasInfo.resolvedEntities;
if (resolvedEntities && resolvedEntities.length) {
var datasources = [];
for (var i=0;i<resolvedEntities.length;i++) {
var resolvedEntity = resolvedEntities[i];
newDatasource = angular.copy(datasource);
newDatasource.entityId = resolvedEntity.id;
newDatasource.entityType = resolvedEntity.entityType;
newDatasource.entityName = resolvedEntity.name;
newDatasource.name = resolvedEntity.name;
newDatasource.generated = i > 0 ? true : false;
datasources.push(newDatasource);
}
deferred.resolve(datasources);
} else {
if (aliasInfo.stateEntity) {
newDatasource = angular.copy(datasource);
newDatasource.unresolvedStateEntity = true;
deferred.resolve([newDatasource]);
} else {
deferred.reject();
}
}
} else {
var entity = aliasInfo.currentEntity;
if (entity) {
datasource.entityId = entity.id;
datasource.entityType = entity.entityType;
datasource.entityName = entity.name;
datasource.name = entity.name;
deferred.resolve([datasource]);
} else {
if (aliasInfo.stateEntity) {
datasource.unresolvedStateEntity = true;
deferred.resolve([datasource]);
} else {
deferred.reject();
}
}
}
},
function fail() {
deferred.reject();
}
);
} else { // entityId
datasource.aliasName = datasource.entityName;
datasource.name = datasource.entityName;
deferred.resolve([datasource]);
}
} else { // function
deferred.resolve([datasource]);
}
return deferred.promise;
}
resolveDatasources(datasources) {
function updateDataKeyLabel(dataKey, datasource) {
if (!dataKey.pattern) {
dataKey.pattern = angular.copy(dataKey.label);
}
var pattern = dataKey.pattern;
var label = dataKey.pattern;
var match = varsRegex.exec(pattern);
while (match !== null) {
var variable = match[0];
var variableName = match[1];
if (variableName === 'dsName') {
label = label.split(variable).join(datasource.name);
} else if (variableName === 'entityName') {
label = label.split(variable).join(datasource.entityName);
} else if (variableName === 'deviceName') {
label = label.split(variable).join(datasource.entityName);
} else if (variableName === 'aliasName') {
label = label.split(variable).join(datasource.aliasName);
}
match = varsRegex.exec(pattern);
}
dataKey.label = label;
}
function updateDatasourceKeyLabels(datasource) {
for (var dk = 0; dk < datasource.dataKeys.length; dk++) {
updateDataKeyLabel(datasource.dataKeys[dk], datasource);
}
}
var deferred = this.$q.defer();
var newDatasources = angular.copy(datasources);
var datasorceResolveTasks = [];
var aliasCtrl = this;
newDatasources.forEach(function (datasource) {
var resolveDatasourceTask = aliasCtrl.resolveDatasource(datasource);
datasorceResolveTasks.push(resolveDatasourceTask);
});
this.$q.all(datasorceResolveTasks).then(
function success(datasourcesArrays) {
var datasources = [].concat.apply([], datasourcesArrays);
datasources = aliasCtrl.$filter('orderBy')(datasources, '+generated');
var index = 0;
var functionIndex = 0;
datasources.forEach(function(datasource) {
if (datasource.type === aliasCtrl.types.datasourceType.function) {
var name;
if (datasource.name && datasource.name.length) {
name = datasource.name;
} else {
functionIndex++;
name = aliasCtrl.types.datasourceType.function;
if (functionIndex > 1) {
name += ' ' + functionIndex;
}
}
datasource.name = name;
datasource.aliasName = name;
datasource.entityName = name;
} else if (datasource.unresolvedStateEntity) {
datasource.name = "Unresolved";
datasource.entityName = "Unresolved";
}
datasource.dataKeys.forEach(function(dataKey) {
if (datasource.generated) {
dataKey._hash = Math.random();
dataKey.color = aliasCtrl.utils.getMaterialColor(index);
}
index++;
});
updateDatasourceKeyLabels(datasource);
});
deferred.resolve(datasources);
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
getInstantAliasInfo(aliasId) {
return this.resolvedAliases[aliasId];
}
updateCurrentAliasEntity(aliasId, currentEntity) {
var aliasInfo = this.resolvedAliases[aliasId];
if (aliasInfo) {
var prevCurrentEntity = aliasInfo.currentEntity;
if (!angular.equals(currentEntity, prevCurrentEntity)) {
aliasInfo.currentEntity = currentEntity;
this.$scope.$broadcast('entityAliasesChanged', [aliasId]);
}
}
}
}

14
ui/src/app/api/entity-relation.service.js

@ -30,7 +30,8 @@ function EntityRelationService($http, $q) {
findByTo: findByTo,
findInfoByTo: findInfoByTo,
findByToAndType: findByToAndType,
findByQuery: findByQuery
findByQuery: findByQuery,
findInfoByQuery: findInfoByQuery
}
return service;
@ -159,4 +160,15 @@ function EntityRelationService($http, $q) {
return deferred.promise;
}
function findInfoByQuery(query) {
var deferred = $q.defer();
var url = '/api/relations/info';
$http.post(url, query).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
}

563
ui/src/app/api/entity.service.js

@ -27,10 +27,14 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
getEntity: getEntity,
getEntities: getEntities,
getEntitiesByNameFilter: getEntitiesByNameFilter,
processEntityAliases: processEntityAliases,
getEntityKeys: getEntityKeys,
resolveAlias: resolveAlias,
resolveAliasFilter: resolveAliasFilter,
checkEntityAlias: checkEntityAlias,
createDatasoucesFromSubscriptionsInfo: createDatasoucesFromSubscriptionsInfo,
filterAliasByEntityTypes: filterAliasByEntityTypes,
getAliasFilterTypesByEntityTypes: getAliasFilterTypesByEntityTypes,
prepareAllowedEntityTypesList: prepareAllowedEntityTypesList,
getEntityKeys: getEntityKeys,
createDatasourcesFromSubscriptionsInfo: createDatasourcesFromSubscriptionsInfo,
getRelatedEntities: getRelatedEntities,
saveRelatedEntity: saveRelatedEntity,
getRelatedEntity: getRelatedEntity,
@ -173,6 +177,54 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
return deferred.promise;
}
function getSingleTenantByPageLinkPromise(pageLink) {
var user = userService.getCurrentUser();
var tenantId = user.tenantId;
var deferred = $q.defer();
tenantService.getTenant(tenantId).then(
function success(tenant) {
var tenantName = tenant.name;
var result = {
data: [],
nextPageLink: pageLink,
hasNext: false
};
if (tenantName.toLowerCase().startsWith(pageLink.textSearch)) {
result.data.push(tenant);
}
deferred.resolve(result);
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function getSingleCustomerByPageLinkPromise(pageLink) {
var user = userService.getCurrentUser();
var customerId = user.customerId;
var deferred = $q.defer();
customerService.getCustomer(customerId).then(
function success(customer) {
var customerName = customer.name;
var result = {
data: [],
nextPageLink: pageLink,
hasNext: false
};
if (customerName.toLowerCase().startsWith(pageLink.textSearch)) {
result.data.push(customer);
}
deferred.resolve(result);
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function getEntitiesByPageLinkPromise(entityType, pageLink, config, subType) {
var promise;
var user = userService.getCurrentUser();
@ -193,10 +245,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
}
break;
case types.entityType.tenant:
promise = tenantService.getTenants(pageLink);
if (user.authority === 'TENANT_ADMIN') {
promise = getSingleTenantByPageLinkPromise(pageLink);
} else {
promise = tenantService.getTenants(pageLink);
}
break;
case types.entityType.customer:
promise = customerService.getCustomers(pageLink);
if (user.authority === 'CUSTOMER_USER') {
promise = getSingleCustomerByPageLinkPromise(pageLink);
} else {
promise = customerService.getCustomers(pageLink);
}
break;
case types.entityType.rule:
promise = ruleService.getAllRules(pageLink);
@ -221,17 +281,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
return promise;
}
function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config, subType) {
var deferred = $q.defer();
var pageLink = {limit: limit, textSearch: entityNameFilter};
function getEntitiesByPageLink(entityType, pageLink, config, subType, data, deferred) {
var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config, subType);
if (promise) {
promise.then(
function success(result) {
if (result.data && result.data.length > 0) {
deferred.resolve(result.data);
data = data.concat(result.data);
if (result.hasNext) {
pageLink = result.nextPageLink;
getEntitiesByPageLink(entityType, pageLink, config, subType, data, deferred);
} else {
deferred.resolve(null);
if (data && data.length > 0) {
deferred.resolve(data);
} else {
deferred.resolve(null);
}
}
},
function fail() {
@ -241,92 +305,418 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
} else {
deferred.resolve(null);
}
}
function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config, subType) {
var deferred = $q.defer();
var pageLink = {limit: limit, textSearch: entityNameFilter};
if (limit == -1) { // all
var data = [];
pageLink.limit = 100;
getEntitiesByPageLink(entityType, pageLink, config, subType, data, deferred);
} else {
var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config, subType);
if (promise) {
promise.then(
function success(result) {
if (result.data && result.data.length > 0) {
deferred.resolve(result.data);
} else {
deferred.resolve(null);
}
},
function fail() {
deferred.resolve(null);
}
);
} else {
deferred.resolve(null);
}
}
return deferred.promise;
}
function entityToEntityInfo(entityType, entity) {
return { name: entity.name, entityType: entityType, id: entity.id.id };
function entityToEntityInfo(entity) {
return { name: entity.name, entityType: entity.id.entityType, id: entity.id.id };
}
function entitiesToEntitiesInfo(entityType, entities) {
function entityRelationInfoToEntityInfo(entityRelationInfo, direction) {
var entityId = direction == types.entitySearchDirection.from ? entityRelationInfo.to : entityRelationInfo.from;
var name = direction == types.entitySearchDirection.from ? entityRelationInfo.toName : entityRelationInfo.fromName;
return {
name: name,
entityType: entityId.entityType,
id: entityId.id
};
}
function entitiesToEntitiesInfo(entities) {
var entitiesInfo = [];
for (var d = 0; d < entities.length; d++) {
entitiesInfo.push(entityToEntityInfo(entityType, entities[d]));
entitiesInfo.push(entityToEntityInfo(entities[d]));
}
return entitiesInfo;
}
function processEntityAlias(index, aliasIds, entityAliases, resolution, deferred) {
if (index < aliasIds.length) {
var aliasId = aliasIds[index];
var entityAlias = entityAliases[aliasId];
var alias = entityAlias.alias;
var entityFilter = entityAlias.entityFilter;
if (entityFilter.useFilter) {
var entityNameFilter = entityFilter.entityNameFilter;
getEntitiesByNameFilter(entityAlias.entityType, entityNameFilter, 100).then(
function(entities) {
if (entities && entities != null) {
var resolvedAlias = {alias: alias, entityType: entityAlias.entityType, entityId: entities[0].id.id};
resolution.aliasesInfo.entityAliases[aliasId] = resolvedAlias;
resolution.aliasesInfo.entityAliasesInfo[aliasId] = entitiesToEntitiesInfo(entityAlias.entityType, entities);
index++;
processEntityAlias(index, aliasIds, entityAliases, resolution, deferred);
function entityRelationInfosToEntitiesInfo(entityRelations, direction) {
var entitiesInfo = [];
for (var d = 0; d < entityRelations.length; d++) {
entitiesInfo.push(entityRelationInfoToEntityInfo(entityRelations[d], direction));
}
return entitiesInfo;
}
function resolveAlias(entityAlias, stateParams) {
var deferred = $q.defer();
var filter = entityAlias.filter;
resolveAliasFilter(filter, stateParams, -1).then(
function (result) {
var aliasInfo = {
alias: entityAlias.alias,
stateEntity: result.stateEntity,
resolveMultiple: filter.resolveMultiple
};
aliasInfo.resolvedEntities = result.entities;
aliasInfo.currentEntity = null;
if (aliasInfo.resolvedEntities.length) {
aliasInfo.currentEntity = aliasInfo.resolvedEntities[0];
}
deferred.resolve(aliasInfo);
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function resolveAliasFilter(filter, stateParams, maxItems) {
var deferred = $q.defer();
var result = {
entities: [],
stateEntity: false
};
switch (filter.type) {
case types.aliasFilterType.entityList.value:
getEntities(filter.entityType, filter.entityList).then(
function success(entities) {
if (entities && entities.length) {
result.entities = entitiesToEntitiesInfo(entities);
deferred.resolve(result);
} else {
if (!resolution.error) {
resolution.error = 'dashboard.invalid-aliases-config';
}
index++;
processEntityAlias(index, aliasIds, entityAliases, resolution, deferred);
deferred.reject();
}
});
} else {
var entityList = entityFilter.entityList;
getEntities(entityAlias.entityType, entityList).then(
},
function fail() {
deferred.reject();
}
);
break;
case types.aliasFilterType.entityName.value:
getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems).then(
function success(entities) {
if (entities && entities.length > 0) {
var resolvedAlias = {alias: alias, entityType: entityAlias.entityType, entityId: entities[0].id.id};
resolution.aliasesInfo.entityAliases[aliasId] = resolvedAlias;
resolution.aliasesInfo.entityAliasesInfo[aliasId] = entitiesToEntitiesInfo(entityAlias.entityType, entities);
index++;
processEntityAlias(index, aliasIds, entityAliases, resolution, deferred);
if (entities && entities.length) {
result.entities = entitiesToEntitiesInfo(entities);
deferred.resolve(result);
} else {
if (!resolution.error) {
resolution.error = 'dashboard.invalid-aliases-config';
}
index++;
processEntityAlias(index, aliasIds, entityAliases, resolution, deferred);
deferred.reject();
}
},
function fail() {
deferred.reject();
}
);
break;
case types.aliasFilterType.stateEntity.value:
result.stateEntity = true;
if (stateParams && stateParams.entityId) {
getEntity(stateParams.entityId.entityType, stateParams.entityId.id).then(
function success(entity) {
result.entities = entitiesToEntitiesInfo([entity]);
deferred.resolve(result);
},
function fail() {
deferred.reject();
}
);
} else {
deferred.resolve(result);
}
break;
case types.aliasFilterType.assetType.value:
getEntitiesByNameFilter(types.entityType.asset, filter.assetNameFilter, maxItems, null, filter.assetType).then(
function success(entities) {
if (entities && entities.length) {
result.entities = entitiesToEntitiesInfo(entities);
deferred.resolve(result);
} else {
deferred.reject();
}
},
function fail() {
if (!resolution.error) {
resolution.error = 'dashboard.invalid-aliases-config';
deferred.reject();
}
);
break;
case types.aliasFilterType.deviceType.value:
getEntitiesByNameFilter(types.entityType.device, filter.deviceNameFilter, maxItems, null, filter.deviceType).then(
function success(entities) {
if (entities && entities.length) {
result.entities = entitiesToEntitiesInfo(entities);
deferred.resolve(result);
} else {
deferred.reject();
}
index++;
processEntityAlias(index, aliasIds, entityAliases, resolution, deferred);
},
function fail() {
deferred.reject();
}
);
break;
case types.aliasFilterType.relationsQuery.value:
result.stateEntity = filter.rootStateEntity;
var rootEntityType;
var rootEntityId;
if (result.stateEntity && stateParams && stateParams.entityId) {
rootEntityType = stateParams.entityId.entityType;
rootEntityId = stateParams.entityId.id;
} else if (!result.stateEntity) {
rootEntityType = filter.rootEntity.entityType;
rootEntityId = filter.rootEntity.id;
}
if (rootEntityType && rootEntityId) {
var searchQuery = {
parameters: {
rootId: rootEntityId,
rootType: rootEntityType,
direction: filter.direction
},
filters: filter.filters
};
searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1;
entityRelationService.findInfoByQuery(searchQuery).then(
function success(allRelations) {
if (allRelations && allRelations.length) {
if (angular.isDefined(maxItems) && maxItems > 0) {
var limit = Math.min(allRelations.length, maxItems);
allRelations.length = limit;
}
result.entities = entityRelationInfosToEntitiesInfo(allRelations, filter.direction);
deferred.resolve(result);
} else {
deferred.reject();
}
},
function fail() {
deferred.reject();
}
);
} else {
deferred.resolve(result);
}
break;
case types.aliasFilterType.assetSearchQuery.value:
case types.aliasFilterType.deviceSearchQuery.value:
result.stateEntity = filter.rootStateEntity;
if (result.stateEntity && stateParams && stateParams.entityId) {
rootEntityType = stateParams.entityId.entityType;
rootEntityId = stateParams.entityId.id;
} else if (!result.stateEntity) {
rootEntityType = filter.rootEntity.entityType;
rootEntityId = filter.rootEntity.id;
}
if (rootEntityType && rootEntityId) {
searchQuery = {
parameters: {
rootId: rootEntityId,
rootType: rootEntityType,
direction: filter.direction
},
relationType: filter.relationType
};
searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1;
var findByQueryPromise;
if (filter.type == types.aliasFilterType.assetSearchQuery.value) {
searchQuery.assetTypes = filter.assetTypes;
findByQueryPromise = assetService.findByQuery(searchQuery, false);
} else if (filter.type == types.aliasFilterType.deviceSearchQuery.value) {
searchQuery.deviceTypes = filter.deviceTypes;
findByQueryPromise = deviceService.findByQuery(searchQuery, false);
}
findByQueryPromise.then(
function success(entities) {
if (entities && entities.length) {
if (angular.isDefined(maxItems) && maxItems > 0) {
var limit = Math.min(entities.length, maxItems);
entities.length = limit;
}
result.entities = entitiesToEntitiesInfo(entities);
deferred.resolve(result);
} else {
deferred.reject();
}
},
function fail() {
deferred.reject();
}
);
} else {
deferred.resolve(result);
}
break;
}
return deferred.promise;
}
function filterAliasByEntityTypes(entityAlias, entityTypes) {
var filter = entityAlias.filter;
if (filterAliasFilterTypeByEntityTypes(filter.type, entityTypes)) {
switch (filter.type) {
case types.aliasFilterType.entityList.value:
return entityTypes.indexOf(filter.entityType) > -1 ? true : false;
case types.aliasFilterType.entityName.value:
return entityTypes.indexOf(filter.entityType) > -1 ? true : false;
case types.aliasFilterType.stateEntity.value:
return true;
case types.aliasFilterType.assetType.value:
return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false;
case types.aliasFilterType.deviceType.value:
return entityTypes.indexOf(types.entityType.device) > -1 ? true : false;
case types.aliasFilterType.relationsQuery.value:
if (filter.filters && filter.filters.length) {
var match = false;
for (var f=0;f<filter.filters.length;f++) {
var relationFilter = filter.filters[f];
if (relationFilter.entityTypes && relationFilter.entityTypes.length) {
for (var et=0;et<relationFilter.entityTypes.length;et++) {
if (entityTypes.indexOf(relationFilter.entityTypes[et]) > -1) {
match = true;
break;
}
}
} else {
match = true;
break;
}
}
return match;
} else {
return true;
}
case types.aliasFilterType.assetSearchQuery.value:
return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false;
case types.aliasFilterType.deviceSearchQuery.value:
return entityTypes.indexOf(types.entityType.device) > -1 ? true : false;
}
} else {
deferred.resolve(resolution);
}
return false;
}
function processEntityAliases(entityAliases) {
var deferred = $q.defer();
var resolution = {
aliasesInfo: {
entityAliases: {},
entityAliasesInfo: {}
function filterAliasFilterTypeByEntityType(aliasFilterType, entityType) {
switch (aliasFilterType) {
case types.aliasFilterType.entityList.value:
return true;
case types.aliasFilterType.entityName.value:
return true;
case types.aliasFilterType.stateEntity.value:
return true;
case types.aliasFilterType.assetType.value:
return entityType === types.entityType.asset;
case types.aliasFilterType.deviceType.value:
return entityType === types.entityType.device;
case types.aliasFilterType.relationsQuery.value:
return true;
case types.aliasFilterType.assetSearchQuery.value:
return entityType === types.entityType.asset;
case types.aliasFilterType.deviceSearchQuery.value:
return entityType === types.entityType.device;
}
return false;
}
function filterAliasFilterTypeByEntityTypes(aliasFilterType, entityTypes) {
if (!entityTypes || !entityTypes.length) {
return true;
}
var valid = false;
entityTypes.forEach(function(entityType) {
valid = valid || filterAliasFilterTypeByEntityType(aliasFilterType, entityType);
});
return valid;
}
function getAliasFilterTypesByEntityTypes(entityTypes) {
var allAliasFilterTypes = types.aliasFilterType;
if (!entityTypes || !entityTypes.length) {
return allAliasFilterTypes;
}
var result = {};
for (var type in allAliasFilterTypes) {
var aliasFilterType = allAliasFilterTypes[type];
if (filterAliasFilterTypeByEntityTypes(aliasFilterType.value, entityTypes)) {
result[type] = aliasFilterType;
}
};
var aliasIds = [];
if (entityAliases) {
for (var aliasId in entityAliases) {
aliasIds.push(aliasId);
}
return result;
}
function prepareAllowedEntityTypesList(allowedEntityTypes) {
var authority = userService.getAuthority();
var entityTypes = {};
switch(authority) {
case 'SYS_ADMIN':
entityTypes.tenant = types.entityType.tenant;
entityTypes.rule = types.entityType.rule;
entityTypes.plugin = types.entityType.plugin;
break;
case 'TENANT_ADMIN':
entityTypes.device = types.entityType.device;
entityTypes.asset = types.entityType.asset;
entityTypes.tenant = types.entityType.tenant;
entityTypes.customer = types.entityType.customer;
entityTypes.rule = types.entityType.rule;
entityTypes.plugin = types.entityType.plugin;
entityTypes.dashboard = types.entityType.dashboard;
break;
case 'CUSTOMER_USER':
entityTypes.device = types.entityType.device;
entityTypes.asset = types.entityType.asset;
entityTypes.customer = types.entityType.customer;
entityTypes.dashboard = types.entityType.dashboard;
break;
}
if (allowedEntityTypes) {
for (var entityType in entityTypes) {
if (allowedEntityTypes.indexOf(entityTypes[entityType]) === -1) {
delete entityTypes[entityType];
}
}
}
processEntityAlias(0, aliasIds, entityAliases, resolution, deferred);
return entityTypes;
}
function checkEntityAlias(entityAlias) {
var deferred = $q.defer();
resolveAliasFilter(entityAlias.filter, null, 1).then(
function success(result) {
if (result.stateEntity) {
deferred.resolve(true);
} else {
var entities = result.entities;
if (entities && entities.length) {
deferred.resolve(true);
} else {
deferred.resolve(false);
}
}
},
function fail() {
deferred.resolve(false);
}
);
return deferred.promise;
}
@ -354,40 +744,13 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
}
}
deferred.resolve(result);
}, function fail(response) {
deferred.reject(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function checkEntityAlias(entityAlias) {
var deferred = $q.defer();
var entityType = entityAlias.entityType;
var entityFilter = entityAlias.entityFilter;
var promise;
if (entityFilter.useFilter) {
var entityNameFilter = entityFilter.entityNameFilter;
promise = getEntitiesByNameFilter(entityType, entityNameFilter, 1);
} else {
var entityList = entityFilter.entityList;
promise = getEntities(entityType, entityList);
}
promise.then(
function success(entities) {
if (entities && entities.length > 0) {
deferred.resolve(true);
} else {
deferred.resolve(false);
}
},
function fail() {
deferred.resolve(false);
}
);
return deferred.promise;
}
function createDatasoucesFromSubscriptionsInfo(subscriptionsInfo) {
function createDatasourcesFromSubscriptionsInfo(subscriptionsInfo) {
var deferred = $q.defer();
var datasources = [];
processSubscriptionsInfo(0, subscriptionsInfo, datasources, deferred);
@ -822,4 +1185,4 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
}
}
}
}

207
ui/src/app/api/subscription.js

@ -39,6 +39,9 @@ export default class Subscription {
this.cafs = {};
this.registrations = [];
var subscription = this;
var deferred = this.ctx.$q.defer();
if (this.type === this.ctx.types.widgetType.rpc.value) {
this.callbacks.rpcStateChanged = this.callbacks.rpcStateChanged || function(){};
this.callbacks.onRpcSuccess = this.callbacks.onRpcSuccess || function(){};
@ -56,7 +59,11 @@ export default class Subscription {
this.rpcEnabled = false;
this.executingRpcRequest = false;
this.executingPromises = [];
this.initRpc();
this.initRpc().then(
function() {
deferred.resolve(subscription);
}
);
} else {
this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || function(){};
this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || function(){};
@ -66,6 +73,15 @@ export default class Subscription {
this.datasources = this.ctx.utils.validateDatasources(options.datasources);
this.datasourceListeners = [];
/*
* data = array of datasourceData
* datasourceData = {
* tbDatasource,
* dataKey, { name, config }
* data = array of [time, value]
* }
*/
this.data = [];
this.hiddenData = [];
this.originalTimewindow = null;
@ -103,11 +119,41 @@ export default class Subscription {
this.legendConfig.showMax === true ||
this.legendConfig.showAvg === true ||
this.legendConfig.showTotal === true);
this.initDataSubscription();
this.initDataSubscription().then(
function success() {
deferred.resolve(subscription);
},
function fail() {
deferred.reject();
}
);
}
return deferred.promise;
}
initDataSubscription() {
var deferred = this.ctx.$q.defer();
if (!this.ctx.aliasController) {
this.configureData();
deferred.resolve();
} else {
var subscription = this;
this.ctx.aliasController.resolveDatasources(this.datasources).then(
function success(datasources) {
subscription.datasources = datasources;
subscription.configureData();
deferred.resolve();
},
function fail() {
deferred.reject();
}
);
}
return deferred.promise;
}
configureData() {
var dataIndex = 0;
for (var i = 0; i < this.datasources.length; i++) {
var datasource = this.datasources[i];
@ -199,21 +245,46 @@ export default class Subscription {
}
initRpc() {
var deferred = this.ctx.$q.defer();
if (this.targetDeviceAliasIds && this.targetDeviceAliasIds.length > 0) {
this.targetDeviceAliasId = this.targetDeviceAliasIds[0];
if (this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId]) {
this.targetDeviceId = this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId].entityId;
var subscription = this;
this.ctx.aliasController.getAliasInfo(this.targetDeviceAliasId).then(
function success(aliasInfo) {
if (aliasInfo.currentEntity && aliasInfo.currentEntity.entityType == subscription.ctx.types.entityType.device) {
subscription.targetDeviceId = aliasInfo.currentEntity.id;
if (subscription.targetDeviceId) {
subscription.rpcEnabled = true;
} else {
subscription.rpcEnabled = subscription.ctx.$scope.widgetEditMode ? true : false;
}
subscription.callbacks.rpcStateChanged(subscription);
deferred.resolve();
} else {
subscription.rpcEnabled = false;
subscription.callbacks.rpcStateChanged(subscription);
deferred.resolve();
}
},
function fail () {
subscription.rpcEnabled = false;
subscription.callbacks.rpcStateChanged(subscription);
deferred.resolve();
}
);
} else {
if (this.targetDeviceIds && this.targetDeviceIds.length > 0) {
this.targetDeviceId = this.targetDeviceIds[0];
}
} else if (this.targetDeviceIds && this.targetDeviceIds.length > 0) {
this.targetDeviceId = this.targetDeviceIds[0];
}
if (this.targetDeviceId) {
this.rpcEnabled = true;
} else {
this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false;
if (this.targetDeviceId) {
this.rpcEnabled = true;
} else {
this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false;
}
this.callbacks.rpcStateChanged(this);
deferred.resolve();
}
this.callbacks.rpcStateChanged(this);
return deferred.promise;
}
clearRpcError() {
@ -319,11 +390,11 @@ export default class Subscription {
this.onDataUpdated();
}
onAliasesChanged() {
onAliasesChanged(aliasIds) {
if (this.type === this.ctx.types.widgetType.rpc.value) {
this.checkRpcTarget();
return this.checkRpcTarget(aliasIds);
} else {
this.checkSubscriptions();
return this.checkSubscriptions(aliasIds);
}
}
@ -481,39 +552,6 @@ export default class Subscription {
var datasource = this.datasources[i];
if (angular.isFunction(datasource))
continue;
var entityId = null;
var entityType = null;
if (datasource.type === this.ctx.types.datasourceType.entity) {
var aliasName = null;
var entityName = null;
if (datasource.entityId) {
entityId = datasource.entityId;
entityType = datasource.entityType;
datasource.name = datasource.entityName;
aliasName = datasource.entityName;
entityName = datasource.entityName;
} else if (datasource.entityAliasId) {
if (this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId]) {
entityId = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].entityId;
entityType = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].entityType;
datasource.name = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].alias;
aliasName = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].alias;
entityName = '';
var entitiesInfo = this.ctx.aliasesInfo.entityAliasesInfo[datasource.entityAliasId];
for (var d = 0; d < entitiesInfo.length; d++) {
if (entitiesInfo[d].id === entityId) {
entityName = entitiesInfo[d].name;
break;
}
}
}
}
} else {
datasource.name = datasource.name || this.ctx.types.datasourceType.function;
}
for (var dk = 0; dk < datasource.dataKeys.length; dk++) {
updateDataKeyLabel(datasource.dataKeys[dk], datasource.name, entityName, aliasName);
}
var subscription = this;
@ -521,8 +559,8 @@ export default class Subscription {
subscriptionType: this.type,
subscriptionTimewindow: this.subscriptionTimewindow,
datasource: datasource,
entityType: entityType,
entityId: entityId,
entityType: datasource.entityType,
entityId: datasource.entityId,
dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) {
subscription.dataUpdated(data, datasourceIndex, dataKeyIndex, apply);
},
@ -544,6 +582,10 @@ export default class Subscription {
this.datasourceListeners.push(listener);
this.ctx.datasourceService.subscribeToDatasource(listener);
if (datasource.unresolvedStateEntity) {
this.notifyDataLoaded();
this.onDataUpdated();
}
}
}
@ -557,48 +599,26 @@ export default class Subscription {
}
}
checkRpcTarget() {
var deviceId = null;
if (this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId]) {
deviceId = this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId].entityId;
}
if (!angular.equals(deviceId, this.targetDeviceId)) {
this.targetDeviceId = deviceId;
if (this.targetDeviceId) {
this.rpcEnabled = true;
} else {
this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false;
}
this.callbacks.rpcStateChanged(this);
checkRpcTarget(aliasIds) {
if (aliasIds.indexOf(this.targetDeviceAliasId) > -1) {
return true;
} else {
return false;
}
}
checkSubscriptions() {
checkSubscriptions(aliasIds) {
var subscriptionsChanged = false;
for (var i = 0; i < this.datasourceListeners.length; i++) {
var listener = this.datasourceListeners[i];
var entityId = null;
var entityType = null;
var aliasName = null;
if (listener.datasource.type === this.ctx.types.datasourceType.entity) {
if (listener.datasource.entityAliasId &&
this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId]) {
entityId = this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId].entityId;
entityType = this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId].entityType;
aliasName = this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId].alias;
}
if (!angular.equals(entityId, listener.entityId) ||
!angular.equals(entityType, listener.entityType) ||
!angular.equals(aliasName, listener.datasource.name)) {
if (listener.datasource.entityAliasId) {
if (aliasIds.indexOf(listener.datasource.entityAliasId) > -1) {
subscriptionsChanged = true;
break;
}
}
}
if (subscriptionsChanged) {
this.unsubscribe();
this.subscribe();
}
return subscriptionsChanged;
}
destroy() {
@ -617,29 +637,6 @@ export default class Subscription {
}
const varsRegex = /\$\{([^\}]*)\}/g;
function updateDataKeyLabel(dataKey, dsName, entityName, aliasName) {
var pattern = dataKey.pattern;
var label = dataKey.pattern;
var match = varsRegex.exec(pattern);
while (match !== null) {
var variable = match[0];
var variableName = match[1];
if (variableName === 'dsName') {
label = label.split(variable).join(dsName);
} else if (variableName === 'entityName') {
label = label.split(variable).join(entityName);
} else if (variableName === 'deviceName') {
label = label.split(variable).join(entityName);
} else if (variableName === 'aliasName') {
label = label.split(variable).join(aliasName);
}
match = varsRegex.exec(pattern);
}
dataKey.label = label;
}
function calculateMin(data) {
if (data.length > 0) {
var result = Number(data[0][1]);

148
ui/src/app/common/dashboard-utils.service.js

@ -23,8 +23,10 @@ function DashboardUtils(types, utils, timeService) {
var service = {
validateAndUpdateDashboard: validateAndUpdateDashboard,
validateAndUpdateWidget: validateAndUpdateWidget,
getRootStateId: getRootStateId,
createSingleWidgetDashboard: createSingleWidgetDashboard,
createSingleEntityFilter: createSingleEntityFilter,
getStateLayoutsData: getStateLayoutsData,
createDefaultState: createDefaultState,
createDefaultLayoutData: createDefaultLayoutData,
@ -39,40 +41,104 @@ function DashboardUtils(types, utils, timeService) {
return service;
function validateAndUpdateEntityAliases(configuration) {
function validateAndUpdateEntityAliases(configuration, datasourcesByAliasId, targetDevicesByAliasId) {
var aliasId, entityAlias;
if (angular.isUndefined(configuration.entityAliases)) {
configuration.entityAliases = {};
if (configuration.deviceAliases) {
var deviceAliases = configuration.deviceAliases;
for (var aliasId in deviceAliases) {
for (aliasId in deviceAliases) {
var deviceAlias = deviceAliases[aliasId];
var alias = deviceAlias.alias;
var entityFilter = {
useFilter: false,
entityNameFilter: '',
entityList: []
}
if (deviceAlias.deviceFilter) {
entityFilter.useFilter = deviceAlias.deviceFilter.useFilter;
entityFilter.entityNameFilter = deviceAlias.deviceFilter.deviceNameFilter;
entityFilter.entityList = deviceAlias.deviceFilter.deviceList;
} else if (deviceAlias.deviceId) {
entityFilter.entityList = [deviceAlias.deviceId];
}
var entityAlias = {
id: aliasId,
alias: alias,
entityType: types.entityType.device,
entityFilter: entityFilter
};
configuration.entityAliases[aliasId] = entityAlias;
entityAlias = validateAndUpdateDeviceAlias(aliasId, deviceAlias, datasourcesByAliasId, targetDevicesByAliasId);
configuration.entityAliases[entityAlias.id] = entityAlias;
}
delete configuration.deviceAliases;
}
} else {
var entityAliases = configuration.entityAliases;
for (aliasId in entityAliases) {
entityAlias = entityAliases[aliasId];
entityAlias = validateAndUpdateEntityAlias(aliasId, entityAlias, datasourcesByAliasId, targetDevicesByAliasId);
if (aliasId != entityAlias.id) {
delete entityAliases[aliasId];
}
entityAliases[entityAlias.id] = entityAlias;
}
}
return configuration;
}
function validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId) {
if (!aliasId || !angular.isString(aliasId) || aliasId.length != 36) {
var newAliasId = utils.guid();
var aliasDatasources = datasourcesByAliasId[aliasId];
if (aliasDatasources) {
aliasDatasources.forEach(
function(datasource) {
datasource.entityAliasId = newAliasId;
}
);
}
var targetDeviceAliasIdsList = targetDevicesByAliasId[aliasId];
if (targetDeviceAliasIdsList) {
targetDeviceAliasIdsList.forEach(
function(targetDeviceAliasIds) {
targetDeviceAliasIds[0] = newAliasId;
}
);
}
return newAliasId;
} else {
return aliasId;
}
}
function validateAndUpdateDeviceAlias(aliasId, deviceAlias, datasourcesByAliasId, targetDevicesByAliasId) {
aliasId = validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId);
var alias = deviceAlias.alias;
var entityAlias = {
id: aliasId,
alias: alias,
filter: {
type: null,
entityType: types.entityType.device,
resolveMultiple: false
},
}
if (deviceAlias.deviceFilter) {
entityAlias.filter.type =
deviceAlias.deviceFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value;
if (entityAlias.filter.type == types.aliasFilterType.entityList.value) {
entityAlias.filter.entityList = deviceAlias.deviceFilter.deviceList;
} else {
entityAlias.filter.entityNameFilter = deviceAlias.deviceFilter.deviceNameFilter;
}
} else {
entityAlias.filter.type = types.aliasFilterType.entityList.value;
entityAlias.filter.entityList = [deviceAlias.deviceId];
}
return entityAlias;
}
function validateAndUpdateEntityAlias(aliasId, entityAlias, datasourcesByAliasId, targetDevicesByAliasId) {
entityAlias.id = validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId);
if (!entityAlias.filter) {
entityAlias.filter = {
type: entityAlias.entityFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value,
entityType: entityAlias.entityType,
resolveMultiple: false
}
if (entityAlias.filter.type == types.aliasFilterType.entityList.value) {
entityAlias.filter.entityList = entityAlias.entityFilter.entityList;
} else {
entityAlias.filter.entityNameFilter = entityAlias.entityFilter.entityNameFilter;
}
delete entityAlias.entityType;
delete entityAlias.entityFilter;
}
return entityAlias;
}
function validateAndUpdateWidget(widget) {
if (!widget.config) {
widget.config = {};
@ -166,7 +232,34 @@ function DashboardUtils(types, utils, timeService) {
states[firstStateId].root = true;
}
}
dashboard.configuration = validateAndUpdateEntityAliases(dashboard.configuration);
var datasourcesByAliasId = {};
var targetDevicesByAliasId = {};
for (var widgetId in dashboard.configuration.widgets) {
widget = dashboard.configuration.widgets[widgetId];
widget.config.datasources.forEach(function (datasource) {
if (datasource.entityAliasId) {
var aliasId = datasource.entityAliasId;
var aliasDatasources = datasourcesByAliasId[aliasId];
if (!aliasDatasources) {
aliasDatasources = [];
datasourcesByAliasId[aliasId] = aliasDatasources;
}
aliasDatasources.push(datasource);
}
});
if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length) {
var aliasId = widget.config.targetDeviceAliasIds[0];
var targetDeviceAliasIdsList = targetDevicesByAliasId[aliasId];
if (!targetDeviceAliasIdsList) {
targetDeviceAliasIdsList = [];
targetDevicesByAliasId[aliasId] = targetDeviceAliasIdsList;
}
targetDeviceAliasIdsList.push(widget.config.targetDeviceAliasIds);
}
}
dashboard.configuration = validateAndUpdateEntityAliases(dashboard.configuration, datasourcesByAliasId, targetDevicesByAliasId);
if (angular.isUndefined(dashboard.configuration.timewindow)) {
dashboard.configuration.timewindow = timeService.defaultTimewindow();
@ -243,6 +336,15 @@ function DashboardUtils(types, utils, timeService) {
return dashboard;
}
function createSingleEntityFilter(entityType, entityId) {
return {
type: types.aliasFilterType.entityList.value,
entityList: [entityId],
entityType: entityType,
resolveMultiple: false
};
}
function getStateLayoutsData(dashboard, targetState) {
var dashboardConfiguration = dashboard.configuration;
var states = dashboardConfiguration.states;

90
ui/src/app/common/types.constant.js

@ -65,6 +65,40 @@ export default angular.module('thingsboard.types', [])
clearedUnack: "CLEARED_UNACK",
clearedAck: "CLEARED_ACK"
},
aliasFilterType: {
entityList: {
value: 'entityList',
name: 'alias.filter-type-entity-list'
},
entityName: {
value: 'entityName',
name: 'alias.filter-type-entity-name'
},
stateEntity: {
value: 'stateEntity',
name: 'alias.filter-type-state-entity'
},
assetType: {
value: 'assetType',
name: 'alias.filter-type-asset-type'
},
deviceType: {
value: 'deviceType',
name: 'alias.filter-type-device-type'
},
relationsQuery: {
value: 'relationsQuery',
name: 'alias.filter-type-relations-query'
},
assetSearchQuery: {
value: 'assetSearchQuery',
name: 'alias.filter-type-asset-search-query'
},
deviceSearchQuery: {
value: 'deviceSearchQuery',
name: 'alias.filter-type-device-search-query'
}
},
position: {
top: {
value: "top",
@ -109,6 +143,62 @@ export default angular.module('thingsboard.types', [])
dashboard: "DASHBOARD",
alarm: "ALARM"
},
entityTypeTranslations: {
"DEVICE": {
type: 'entity.type-device',
typePlural: 'entity.type-devices',
list: 'entity.list-of-devices',
nameStartsWith: 'entity.device-name-starts-with'
},
"ASSET": {
type: 'entity.type-asset',
typePlural: 'entity.type-assets',
list: 'entity.list-of-assets',
nameStartsWith: 'entity.asset-name-starts-with'
},
"RULE": {
type: 'entity.type-rule',
typePlural: 'entity.type-rules',
list: 'entity.list-of-rules',
nameStartsWith: 'entity.rule-name-starts-with'
},
"PLUGIN": {
type: 'entity.type-plugin',
typePlural: 'entity.type-plugins',
list: 'entity.list-of-plugins',
nameStartsWith: 'entity.plugin-name-starts-with'
},
"TENANT": {
type: 'entity.type-tenant',
typePlural: 'entity.type-tenants',
list: 'entity.list-of-tenants',
nameStartsWith: 'entity.tenant-name-starts-with'
},
"CUSTOMER": {
type: 'entity.type-customer',
typePlural: 'entity.type-customers',
list: 'entity.list-of-customers',
nameStartsWith: 'entity.customer-name-starts-with'
},
"USER": {
type: 'entity.type-user',
typePlural: 'entity.type-users',
list: 'entity.list-of-users',
nameStartsWith: 'entity.user-name-starts-with'
},
"DASHBOARD": {
type: 'entity.type-dashboard',
typePlural: 'entity.type-dashboards',
list: 'entity.list-of-dashboards',
nameStartsWith: 'entity.dashboard-name-starts-with'
},
"ALARM": {
type: 'entity.type-alarm',
typePlural: 'entity.type-alarms',
list: 'entity.list-of-alarms',
nameStartsWith: 'entity.alarm-name-starts-with'
}
},
entitySearchDirection: {
from: "FROM",
to: "TO"

37
ui/src/app/common/utils.service.js

@ -106,10 +106,10 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
isDescriptorSchemaNotEmpty: isDescriptorSchemaNotEmpty,
filterSearchTextEntities: filterSearchTextEntities,
guid: guid,
cleanCopy: cleanCopy,
isLocalUrl: isLocalUrl,
validateDatasources: validateDatasources,
createKey: createKey,
entityTypeName: entityTypeName
createKey: createKey
}
return service;
@ -291,6 +291,16 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
s4() + '-' + s4() + s4() + s4();
}
function cleanCopy(object) {
var copy = angular.copy(object);
for (var prop in copy) {
if (prop && prop.startsWith('$$')) {
delete copy[prop];
}
}
return copy;
}
function genNextColor(datasources) {
var index = 0;
if (datasources) {
@ -347,27 +357,4 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
return dataKey;
}
function entityTypeName (type) {
switch (type) {
case types.entityType.device:
return 'entity.type-device';
case types.entityType.asset:
return 'entity.type-asset';
case types.entityType.rule:
return 'entity.type-rule';
case types.entityType.plugin:
return 'entity.type-plugin';
case types.entityType.tenant:
return 'entity.type-tenant';
case types.entityType.customer:
return 'entity.type-customer';
case types.entityType.user:
return 'entity.type-user';
case types.entityType.dashboard:
return 'entity.type-dashboard';
case types.entityType.alarm:
return 'entity.type-alarm';
}
}
}

37
ui/src/app/components/dashboard.directive.js

@ -52,7 +52,7 @@ function Dashboard() {
bindToController: {
widgets: '=',
widgetLayouts: '=?',
aliasesInfo: '=',
aliasController: '=',
stateController: '=',
dashboardTimewindow: '=?',
columns: '=',
@ -85,7 +85,7 @@ function Dashboard() {
}
/*@ngInject*/
function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, timeService, types, utils) {
function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $mdUtil, timeService, types, utils) {
var highlightedMode = false;
var highlightedWidget = null;
@ -329,10 +329,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
$scope.$broadcast('toggleDashboardEditMode', vm.isEdit);
});
$scope.$watch('vm.aliasesInfo.entityAliases', function () {
$scope.$broadcast('entityAliasListChanged', vm.aliasesInfo);
}, true);
$scope.$on('gridster-resized', function (event, sizes, theGridster) {
if (checkIsLocalGridsterElement(theGridster)) {
vm.gridster = theGridster;
@ -796,7 +792,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
}
function dashboardLoaded() {
$timeout(function () {
$mdUtil.nextTick(function () {
if (vm.dashboardTimewindowWatch) {
vm.dashboardTimewindowWatch();
vm.dashboardTimewindowWatch = null;
@ -806,14 +802,27 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
}, true);
adoptMaxRows();
vm.dashboardLoading = false;
$timeout(function () {
var gridsterScope = gridsterElement.scope();
vm.gridster = gridsterScope.gridster;
if (vm.onInit) {
vm.onInit({dashboard: vm});
if ($scope.gridsterScopeWatcher) {
$scope.gridsterScopeWatcher();
}
$scope.gridsterScopeWatcher = $scope.$watch(
function() {
var hasScope = gridsterElement.scope() ? true : false;
return hasScope;
},
function(hasScope) {
if (hasScope) {
$scope.gridsterScopeWatcher();
$scope.gridsterScopeWatcher = null;
var gridsterScope = gridsterElement.scope();
vm.gridster = gridsterScope.gridster;
if (vm.onInit) {
vm.onInit({dashboard: vm});
}
}
}
}, 0, false);
}, 0, false);
);
});
}
function loading() {

2
ui/src/app/components/dashboard.tpl.html

@ -89,7 +89,7 @@
<div flex tb-widget
locals="{ visibleRect: vm.visibleRect,
widget: widget,
aliasesInfo: vm.aliasesInfo,
aliasController: vm.aliasController,
stateController: vm.stateController,
isEdit: vm.isEdit,
stDiff: vm.stDiff,

32
ui/src/app/components/datakey-config-dialog.controller.js

@ -20,14 +20,14 @@ export default angular.module('thingsboard.dialogs.datakeyConfigDialog', [things
.name;
/*@ngInject*/
function DatakeyConfigDialogController($scope, $mdDialog, entityService, dataKey, dataKeySettingsSchema, entityAlias, entityAliases) {
function DatakeyConfigDialogController($scope, $mdDialog, $q, entityService, dataKey, dataKeySettingsSchema, entityAlias, aliasController) {
var vm = this;
vm.dataKey = dataKey;
vm.dataKeySettingsSchema = dataKeySettingsSchema;
vm.entityAlias = entityAlias;
vm.entityAliases = entityAliases;
vm.aliasController = aliasController;
vm.hide = function () {
$mdDialog.hide();
@ -38,12 +38,28 @@ function DatakeyConfigDialogController($scope, $mdDialog, entityService, dataKey
};
vm.fetchEntityKeys = function (entityAliasId, query, type) {
var alias = vm.entityAliases[entityAliasId];
if (alias) {
return entityService.getEntityKeys(alias.entityType, alias.entityId, query, type);
} else {
return [];
}
var deferred = $q.defer();
vm.aliasController.getAliasInfo(entityAliasId).then(
function success(aliasInfo) {
var entity = aliasInfo.currentEntity;
if (entity) {
entityService.getEntityKeys(entity.entityType, entity.id, query, type).then(
function success(keys) {
deferred.resolve(keys);
},
function fail() {
deferred.resolve([]);
}
);
} else {
deferred.resolve([]);
}
},
function fail() {
deferred.resolve([]);
}
);
return deferred.promise;
};
vm.save = function () {

11
ui/src/app/components/datasource-entity.directive.js

@ -103,10 +103,9 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
var entityAliasId = ngModelCtrl.$viewValue.entityAliasId;
if (scope.entityAliases[entityAliasId]) {
scope.entityAlias = {id: entityAliasId, alias: scope.entityAliases[entityAliasId].alias,
entityType: scope.entityAliases[entityAliasId].entityType,
entityId: scope.entityAliases[entityAliasId].entityId};
var entityAliases = scope.aliasController.getEntityAliases();
if (entityAliases[entityAliasId]) {
scope.entityAlias = entityAliases[entityAliasId];
} else {
scope.entityAlias = null;
}
@ -182,7 +181,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
dataKey: angular.copy(dataKey),
dataKeySettingsSchema: scope.datakeySettingsSchema,
entityAlias: scope.entityAlias,
entityAliases: scope.entityAliases
aliasController: scope.aliasController
},
parent: angular.element($document[0].body),
fullscreen: true,
@ -236,7 +235,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
require: "^ngModel",
scope: {
widgetType: '=',
entityAliases: '=',
aliasController: '=',
datakeySettingsSchema: '=',
generateDataKey: '&',
fetchEntityKeys: '&',

2
ui/src/app/components/datasource-entity.tpl.html

@ -18,7 +18,7 @@
<section flex layout='column' layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
<tb-entity-alias-select
tb-required="true"
entity-aliases="entityAliases"
alias-controller="aliasController"
ng-model="entityAlias"
on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})">
</tb-entity-alias-select>

8
ui/src/app/components/datasource-func.directive.js

@ -71,6 +71,13 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
}
}, true);
scope.$watch('datasourceName', function () {
if (ngModelCtrl.$viewValue) {
ngModelCtrl.$viewValue.name = scope.datasourceName;
scope.updateValidity();
}
});
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
var funcDataKeys = [];
@ -78,6 +85,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
funcDataKeys = funcDataKeys.concat(ngModelCtrl.$viewValue.dataKeys);
}
scope.funcDataKeys = funcDataKeys;
scope.datasourceName = ngModelCtrl.$viewValue.name;
}
};

38
ui/src/app/components/datasource-func.scss

@ -15,23 +15,29 @@
*/
@import '../../scss/constants';
.tb-func-datakey-autocomplete {
.tb-not-found {
display: block;
line-height: 1.5;
height: 48px;
.tb-no-entries {
line-height: 48px;
}
.tb-datasource-func {
@media (min-width: $layout-breakpoint-gt-sm) {
padding-left: 8px;
}
li {
height: auto !important;
white-space: normal !important;
md-input-container.tb-datasource-name {
.md-errors-spacer {
display: none;
}
}
}
tb-datasource-func {
@media (min-width: $layout-breakpoint-gt-sm) {
padding-left: 8px;
.tb-func-datakey-autocomplete {
.tb-not-found {
display: block;
line-height: 1.5;
height: 48px;
.tb-no-entries {
line-height: 48px;
}
}
li {
height: auto !important;
white-space: normal !important;
}
}
}
}

111
ui/src/app/components/datasource-func.tpl.html

@ -15,59 +15,68 @@
limitations under the License.
-->
<section flex layout='column' style="padding-left: 4px;">
<md-chips flex
id="function_datakey_chips"
ng-required="true"
ng-model="funcDataKeys" md-autocomplete-snap
md-transform-chip="transformDataKeyChip($chip)"
md-require-match="false">
<md-autocomplete
md-no-cache="false"
id="dataKey"
md-selected-item="selectedDataKey"
md-search-text="dataKeySearchText"
md-items="item in dataKeysSearch(dataKeySearchText)"
md-item-text="item.name"
md-min-length="0"
placeholder="{{ 'datakey.function-types' | translate }}"
md-menu-class="tb-func-datakey-autocomplete">
<span md-highlight-text="dataKeySearchText" md-highlight-flags="^i">{{item}}</span>
<md-not-found>
<div class="tb-not-found">
<div class="tb-no-entries" ng-if="!textIsNotEmpty(dataKeySearchText)">
<span translate>device.no-keys-found</span>
<section class="tb-datasource-func" flex layout='column'
layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
<md-input-container class="tb-datasource-name" md-no-float style="min-width: 200px;">
<input name="datasourceName"
placeholder="{{ 'datasource.name' | translate }}"
ng-model="datasourceName"
aria-label="{{ 'datasource.name' | translate }}">
</md-input-container>
<section flex layout='column' style="padding-left: 4px;">
<md-chips flex
id="function_datakey_chips"
ng-required="true"
ng-model="funcDataKeys" md-autocomplete-snap
md-transform-chip="transformDataKeyChip($chip)"
md-require-match="false">
<md-autocomplete
md-no-cache="false"
id="dataKey"
md-selected-item="selectedDataKey"
md-search-text="dataKeySearchText"
md-items="item in dataKeysSearch(dataKeySearchText)"
md-item-text="item.name"
md-min-length="0"
placeholder="{{ 'datakey.function-types' | translate }}"
md-menu-class="tb-func-datakey-autocomplete">
<span md-highlight-text="dataKeySearchText" md-highlight-flags="^i">{{item}}</span>
<md-not-found>
<div class="tb-not-found">
<div class="tb-no-entries" ng-if="!textIsNotEmpty(dataKeySearchText)">
<span translate>device.no-keys-found</span>
</div>
<div ng-if="textIsNotEmpty(dataKeySearchText)">
<span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>device.no-key-matching</span>
<span>
<a translate ng-click="createKey($event, '#function_datakey_chips')">device.create-new-key</a>
</span>
</div>
</div>
<div ng-if="textIsNotEmpty(dataKeySearchText)">
<span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>device.no-key-matching</span>
<span>
<a translate ng-click="createKey($event, '#function_datakey_chips')">device.create-new-key</a>
</span>
</div>
</div>
</md-not-found>
</md-autocomplete>
<md-chip-template>
<div layout="row" layout-align="start center" class="tb-attribute-chip">
<div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
<div class="tb-color-result" ng-style="{background: $chip.color}"></div>
</div>
<div layout="row" flex>
<div class="tb-chip-label">
{{$chip.label}}
</md-not-found>
</md-autocomplete>
<md-chip-template>
<div layout="row" layout-align="start center" class="tb-attribute-chip">
<div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
<div class="tb-color-result" ng-style="{background: $chip.color}"></div>
</div>
<div class="tb-chip-separator">: </div>
<div class="tb-chip-label">
<strong>{{$chip.name}}</strong>
<div layout="row" flex>
<div class="tb-chip-label">
{{$chip.label}}
</div>
<div class="tb-chip-separator">: </div>
<div class="tb-chip-label">
<strong>{{$chip.name}}</strong>
</div>
</div>
<md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
<md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
</md-button>
</div>
<md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
<md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
</md-button>
</div>
</md-chip-template>
</md-chips>
<div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
<div translate ng-message="funcTypes" class="tb-error-message">datakey.function-types-required</div>
</div>
</md-chip-template>
</md-chips>
<div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
<div translate ng-message="funcTypes" class="tb-error-message">datakey.function-types-required</div>
</div>
</section>
</section>

2
ui/src/app/components/datasource.directive.js

@ -76,7 +76,7 @@ function Datasource($compile, $templateCache, types) {
restrict: "E",
require: "^ngModel",
scope: {
entityAliases: '=',
aliasController: '=',
widgetType: '=',
functionsOnly: '=',
datakeySettingsSchema: '=',

2
ui/src/app/components/datasource.tpl.html

@ -37,7 +37,7 @@
ng-switch-when="entity"
ng-required="model.type === types.datasourceType.entity"
widget-type="widgetType"
entity-aliases="entityAliases"
alias-controller="aliasController"
generate-data-key="generateDataKey({chip: chip, type: type})"
fetch-entity-keys="fetchEntityKeys({entityAliasId: entityAliasId, query: query, type: type})"
on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})">

17
ui/src/app/components/entity-alias-select.directive.js

@ -31,7 +31,7 @@ export default angular.module('thingsboard.directives.entityAliasSelect', [])
.name;
/*@ngInject*/
function EntityAliasSelect($compile, $templateCache, $mdConstant) {
function EntityAliasSelect($compile, $templateCache, $mdConstant, entityService) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(entityAliasSelectTemplate);
@ -49,19 +49,18 @@ function EntityAliasSelect($compile, $templateCache, $mdConstant) {
ngModelCtrl.$setValidity('entityAlias', valid);
};
scope.$watch('entityAliases', function () {
scope.$watch('aliasController', function () {
scope.entityAliasList = [];
for (var aliasId in scope.entityAliases) {
var entityAliases = scope.aliasController.getEntityAliases();
for (var aliasId in entityAliases) {
if (scope.allowedEntityTypes) {
if (scope.allowedEntityTypes.indexOf(scope.entityAliases[aliasId].entityType) === -1) {
if (!entityService.filterAliasByEntityTypes(entityAliases[aliasId], scope.allowedEntityTypes)) {
continue;
}
}
var entityAlias = {id: aliasId, alias: scope.entityAliases[aliasId].alias,
entityType: scope.entityAliases[aliasId].entityType, entityId: scope.entityAliases[aliasId].entityId};
scope.entityAliasList.push(entityAlias);
scope.entityAliasList.push(entityAliases[aliasId]);
}
}, true);
});
scope.$watch('entityAlias', function () {
scope.updateView();
@ -141,7 +140,7 @@ function EntityAliasSelect($compile, $templateCache, $mdConstant) {
link: linker,
scope: {
tbRequired: '=?',
entityAliases: '=',
aliasController: '=',
allowedEntityTypes: '=?',
onCreateEntityAlias: '&'
}

12
ui/src/app/components/widget-config.directive.js

@ -128,13 +128,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
} else if (scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) {
if (config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0) {
var aliasId = config.targetDeviceAliasIds[0];
if (scope.entityAliases[aliasId]) {
scope.targetDeviceAlias.value = {
id: aliasId,
alias: scope.entityAliases[aliasId].alias,
entityType: scope.entityAliases[aliasId].entityType,
entityId: scope.entityAliases[aliasId].entityId
};
var entityAliases = scope.aliasController.getEntityAliases();
if (entityAliases[aliasId]) {
scope.targetDeviceAlias.value = entityAliases[aliasId];
} else {
scope.targetDeviceAlias.value = null;
}
@ -395,7 +391,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
widgetType: '=',
widgetSettingsSchema: '=',
datakeySettingsSchema: '=',
entityAliases: '=',
aliasController: '=',
functionsOnly: '=',
fetchEntityKeys: '&',
onCreateEntityAlias: '&',

4
ui/src/app/components/widget-config.tpl.html

@ -60,7 +60,7 @@
style="padding: 0 0 0 10px; margin: 5px;">
<tb-datasource flex ng-model="datasource.value"
widget-type="widgetType"
entity-aliases="entityAliases"
alias-controller="aliasController"
functions-only="functionsOnly"
datakey-settings-schema="datakeySettingsSchema"
generate-data-key="generateDataKey(chip,type)"
@ -104,7 +104,7 @@
<v-pane-content style="padding: 0 5px;">
<tb-entity-alias-select flex
tb-required="widgetType === types.widgetType.rpc.value && !widgetEditMode"
entity-aliases="entityAliases"
alias-controller="aliasController"
allowed-entity-types="[types.entityType.device]"
ng-model="targetDeviceAlias.value"
on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias, allowedEntityTypes: allowedEntityTypes})">

320
ui/src/app/components/widget.controller.js

@ -20,9 +20,9 @@ import Subscription from '../api/subscription';
/* eslint-disable angular/angularelement */
/*@ngInject*/
export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, tbRaf, types, utils, timeService,
export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, $compile, tbRaf, types, utils, timeService,
datasourceService, entityService, deviceService, visibleRect, isEdit, stDiff, dashboardTimewindow,
dashboardTimewindowApi, widget, aliasesInfo, stateController, widgetType) {
dashboardTimewindowApi, widget, aliasController, stateController, widgetInfo, widgetType) {
var vm = this;
@ -37,23 +37,16 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
$scope.executingRpcRequest = false;
var gridsterItemInited = false;
var subscriptionInited = false;
var widgetSizeDetected = false;
var cafs = {};
/*
* data = array of datasourceData
* datasourceData = {
* tbDatasource,
* dataKey, { name, config }
* data = array of [time, value]
* }
*/
var widgetContext = {
inited: false,
$scope: $scope,
$container: $('#container', $element),
$containerParent: $($element),
$container: null,
$containerParent: null,
width: 0,
height: 0,
isEdit: isEdit,
@ -80,30 +73,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
createSubscription: function(options, subscribe) {
return createSubscription(options, subscribe);
},
// type: "timeseries" or "latest" or "rpc"
/* subscriptionInfo = [
{
entityType: ""
entityId: ""
entityName: ""
timeseries: [{ name: "", label: "" }, ..]
attributes: [{ name: "", label: "" }, ..]
}
..
]*/
// options = {
// timeWindowConfig,
// useDashboardTimewindow,
// legendConfig,
// decimals,
// units,
// callbacks [ onDataUpdated(subscription, apply) ]
// }
//
createSubscriptionFromInfo: function (type, subscriptionsInfo, options, useDefaultComponents, subscribe) {
return createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe);
},
@ -149,7 +118,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
dashboardTimewindowApi: dashboardTimewindowApi,
types: types,
stDiff: stDiff,
aliasesInfo: aliasesInfo
aliasController: aliasController
};
var widgetTypeInstance;
@ -203,23 +172,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
vm.gridsterItemInitialized = gridsterItemInitialized;
initialize();
/*
options = {
type,
targetDeviceAliasIds, // RPC
targetDeviceIds, // RPC
datasources,
timeWindowConfig,
useDashboardTimewindow,
legendConfig,
decimals,
units,
callbacks
}
*/
initialize().then(
function(){
onInit();
}
);
function createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe) {
var deferred = $q.defer();
@ -233,28 +190,42 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
}
}
entityService.createDatasoucesFromSubscriptionsInfo(subscriptionsInfo).then(
entityService.createDatasourcesFromSubscriptionsInfo(subscriptionsInfo).then(
function (datasources) {
options.datasources = datasources;
var subscription = createSubscription(options, subscribe);
if (useDefaultComponents) {
defaultSubscriptionOptions(subscription, options);
}
deferred.resolve(subscription);
createSubscription(options, subscribe).then(
function success(subscription) {
if (useDefaultComponents) {
defaultSubscriptionOptions(subscription, options);
}
deferred.resolve(subscription);
},
function fail() {
deferred.reject();
}
);
}
);
return deferred.promise;
}
function createSubscription(options, subscribe) {
var deferred = $q.defer();
options.dashboardTimewindow = dashboardTimewindow;
var subscription =
new Subscription(subscriptionContext, options);
widgetContext.subscriptions[subscription.id] = subscription;
if (subscribe) {
subscription.subscribe();
}
return subscription;
new Subscription(subscriptionContext, options).then(
function success(subscription) {
widgetContext.subscriptions[subscription.id] = subscription;
if (subscribe) {
subscription.subscribe();
}
deferred.resolve(subscription);
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function defaultComponentsOptions(options) {
@ -310,8 +281,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
}
function createDefaultSubscription() {
var subscription;
var options;
var deferred = $q.defer();
if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) {
options = {
type: widget.type,
@ -319,16 +290,23 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
};
defaultComponentsOptions(options);
subscription = createSubscription(options);
defaultSubscriptionOptions(subscription, options);
createSubscription(options).then(
function success(subscription) {
defaultSubscriptionOptions(subscription, options);
// backward compatibility
// backward compatibility
widgetContext.datasources = subscription.datasources;
widgetContext.data = subscription.data;
widgetContext.hiddenData = subscription.hiddenData;
widgetContext.timeWindow = subscription.timeWindow;
widgetContext.datasources = subscription.datasources;
widgetContext.data = subscription.data;
widgetContext.hiddenData = subscription.hiddenData;
widgetContext.timeWindow = subscription.timeWindow;
widgetContext.defaultSubscription = subscription;
deferred.resolve();
},
function fail() {
deferred.reject();
}
);
} else if (widget.type === types.widgetType.rpc.value) {
$scope.loadingData = false;
@ -356,30 +334,126 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
$scope.rpcRejection = null;
}
}
subscription = createSubscription(options);
createSubscription(options).then(
function success(subscription) {
widgetContext.defaultSubscription = subscription;
deferred.resolve();
},
function fail() {
deferred.reject();
}
);
} else if (widget.type === types.widgetType.static.value) {
$scope.loadingData = false;
deferred.resolve();
} else {
deferred.resolve();
}
if (subscription) {
widgetContext.defaultSubscription = subscription;
}
return deferred.promise;
}
function configureWidgetElement() {
function initialize() {
$scope.displayLegend = angular.isDefined(widget.config.showLegend) ?
widget.config.showLegend : widget.type === types.widgetType.timeseries.value;
if (!vm.useCustomDatasources) {
createDefaultSubscription();
if ($scope.displayLegend) {
$scope.legendConfig = widget.config.legendConfig ||
{
position: types.position.bottom.value,
showMin: false,
showMax: false,
showAvg: widget.type === types.widgetType.timeseries.value,
showTotal: false
};
$scope.legendData = {
keys: [],
data: []
};
}
var html = '<div class="tb-absolute-fill tb-widget-error" ng-if="widgetErrorData">' +
'<span>Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}</span>' +
'</div>' +
'<div class="tb-absolute-fill tb-widget-loading" ng-show="loadingData" layout="column" layout-align="center center">' +
'<md-progress-circular md-mode="indeterminate" ng-disabled="!loadingData" class="md-accent" md-diameter="40"></md-progress-circular>' +
'</div>';
var containerHtml = '<div id="container">' + widgetInfo.templateHtml + '</div>';
if ($scope.displayLegend) {
var layoutType;
if ($scope.legendConfig.position === types.position.top.value ||
$scope.legendConfig.position === types.position.bottom.value) {
layoutType = 'column';
} else {
layoutType = 'row';
}
var legendStyle;
switch($scope.legendConfig.position) {
case types.position.top.value:
legendStyle = 'padding-bottom: 8px;';
break;
case types.position.bottom.value:
legendStyle = 'padding-top: 8px;';
break;
case types.position.left.value:
legendStyle = 'padding-right: 0px;';
break;
case types.position.right.value:
legendStyle = 'padding-left: 0px;';
break;
}
var legendHtml = '<tb-legend style="'+legendStyle+'" legend-config="legendConfig" legend-data="legendData"></tb-legend>';
containerHtml = '<div flex id="widget-container">' + containerHtml + '</div>';
html += '<div class="tb-absolute-fill" layout="'+layoutType+'">';
if ($scope.legendConfig.position === types.position.top.value ||
$scope.legendConfig.position === types.position.left.value) {
html += legendHtml;
html += containerHtml;
} else {
html += containerHtml;
html += legendHtml;
}
html += '</div>';
} else {
$scope.loadingData = false;
html += containerHtml;
}
//TODO:
/*if (progressElement) {
progressScope.$destroy();
progressScope = null;
progressElement.remove();
progressElement = null;
}*/
$element.html(html);
var containerElement = $scope.displayLegend ? angular.element($element[0].querySelector('#widget-container')) : $element;
widgetContext.$container = $('#container', containerElement);
widgetContext.$containerParent = $(containerElement);
$compile($element.contents())($scope);
addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef
}
function destroyWidgetElement() {
removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef
$element.html('');
widgetContext.$container = null;
widgetContext.$containerParent = null;
}
function initialize() {
$scope.$on('toggleDashboardEditMode', function (event, isEdit) {
onEditModeChanged(isEdit);
});
addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef
$scope.$watch(function () {
return widget.row + ',' + widget.col + ',' + widget.config.mobileOrder;
}, function () {
@ -398,18 +472,60 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
onMobileModeChanged(newIsMobile);
});
$scope.$on('entityAliasListChanged', function (event, aliasesInfo) {
subscriptionContext.aliasesInfo = aliasesInfo;
$scope.$on('entityAliasesChanged', function (event, aliasIds) {
var subscriptionChanged = false;
for (var id in widgetContext.subscriptions) {
var subscription = widgetContext.subscriptions[id];
subscription.onAliasesChanged();
subscriptionChanged = subscriptionChanged || subscription.onAliasesChanged(aliasIds);
}
if (subscriptionChanged && !vm.useCustomDatasources) {
reInit();
}
});
$scope.$on("$destroy", function () {
removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef
onDestroy();
});
configureWidgetElement();
var deferred = $q.defer();
if (!vm.useCustomDatasources) {
createDefaultSubscription().then(
function success() {
subscriptionInited = true;
deferred.resolve();
},
function fail() {
subscriptionInited = true;
deferred.reject();
}
);
} else {
$scope.loadingData = false;
subscriptionInited = true;
deferred.resolve();
}
return deferred.promise;
}
function reInit() {
onDestroy();
configureWidgetElement();
if (!vm.useCustomDatasources) {
createDefaultSubscription().then(
function success() {
subscriptionInited = true;
onInit();
},
function fail() {
subscriptionInited = true;
onInit();
}
);
} else {
subscriptionInited = true;
onInit();
}
}
function handleWidgetException(e) {
@ -417,8 +533,15 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
$scope.widgetErrorData = utils.processWidgetException(e);
}
function onInit() {
if (!widgetContext.inited) {
function isReady() {
return subscriptionInited && gridsterItemInited && widgetSizeDetected;
}
function onInit(skipSizeCheck) {
if (!skipSizeCheck) {
checkSize();
}
if (!widgetContext.inited && isReady()) {
widgetContext.inited = true;
try {
widgetTypeInstance.onInit();
@ -443,6 +566,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
widgetContext.width = width;
widgetContext.height = height;
sizeChanged = true;
widgetSizeDetected = true;
}
}
return sizeChanged;
@ -462,8 +586,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
handleWidgetException(e);
}
});
} else if (gridsterItemInited) {
onInit();
} else {
onInit(true);
}
}
}
@ -472,9 +596,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
if (item && item.gridster) {
widgetContext.isMobile = item.gridster.isMobile;
gridsterItemInited = true;
if (checkSize()) {
onInit();
}
onInit();
// gridsterItemElement = $(item.$element);
//updateVisibility();
}
@ -544,6 +666,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
var subscription = widgetContext.subscriptions[id];
subscription.destroy();
}
subscriptionInited = false;
widgetContext.subscriptions = [];
if (widgetContext.inited) {
widgetContext.inited = false;
@ -559,6 +682,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
handleWidgetException(e);
}
}
destroyWidgetElement();
}
//TODO: widgets visibility

85
ui/src/app/components/widget.directive.js

@ -28,7 +28,7 @@ export default angular.module('thingsboard.directives.widget', [thingsboardLegen
.name;
/*@ngInject*/
function Widget($controller, $compile, types, widgetService) {
function Widget($controller, widgetService) {
return {
scope: true,
link: function (scope, elem, attrs) {
@ -81,90 +81,9 @@ function Widget($controller, $compile, types, widgetService) {
elem.addClass(widgetNamespace);
var html = '<div class="tb-absolute-fill tb-widget-error" ng-if="widgetErrorData">' +
'<span>Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}</span>' +
'</div>' +
'<div class="tb-absolute-fill tb-widget-loading" ng-show="loadingData" layout="column" layout-align="center center">' +
'<md-progress-circular md-mode="indeterminate" ng-disabled="!loadingData" class="md-accent" md-diameter="40"></md-progress-circular>' +
'</div>';
scope.displayLegend = angular.isDefined(widget.config.showLegend) ?
widget.config.showLegend : widget.type === types.widgetType.timeseries.value;
var containerHtml = '<div id="container">' + widgetInfo.templateHtml + '</div>';
if (scope.displayLegend) {
scope.legendConfig = widget.config.legendConfig ||
{
position: types.position.bottom.value,
showMin: false,
showMax: false,
showAvg: widget.type === types.widgetType.timeseries.value,
showTotal: false
};
scope.legendData = {
keys: [],
data: []
};
var layoutType;
if (scope.legendConfig.position === types.position.top.value ||
scope.legendConfig.position === types.position.bottom.value) {
layoutType = 'column';
} else {
layoutType = 'row';
}
var legendStyle;
switch(scope.legendConfig.position) {
case types.position.top.value:
legendStyle = 'padding-bottom: 8px;';
break;
case types.position.bottom.value:
legendStyle = 'padding-top: 8px;';
break;
case types.position.left.value:
legendStyle = 'padding-right: 0px;';
break;
case types.position.right.value:
legendStyle = 'padding-left: 0px;';
break;
}
var legendHtml = '<tb-legend style="'+legendStyle+'" legend-config="legendConfig" legend-data="legendData"></tb-legend>';
containerHtml = '<div flex id="widget-container">' + containerHtml + '</div>';
html += '<div class="tb-absolute-fill" layout="'+layoutType+'">';
if (scope.legendConfig.position === types.position.top.value ||
scope.legendConfig.position === types.position.left.value) {
html += legendHtml;
html += containerHtml;
} else {
html += containerHtml;
html += legendHtml;
}
html += '</div>';
} else {
html += containerHtml;
}
//TODO:
/*if (progressElement) {
progressScope.$destroy();
progressScope = null;
progressElement.remove();
progressElement = null;
}*/
elem.html(html);
var containerElement = scope.displayLegend ? angular.element(elem[0].querySelector('#widget-container')) : elem;
$compile(elem.contents())(scope);
var widgetType = widgetService.getWidgetTypeFunction(widget.bundleAlias, widget.typeAlias, widget.isSystemType);
angular.extend(locals, {$scope: scope, $element: containerElement, widgetType: widgetType});
angular.extend(locals, {$scope: scope, $element: elem, widgetInfo: widgetInfo, widgetType: widgetType});
widgetController = $controller('WidgetController', locals);

69
ui/src/app/dashboard/add-widget.controller.js

@ -15,17 +15,18 @@
*/
/* eslint-disable import/no-unresolved, import/default */
import entityAliasesTemplate from '../entity/entity-aliases.tpl.html';
import entityAliasDialogTemplate from '../entity/alias/entity-alias-dialog.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function AddWidgetController($scope, widgetService, entityService, $mdDialog, $q, $document, types, dashboard, aliasesInfo, widget, widgetInfo) {
export default function AddWidgetController($scope, widgetService, entityService, $mdDialog, $q, $document, types, dashboard,
aliasController, widget, widgetInfo) {
var vm = this;
vm.dashboard = dashboard;
vm.aliasesInfo = aliasesInfo;
vm.aliasController = aliasController;
vm.widget = widget;
vm.widgetInfo = widgetInfo;
@ -85,7 +86,7 @@ export default function AddWidgetController($scope, widgetService, entityService
}
function cancel () {
$mdDialog.cancel({aliasesInfo: vm.aliasesInfo});
$mdDialog.cancel();
}
function add () {
@ -94,52 +95,58 @@ export default function AddWidgetController($scope, widgetService, entityService
vm.widget.config = vm.widgetConfig.config;
vm.widget.config.mobileOrder = vm.widgetConfig.layout.mobileOrder;
vm.widget.config.mobileHeight = vm.widgetConfig.layout.mobileHeight;
$mdDialog.hide({widget: vm.widget, aliasesInfo: vm.aliasesInfo});
$mdDialog.hide({widget: vm.widget});
}
}
function fetchEntityKeys (entityAliasId, query, type) {
var entityAlias = vm.aliasesInfo.entityAliases[entityAliasId];
if (entityAlias && entityAlias.entityId) {
return entityService.getEntityKeys(entityAlias.entityType, entityAlias.entityId, query, type);
} else {
return $q.when([]);
}
var deferred = $q.defer();
vm.aliasController.getAliasInfo(entityAliasId).then(
function success(aliasInfo) {
var entity = aliasInfo.currentEntity;
if (entity) {
entityService.getEntityKeys(entity.entityType, entity.id, query, type).then(
function success(keys) {
deferred.resolve(keys);
},
function fail() {
deferred.resolve([]);
}
);
} else {
deferred.resolve([]);
}
},
function fail() {
deferred.resolve([]);
}
);
return deferred.promise;
}
function createEntityAlias (event, alias, allowedEntityTypes) {
var deferred = $q.defer();
var singleEntityAlias = {id: null, alias: alias, entityType: types.entityType.device, entityFilter: null};
var singleEntityAlias = {id: null, alias: alias, filter: {}};
$mdDialog.show({
controller: 'EntityAliasesController',
controller: 'EntityAliasDialogController',
controllerAs: 'vm',
templateUrl: entityAliasesTemplate,
templateUrl: entityAliasDialogTemplate,
locals: {
config: {
entityAliases: angular.copy(vm.dashboard.configuration.entityAliases),
widgets: null,
isSingleEntityAlias: true,
singleEntityAlias: singleEntityAlias,
allowedEntityTypes: allowedEntityTypes
}
isAdd: true,
allowedEntityTypes: allowedEntityTypes,
entityAliases: vm.dashboard.configuration.entityAliases,
alias: singleEntityAlias
},
parent: angular.element($document[0].body),
fullscreen: true,
skipHide: true,
targetEvent: event
}).then(function (singleEntityAlias) {
vm.dashboard.configuration.entityAliases[singleEntityAlias.id] =
{ alias: singleEntityAlias.alias, entityType: singleEntityAlias.entityType, entityFilter: singleEntityAlias.entityFilter };
entityService.processEntityAliases(vm.dashboard.configuration.entityAliases).then(
function(resolution) {
if (!resolution.error) {
vm.aliasesInfo = resolution.aliasesInfo;
}
deferred.resolve(singleEntityAlias);
}
);
vm.dashboard.configuration.entityAliases[singleEntityAlias.id] = singleEntityAlias;
vm.aliasController.updateEntityAliases(vm.dashboard.configuration.entityAliases);
deferred.resolve(singleEntityAlias);
}, function () {
deferred.reject();
});

2
ui/src/app/dashboard/add-widget.tpl.html

@ -37,7 +37,7 @@
ng-model="vm.widgetConfig"
widget-settings-schema="vm.settingsSchema"
datakey-settings-schema="vm.dataKeySettingsSchema"
entity-aliases="vm.aliasesInfo.entityAliases"
alias-controller="vm.aliasController"
functions-only="vm.functionsOnly"
fetch-entity-keys="vm.fetchEntityKeys(entityAliasId, query, type)"
on-create-entity-alias="vm.createEntityAlias(event, alias, allowedEntityTypes)"

41
ui/src/app/dashboard/dashboard.controller.js

@ -15,7 +15,7 @@
*/
/* eslint-disable import/no-unresolved, import/default */
import entityAliasesTemplate from '../entity/entity-aliases.tpl.html';
import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html';
import dashboardSettingsTemplate from './dashboard-settings.tpl.html';
import manageDashboardLayoutsTemplate from './layouts/manage-dashboard-layouts.tpl.html';
import manageDashboardStatesTemplate from './states/manage-dashboard-states.tpl.html';
@ -24,8 +24,10 @@ import selectTargetLayoutTemplate from './layouts/select-target-layout.tpl.html'
/* eslint-enable import/no-unresolved, import/default */
import AliasController from '../api/alias-controller';
/*@ngInject*/
export default function DashboardController(types, dashboardUtils, widgetService, userService,
export default function DashboardController(types, utils, dashboardUtils, widgetService, userService,
dashboardService, timeService, entityService, itembuffer, importExport, hotkeys, $window, $rootScope,
$scope, $element, $state, $stateParams, $mdDialog, $mdMedia, $timeout, $document, $q, $translate, $filter) {
@ -342,6 +344,8 @@ export default function DashboardController(types, dashboardUtils, widgetService
vm.dashboardConfiguration = vm.dashboard.configuration;
vm.dashboardCtx.dashboard = vm.dashboard;
vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow;
vm.dashboardCtx.aliasController = new AliasController($scope, $q, $filter, utils,
types, entityService, vm.dashboardCtx.stateController, vm.dashboardConfiguration.entityAliases);
var parentScope = $window.parent.angular.element($window.frameElement).scope();
parentScope.$root.$broadcast('widgetEditModeInited');
parentScope.$root.$apply();
@ -349,7 +353,13 @@ export default function DashboardController(types, dashboardUtils, widgetService
dashboardService.getDashboard($stateParams.dashboardId)
.then(function success(dashboard) {
vm.dashboard = dashboardUtils.validateAndUpdateDashboard(dashboard);
entityService.processEntityAliases(vm.dashboard.configuration.entityAliases)
vm.dashboardConfiguration = vm.dashboard.configuration;
vm.dashboardCtx.dashboard = vm.dashboard;
vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow;
vm.dashboardCtx.aliasController = new AliasController($scope, $q, $filter, utils,
types, entityService, vm.dashboardCtx.stateController, vm.dashboardConfiguration.entityAliases);
/* entityService.processEntityAliases(vm.dashboard.configuration.entityAliases)
.then(
function(resolution) {
if (resolution.error && !isTenantAdmin()) {
@ -362,7 +372,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow;
}
}
);
);*/
}, function fail() {
vm.configurationError = true;
});
@ -373,6 +383,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
var layoutsData = dashboardUtils.getStateLayoutsData(vm.dashboard, state);
if (layoutsData) {
vm.dashboardCtx.state = state;
vm.dashboardCtx.aliasController.dashboardStateChanged();
var layoutVisibilityChanged = false;
for (var l in vm.layouts) {
var layout = vm.layouts[l];
@ -916,7 +927,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
templateUrl: addWidgetTemplate,
locals: {
dashboard: vm.dashboard,
aliasesInfo: vm.dashboardCtx.aliasesInfo,
aliasController: vm.dashboardCtx.aliasController,
widget: newWidget,
widgetInfo: widgetTypeInfo
},
@ -930,10 +941,8 @@ export default function DashboardController(types, dashboardUtils, widgetService
}
}).then(function (result) {
var widget = result.widget;
vm.dashboardCtx.aliasesInfo = result.aliasesInfo;
addWidget(widget);
}, function (rejection) {
vm.dashboardCtx.aliasesInfo = rejection.aliasesInfo;
}, function () {
});
}
}
@ -1025,7 +1034,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
notifyDashboardUpdated();
}
function showAliasesResolutionError(error) {
/* function showAliasesResolutionError(error) {
var alert = $mdDialog.alert()
.parent(angular.element($document[0].body))
.clickOutsideToClose(true)
@ -1037,20 +1046,10 @@ export default function DashboardController(types, dashboardUtils, widgetService
alert._options.fullscreen = true;
$mdDialog.show(alert);
}
}*/
function entityAliasesUpdated() {
var deferred = $q.defer();
entityService.processEntityAliases(vm.dashboard.configuration.entityAliases)
.then(
function(resolution) {
if (resolution.aliasesInfo) {
vm.dashboardCtx.aliasesInfo = resolution.aliasesInfo;
}
deferred.resolve();
}
);
return deferred.promise;
vm.dashboardCtx.aliasController.updateEntityAliases(vm.dashboard.configuration.entityAliases);
}
function notifyDashboardUpdated() {

5
ui/src/app/dashboard/dashboard.tpl.html

@ -57,8 +57,7 @@
</tb-timewindow>
<tb-aliases-entity-select ng-show="!vm.isEdit && vm.displayEntitiesSelect()"
tooltip-direction="bottom"
ng-model="vm.dashboardCtx.aliasesInfo.entityAliases"
entity-aliases-info="vm.dashboardCtx.aliasesInfo.entityAliasesInfo">
alias-controller="vm.dashboardCtx.aliasController">
</tb-aliases-entity-select>
<md-button ng-show="vm.isEdit" aria-label="{{ 'entity.aliases' | translate }}" class="md-icon-button"
ng-click="vm.openEntityAliases($event)">
@ -179,7 +178,7 @@
<form name="vm.widgetForm" ng-if="vm.isEditingWidget">
<tb-edit-widget
dashboard="vm.dashboard"
aliases-info="vm.dashboardCtx.aliasesInfo"
alias-controller="vm.dashboardCtx.aliasController"
widget="vm.editingWidget"
widget-layout="vm.editingWidgetLayout"
the-form="vm.widgetForm">

62
ui/src/app/dashboard/edit-widget.directive.js

@ -15,7 +15,7 @@
*/
/* eslint-disable import/no-unresolved, import/default */
import entityAliasesTemplate from '../entity/entity-aliases.tpl.html';
import entityAliasDialogTemplate from '../entity/alias/entity-alias-dialog.tpl.html';
import editWidgetTemplate from './edit-widget.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
@ -68,47 +68,53 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
});
scope.fetchEntityKeys = function (entityAliasId, query, type) {
var entityAlias = scope.aliasesInfo.entityAliases[entityAliasId];
if (entityAlias && entityAlias.entityId) {
return entityService.getEntityKeys(entityAlias.entityType, entityAlias.entityId, query, type);
} else {
return $q.when([]);
}
var deferred = $q.defer();
scope.aliasController.getAliasInfo(entityAliasId).then(
function success(aliasInfo) {
var entity = aliasInfo.currentEntity;
if (entity) {
entityService.getEntityKeys(entity.entityType, entity.id, query, type).then(
function success(keys) {
deferred.resolve(keys);
},
function fail() {
deferred.resolve([]);
}
);
} else {
deferred.resolve([]);
}
},
function fail() {
deferred.resolve([]);
}
);
return deferred.promise;
};
scope.createEntityAlias = function (event, alias, allowedEntityTypes) {
var deferred = $q.defer();
var singleEntityAlias = {id: null, alias: alias, entityType: types.entityType.device, entityFilter: null};
var singleEntityAlias = {id: null, alias: alias, filter: {}};
$mdDialog.show({
controller: 'EntityAliasesController',
controller: 'EntityAliasDialogController',
controllerAs: 'vm',
templateUrl: entityAliasesTemplate,
templateUrl: entityAliasDialogTemplate,
locals: {
config: {
entityAliases: angular.copy(scope.dashboard.configuration.entityAliases),
widgets: null,
isSingleEntityAlias: true,
singleEntityAlias: singleEntityAlias,
allowedEntityTypes: allowedEntityTypes
}
isAdd: true,
allowedEntityTypes: allowedEntityTypes,
entityAliases: scope.dashboard.configuration.entityAliases,
alias: singleEntityAlias
},
parent: angular.element($document[0].body),
fullscreen: true,
skipHide: true,
targetEvent: event
}).then(function (singleEntityAlias) {
scope.dashboard.configuration.entityAliases[singleEntityAlias.id] =
{ alias: singleEntityAlias.alias, entityType: singleEntityAlias.entityType, entityFilter: singleEntityAlias.entityFilter };
entityService.processEntityAliases(scope.dashboard.configuration.entityAliases).then(
function(resolution) {
if (!resolution.error) {
scope.aliasesInfo = resolution.aliasesInfo;
}
deferred.resolve(singleEntityAlias);
}
);
scope.dashboard.configuration.entityAliases[singleEntityAlias.id] = singleEntityAlias;
scope.aliasController.updateEntityAliases(scope.dashboard.configuration.entityAliases);
deferred.resolve(singleEntityAlias);
}, function () {
deferred.reject();
});
@ -124,7 +130,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
link: linker,
scope: {
dashboard: '=',
aliasesInfo: '=',
aliasController: '=',
widget: '=',
widgetLayout: '=',
theForm: '='

2
ui/src/app/dashboard/edit-widget.tpl.html

@ -21,7 +21,7 @@
is-data-enabled="isDataEnabled"
widget-settings-schema="settingsSchema"
datakey-settings-schema="dataKeySettingsSchema"
entity-aliases="aliasesInfo.entityAliases"
alias-controller="aliasController"
functions-only="functionsOnly"
fetch-entity-keys="fetchEntityKeys(entityAliasId, query, type)"
on-create-entity-alias="createEntityAlias(event, alias, allowedEntityTypes)"

2
ui/src/app/dashboard/layouts/dashboard-layout.tpl.html

@ -45,7 +45,7 @@
widget-layouts="vm.layoutCtx.widgetLayouts"
columns="vm.layoutCtx.gridSettings.columns"
margins="vm.layoutCtx.gridSettings.margins"
aliases-info="vm.dashboardCtx.aliasesInfo"
alias-controller="vm.dashboardCtx.aliasController"
state-controller="vm.dashboardCtx.stateController"
dashboard-timewindow="vm.dashboardCtx.dashboardTimewindow"
is-edit="vm.isEdit"

0
ui/src/app/entity/aliases-entity-select-button.tpl.html → ui/src/app/entity/alias/aliases-entity-select-button.tpl.html

49
ui/src/app/entity/alias/aliases-entity-select-panel.controller.js

@ -0,0 +1,49 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*@ngInject*/
export default function AliasesEntitySelectPanelController(mdPanelRef, $scope, $filter, types, aliasController, onEntityAliasesUpdate) {
var vm = this;
vm._mdPanelRef = mdPanelRef;
vm.aliasController = aliasController;
vm.onEntityAliasesUpdate = onEntityAliasesUpdate;
vm.entityAliases = {};
vm.entityAliasesInfo = {};
vm.currentAliasEntityChanged = currentAliasEntityChanged;
var allEntityAliases = vm.aliasController.getEntityAliases();
for (var aliasId in allEntityAliases) {
var aliasInfo = vm.aliasController.getInstantAliasInfo(aliasId);
if (aliasInfo && !aliasInfo.resolveMultiple && aliasInfo.currentEntity) {
vm.entityAliasesInfo[aliasId] = angular.copy(aliasInfo);
vm.entityAliasesInfo[aliasId].selectedId = aliasInfo.currentEntity.id;
}
}
function currentAliasEntityChanged(aliasId, selectedId) {
var resolvedEntities = vm.entityAliasesInfo[aliasId].resolvedEntities;
var selected = $filter('filter')(resolvedEntities, {id: selectedId});
if (selected && selected.length) {
vm.aliasController.updateCurrentAliasEntity(aliasId, selected[0]);
if (onEntityAliasesUpdate) {
onEntityAliasesUpdate();
}
}
}
}

10
ui/src/app/entity/aliases-entity-select-panel.tpl.html → ui/src/app/entity/alias/aliases-entity-select-panel.tpl.html

@ -18,12 +18,12 @@
<md-content flex layout="column">
<section flex layout="column">
<md-content flex class="md-padding" layout="column">
<div flex layout="row" ng-repeat="(aliasId, entityAlias) in vm.entityAliases">
<div flex layout="row" ng-repeat="(aliasId, entityAliasInfo) in vm.entityAliasesInfo">
<md-input-container flex>
<label>{{entityAlias.alias}}</label>
<md-select ng-model="vm.entityAliases[aliasId].entityId">
<md-option ng-repeat="entityInfo in vm.entityAliasesInfo[aliasId]" ng-value="entityInfo.id">
{{entityInfo.name}}
<label>{{entityAliasInfo.alias}}</label>
<md-select ng-model="entityAliasInfo.selectedId" ng-change="vm.currentAliasEntityChanged(aliasId, entityAliasInfo.selectedId)">
<md-option ng-repeat="resolvedEntity in entityAliasInfo.resolvedEntities" ng-value="resolvedEntity.id">
{{resolvedEntity.name}}
</md-option>
</md-select>
</md-input-container>

58
ui/src/app/entity/aliases-entity-select.directive.js → ui/src/app/entity/alias/aliases-entity-select.directive.js

@ -29,7 +29,7 @@ import aliasesEntitySelectPanelTemplate from './aliases-entity-select-panel.tpl.
/*@ngInject*/
export default function AliasesEntitySelectDirective($compile, $templateCache, $mdMedia, types, $mdPanel, $document, $translate) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var linker = function (scope, element, attrs) {
/* tbAliasesEntitySelect (ng-model)
* {
@ -81,10 +81,8 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $
position: position,
fullscreen: false,
locals: {
'entityAliases': angular.copy(scope.model),
'entityAliasesInfo': scope.entityAliasesInfo,
'onEntityAliasesUpdate': function (entityAliases) {
scope.model = entityAliases;
'aliasController': scope.aliasController,
'onEntityAliasesUpdate': function () {
scope.updateView();
}
},
@ -96,41 +94,40 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $
$mdPanel.open(config);
}
scope.$on('entityAliasesChanged', function() {
scope.updateView();
});
scope.$on('entityAliasResolved', function() {
scope.updateView();
});
scope.updateView = function () {
var value = angular.copy(scope.model);
ngModelCtrl.$setViewValue(value);
updateDisplayValue();
}
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
scope.model = angular.copy(value);
updateDisplayValue();
}
}
function updateDisplayValue() {
var displayValue;
var singleValue = true;
var currentAliasId;
for (var aliasId in scope.model) {
if (!currentAliasId) {
currentAliasId = aliasId;
} else {
singleValue = false;
break;
var entityAliases = scope.aliasController.getEntityAliases();
for (var aliasId in entityAliases) {
var entityAlias = entityAliases[aliasId];
if (!entityAlias.filter.resolveMultiple) {
var resolvedAlias = scope.aliasController.getInstantAliasInfo(aliasId);
if (resolvedAlias && resolvedAlias.currentEntity) {
if (!currentAliasId) {
currentAliasId = aliasId;
} else {
singleValue = false;
break;
}
}
}
}
if (singleValue && currentAliasId) {
var entityId = scope.model[currentAliasId].entityId;
var entitiesInfo = scope.entityAliasesInfo[currentAliasId];
for (var i=0;i<entitiesInfo.length;i++) {
if (entitiesInfo[i].id === entityId) {
displayValue = entitiesInfo[i].name;
break;
}
}
var aliasInfo = scope.aliasController.getInstantAliasInfo(currentAliasId);
displayValue = aliasInfo.currentEntity.name;
} else {
displayValue = $translate.instant('entity.entities');
}
@ -142,9 +139,8 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $
return {
restrict: "E",
require: "^ngModel",
scope: {
entityAliasesInfo:'='
aliasController:'='
},
link: linker
};

0
ui/src/app/entity/aliases-entity-select.scss → ui/src/app/entity/alias/aliases-entity-select.scss

109
ui/src/app/entity/alias/entity-alias-dialog.controller.js

@ -0,0 +1,109 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import './entity-alias-dialog.scss';
/*@ngInject*/
export default function EntityAliasDialogController($scope, $mdDialog, $q, $filter, utils, entityService, types, isAdd, allowedEntityTypes, entityAliases, alias) {
var vm = this;
vm.types = types;
vm.isAdd = isAdd;
vm.allowedEntityTypes = allowedEntityTypes;
if (angular.isArray(entityAliases)) {
vm.entityAliases = entityAliases;
} else {
vm.entityAliases = [];
for (var aliasId in entityAliases) {
vm.entityAliases.push(entityAliases[aliasId]);
}
}
if (vm.isAdd && !alias) {
vm.alias = {
alias: '',
filter: {
resolveMultiple: false
}
};
} else {
vm.alias = alias;
}
vm.cancel = cancel;
vm.save = save;
$scope.$watch('vm.alias.alias', function (newAlias) {
if (newAlias) {
var valid = true;
var result = $filter('filter')(vm.entityAliases, {alias: newAlias}, true);
if (result && result.length) {
if (vm.isAdd || vm.alias.id != result[0].id) {
valid = false;
}
}
$scope.theForm.aliasName.$setValidity('duplicateAliasName', valid);
}
});
$scope.$watch('theForm.$pristine', function() {
if ($scope.theForm && !$scope.theForm.$pristine) {
$scope.theForm.$setValidity('entityFilter', true);
}
});
function validate() {
var deferred = $q.defer();
var validationResult = {
entity: null,
stateEntity: false
}
entityService.resolveAliasFilter(vm.alias.filter, null, 1).then(
function success(result) {
validationResult.stateEntity = result.stateEntity;
var entities = result.entities;
if (entities.length) {
validationResult.entity = entities[0];
}
deferred.resolve(validationResult);
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function cancel() {
$mdDialog.cancel();
}
function save() {
$scope.theForm.$setPristine();
validate().then(
function success() {
if (vm.isAdd) {
vm.alias.id = utils.guid();
}
$mdDialog.hide(vm.alias);
},
function fail() {
$scope.theForm.$setValidity('entityFilter', false);
}
)
}
}

19
ui/src/app/entity/entity-aliases.scss → ui/src/app/entity/alias/entity-alias-dialog.scss

@ -14,15 +14,14 @@
* limitations under the License.
*/
.tb-aliases-dialog {
.md-dialog-content {
padding-bottom: 0px;
}
.tb-alias {
padding: 10px 0 0 10px;
margin: 5px;
md-select.tb-entity-type-select {
padding-bottom: 24px;
.tb-entity-alias-dialog {
.tb-resolve-multiple-switch {
padding-left: 10px;
.resolve-multiple-switch {
margin: 0;
}
.resolve-multiple-label {
margin: 5px 0;
}
}
}
}

73
ui/src/app/entity/alias/entity-alias-dialog.tpl.html

@ -0,0 +1,73 @@
<!--
Copyright © 2016-2017 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-dialog class="tb-entity-alias-dialog" style="width: 600px;" aria-label="{{ (vm.isAdd ? 'alias.add' : 'alias.edit') | translate }}">
<form name="theForm" ng-submit="vm.save()">
<md-toolbar>
<div class="md-toolbar-tools">
<h2>{{ (vm.isAdd ? 'alias.add' : 'alias.edit') | translate }}</h2>
<span flex></span>
<md-button class="md-icon-button" ng-click="vm.cancel()">
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">
<fieldset ng-disabled="loading">
<div flex layout="column">
<div layout="row">
<md-input-container flex class="md-block">
<label translate>alias.name</label>
<input required name="aliasName"
ng-model="vm.alias.alias"
aria-label="{{ 'alias.name' | translate }}">
<div ng-messages="theForm.aliasName.$error">
<div ng-message="required" translate>alias.name-required</div>
<div ng-message="duplicateAliasName" translate>alias.duplicate-alias</div>
</div>
</md-input-container>
<section class="tb-resolve-multiple-switch" layout="column" layout-align="start center">
<label class="tb-small resolve-multiple-label" translate>alias.resolve-multiple</label>
<md-switch class="resolve-multiple-switch" ng-model="vm.alias.filter.resolveMultiple"
aria-label="{{ 'alias.resolve-multiple' | translate }}">
</md-switch>
</section>
</div>
<tb-entity-filter
ng-model="vm.alias.filter"
allowed-entity-types="vm.allowedEntityTypes"
the-form="theForm">
</tb-entity-filter>
<div class="tb-error-messages" ng-messages="theForm.$error" role="alert">
<div translate ng-message="entityFilter" class="tb-error-message">alias.entity-filter-no-entity-matched</div>
</div>
</div>
</fieldset>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
<span flex></span>
<md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary">
{{ (vm.isAdd ? 'action.add' : 'action.save') | translate }}
</md-button>
<md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button>
</md-dialog-actions>
</form>
</md-dialog>

152
ui/src/app/entity/entity-aliases.controller.js → ui/src/app/entity/alias/entity-aliases.controller.js

@ -15,22 +15,27 @@
*/
import './entity-aliases.scss';
/* eslint-disable import/no-unresolved, import/default */
import entityAliasDialogTemplate from './entity-alias-dialog.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function EntityAliasesController(utils, entityService, toast, $scope, $mdDialog, $document, $q, $translate,
types, config) {
var vm = this;
vm.isSingleEntityAlias = config.isSingleEntityAlias;
vm.singleEntityAlias = config.singleEntityAlias;
vm.types = types;
vm.entityAliases = [];
vm.title = config.customTitle ? config.customTitle : 'entity.aliases';
vm.disableAdd = config.disableAdd;
vm.aliasToWidgetsMap = {};
vm.allowedEntityTypes = config.allowedEntityTypes;
vm.onFilterEntityChanged = onFilterEntityChanged;
vm.addAlias = addAlias;
vm.editAlias = editAlias;
vm.removeAlias = removeAlias;
vm.cancel = cancel;
@ -79,51 +84,61 @@ export default function EntityAliasesController(utils, entityService, toast, $sc
}
}
if (vm.isSingleEntityAlias) {
checkEntityAlias(vm.singleEntityAlias);
}
for (aliasId in config.entityAliases) {
var entityAlias = config.entityAliases[aliasId];
var result = {id: aliasId, alias: entityAlias.alias, entityType: entityAlias.entityType, entityFilter: entityAlias.entityFilter, changed: true};
checkEntityAlias(result);
var filter = entityAlias.filter;
if (!filter) {
filter = {
resolveMultiple: false
};
}
if (!filter.resolveMultiple) {
filter.resolveMultiple = false;
}
var result = {id: aliasId, alias: entityAlias.alias, filter: filter};
vm.entityAliases.push(result);
}
}
function checkEntityAlias(entityAlias) {
if (!entityAlias.entityType) {
entityAlias.entityType = types.entityType.device;
}
if (!entityAlias.entityFilter || entityAlias.entityFilter == null) {
entityAlias.entityFilter = {
useFilter: false,
entityNameFilter: '',
entityList: [],
};
}
function addAlias($event) {
openAliasDialog($event);
}
function onFilterEntityChanged(entity, entityAlias) {
if (entityAlias) {
if (!entityAlias.alias || entityAlias.alias.length == 0) {
entityAlias.changed = false;
}
if (!entityAlias.changed && entity && entityAlias.entityType) {
entityAlias.alias = entity.name;
}
}
function editAlias($event, entityAlias) {
openAliasDialog($event, entityAlias);
}
function addAlias() {
var aliasId = 0;
for (var a in vm.entityAliases) {
aliasId = Math.max(vm.entityAliases[a].id, aliasId);
function openAliasDialog($event, entityAlias) {
var isAdd = entityAlias ? false : true;
var aliasIndex;
if (!isAdd) {
aliasIndex = vm.entityAliases.indexOf(entityAlias);
}
aliasId++;
var entityAlias = {id: aliasId, alias: '', entityType: types.entityType.device,
entityFilter: {useFilter: false, entityNameFilter: '', entityList: []}, changed: false};
vm.entityAliases.push(entityAlias);
$mdDialog.show({
controller: 'EntityAliasDialogController',
controllerAs: 'vm',
templateUrl: entityAliasDialogTemplate,
locals: {
isAdd: isAdd,
allowedEntityTypes: vm.allowedEntityTypes,
entityAliases: vm.entityAliases,
alias: isAdd ? null : angular.copy(entityAlias)
},
parent: angular.element($document[0].body),
fullscreen: true,
skipHide: true,
targetEvent: $event
}).then(function (alias) {
if (isAdd) {
vm.entityAliases.push(alias);
} else {
vm.entityAliases[aliasIndex] = alias;
}
if ($scope.theForm) {
$scope.theForm.$setDirty();
}
}, function () {
});
}
function removeAlias($event, entityAlias) {
@ -160,61 +175,36 @@ export default function EntityAliasesController(utils, entityService, toast, $sc
$mdDialog.cancel();
}
function cleanupEntityFilter(entityFilter) {
if (entityFilter.useFilter) {
entityFilter.entityList = [];
} else {
entityFilter.entityNameFilter = '';
}
return entityFilter;
}
function save() {
var entityAliases = {};
var uniqueAliasList = {};
var valid = true;
var aliasId, maxAliasId;
var alias;
var i;
if (vm.isSingleEntityAlias) {
maxAliasId = 0;
vm.singleEntityAlias.entityFilter = cleanupEntityFilter(vm.singleEntityAlias.entityFilter);
for (i = 0; i < vm.entityAliases.length; i ++) {
aliasId = vm.entityAliases[i].id;
alias = vm.entityAliases[i].alias;
if (alias === vm.singleEntityAlias.alias) {
valid = false;
break;
}
maxAliasId = Math.max(aliasId, maxAliasId);
}
maxAliasId++;
vm.singleEntityAlias.id = maxAliasId;
} else {
for (i = 0; i < vm.entityAliases.length; i++) {
aliasId = vm.entityAliases[i].id;
alias = vm.entityAliases[i].alias;
if (!uniqueAliasList[alias]) {
uniqueAliasList[alias] = alias;
entityAliases[aliasId] = {alias: alias, entityType: vm.entityAliases[i].entityType, entityFilter: cleanupEntityFilter(vm.entityAliases[i].entityFilter)};
} else {
valid = false;
break;
}
var message, aliasId, alias, filter;
for (var i = 0; i < vm.entityAliases.length; i++) {
aliasId = vm.entityAliases[i].id;
alias = vm.entityAliases[i].alias;
filter = vm.entityAliases[i].filter;
if (uniqueAliasList[alias]) {
valid = false;
message = $translate.instant('entity.duplicate-alias-error', {alias: alias});
break;
} else if (!filter || !filter.type) {
valid = false;
message = $translate.instant('entity.missing-entity-filter-error', {alias: alias});
break;
} else {
uniqueAliasList[alias] = alias;
entityAliases[aliasId] = {id: aliasId, alias: alias, filter: filter};
}
}
if (valid) {
$scope.theForm.$setPristine();
if (vm.isSingleEntityAlias) {
$mdDialog.hide(vm.singleEntityAlias);
} else {
$mdDialog.hide(entityAliases);
}
$mdDialog.hide(entityAliases);
} else {
toast.showError($translate.instant('entity.duplicate-alias-error', {alias: alias}));
toast.showError(message);
}
}

49
ui/src/app/entity/alias/entity-aliases.scss

@ -0,0 +1,49 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-aliases-dialog {
.md-dialog-content {
padding-bottom: 0px;
padding-top: 0px;
}
.tb-aliases-header {
min-height: 40px;
padding: 0 34px 0 34px;
margin: 5px;
.tb-header-label {
font-size: 14px;
color: rgba(0, 0, 0, 0.570588);
}
}
.tb-alias {
padding: 0 0 0 10px;
margin: 5px;
md-input-container {
margin: 0px;
}
.tb-resolve-multiple-switch {
padding-left: 10px;
.resolve-multiple-switch {
margin: 0;
}
}
.md-button {
&.md-icon-button {
margin: 0px;
}
}
}
}

105
ui/src/app/entity/alias/entity-aliases.tpl.html

@ -0,0 +1,105 @@
<!--
Copyright © 2016-2017 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-dialog class="tb-aliases-dialog" style="width: 700px;" aria-label="{{ vm.title | translate }}">
<form name="theForm" ng-submit="vm.save()">
<md-toolbar>
<div class="md-toolbar-tools">
<h2>{{ vm.title | translate }}</h2>
<span flex></span>
<md-button class="md-icon-button" ng-click="vm.cancel()">
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<div class="tb-aliases-header" flex layout="row" layout-align="start center">
<span flex="5"></span>
<div flex layout="row" layout-align="start center">
<span class="tb-header-label" translate flex="20" style="min-width: 150px;">alias.name</span>
<span class="tb-header-label" translate flex="70" style="padding-left: 10px;">alias.entity-filter</span>
<span class="tb-header-label" translate flex="10" style="padding-left: 10px; min-width: 120px;">alias.resolve-multiple</span>
<span style="min-width: 80px;"></span>
</div>
</div>
<md-divider></md-divider>
<md-dialog-content>
<div class="md-dialog-content">
<fieldset ng-disabled="loading">
<div ng-form name="aliasForm" flex layout="row" layout-align="start center" ng-repeat="entityAlias in vm.entityAliases track by $index">
<span flex="5">{{$index + 1}}.</span>
<di class="md-whiteframe-4dp tb-alias" flex layout="row" layout-align="start center">
<md-input-container flex="20" style="min-width: 150px;" md-no-float class="md-block">
<input required name="alias" placeholder="{{ 'entity.alias' | translate }}" ng-model="entityAlias.alias">
<div ng-messages="aliasForm.alias.$error">
<div translate ng-message="required">entity.alias-required</div>
</div>
</md-input-container>
<tb-entity-filter-view
flex="70" style="padding-left: 10px;"
ng-model="entityAlias.filter">
</tb-entity-filter-view>
<section flex="10" style="padding-left: 10px; min-width: 120px;"
class="tb-resolve-multiple-switch"
layout="column"
layout-align="center center">
<md-switch class="resolve-multiple-switch"
ng-model="entityAlias.filter.resolveMultiple"
aria-label="resolve-multiple-switcher">
</md-switch>
</section>
<md-button ng-disabled="loading" class="md-icon-button md-primary" style="min-width: 40px;"
ng-click="vm.editAlias($event, entityAlias)" aria-label="{{ 'action.edit' | translate }}">
<md-tooltip md-direction="top">
{{ 'alias.edit' | translate }}
</md-tooltip>
<md-icon aria-label="{{ 'alias.edit' | translate }}" class="material-icons">
edit
</md-icon>
</md-button>
<md-button ng-disabled="loading" class="md-icon-button md-primary" style="min-width: 40px;"
ng-click="vm.removeAlias($event, entityAlias)" aria-label="{{ 'action.remove' | translate }}">
<md-tooltip md-direction="top">
{{ 'entity.remove-alias' | translate }}
</md-tooltip>
<md-icon aria-label="{{ 'action.delete' | translate }}" class="material-icons">
close
</md-icon>
</md-button>
</di>
</div>
</fieldset>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
<md-button ng-show="!vm.disableAdd" ng-disabled="loading" class="md-primary md-raised"
ng-click="vm.addAlias($event)"
aria-label="{{ 'alias.add' | translate }}">
<md-tooltip md-direction="top">
{{ 'alias.add' | translate }}
</md-tooltip>
<span translate>alias.add</span>
</md-button>
<span flex></span>
<md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary">
{{ 'action.save' | translate }}
</md-button>
<md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button>
</md-dialog-actions>
</form>
</md-dialog>

12
ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js

@ -22,7 +22,8 @@ import selectTargetLayoutTemplate from '../../dashboard/layouts/select-target-la
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function AddWidgetToDashboardDialogController($scope, $mdDialog, $state, $q, $document, itembuffer, dashboardService, entityId, entityType, entityName, widget) {
export default function AddWidgetToDashboardDialogController($scope, $mdDialog, $state, $q, $document, dashboardUtils,
types, itembuffer, dashboardService, entityId, entityType, entityName, widget) {
var vm = this;
@ -125,13 +126,8 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog,
targetDeviceAliases: {}
};
aliasesInfo.datasourceAliases[0] = {
aliasName: entityName,
entityType: entityType,
entityFilter: {
useFilter: false,
entityNameFilter: '',
entityList: [entityId]
}
alias: entityName,
filter: dashboardUtils.createSingleEntityFilter(entityType, entityId)
};
itembuffer.addWidgetToDashboard(theDashboard, targetState, targetLayout, vm.widget, aliasesInfo, null, 48, null, -1, -1).then(
function(theDashboard) {

43
ui/src/app/entity/attribute/attribute-table.directive.js

@ -26,10 +26,12 @@ import editAttributeValueTemplate from './edit-attribute-value.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
import EditAttributeValueController from './edit-attribute-value.controller';
import AliasController from '../../api/alias-controller';
/*@ngInject*/
export default function AttributeTableDirective($compile, $templateCache, $rootScope, $q, $mdEditDialog, $mdDialog,
$document, $translate, $filter, utils, types, dashboardService, attributeService, widgetService) {
$mdUtil, $document, $translate, $filter, utils, types, dashboardUtils,
dashboardService, entityService, attributeService, widgetService) {
var linker = function (scope, element, attrs) {
@ -246,15 +248,19 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
}
scope.nextWidget = function() {
if (scope.widgetsCarousel.index < scope.widgetsList.length-1) {
scope.widgetsCarousel.index++;
}
$mdUtil.nextTick(function () {
if (scope.widgetsCarousel.index < scope.widgetsList.length - 1) {
scope.widgetsCarousel.index++;
}
});
}
scope.prevWidget = function() {
if (scope.widgetsCarousel.index > 0) {
scope.widgetsCarousel.index--;
}
$mdUtil.nextTick(function () {
if (scope.widgetsCarousel.index > 0) {
scope.widgetsCarousel.index--;
}
});
}
scope.enterWidgetMode = function() {
@ -281,23 +287,28 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
scope.firstBundle = true;
scope.selectedWidgetsBundleAlias = types.systemBundleAlias.cards;
scope.aliasesInfo = {
entityAliases: {
'1': {alias: scope.entityName, entityType: scope.entityType, entityId: scope.entityId}
},
entityAliasesInfo: {
'1': [
{name: scope.entityName, entityType: scope.entityType, id: scope.entityId}
]
var entityAlias = {
id: utils.guid(),
alias: scope.entityName,
filter: dashboardUtils.createSingleEntityFilter(scope.entityType, scope.entityId)
};
var entitiAliases = {};
entitiAliases[entityAlias.id] = entityAlias;
var stateController = {
getStateParams: function() {
return {};
}
};
scope.aliasController = new AliasController(scope, $q, $filter, utils,
types, entityService, stateController, entitiAliases);
var dataKeyType = scope.attributeScope === types.latestTelemetry ?
types.dataKeyType.timeseries : types.dataKeyType.attribute;
var datasource = {
type: types.datasourceType.entity,
entityAliasId: '1',
entityAliasId: entityAlias.id,
dataKeys: []
}
var i = 0;

2
ui/src/app/entity/attribute/attribute-table.tpl.html

@ -156,7 +156,7 @@
rn-swipe-disabled="true">
<li ng-repeat="widgets in widgetsList">
<tb-dashboard
aliases-info="aliasesInfo"
alias-controller="aliasController"
widgets="widgets"
get-st-diff="getServerTimeDiff()"
columns="20"

106
ui/src/app/entity/entity-aliases.tpl.html

@ -1,106 +0,0 @@
<!--
Copyright © 2016-2017 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-dialog class="tb-aliases-dialog" style="width: 700px;" aria-label="{{ vm.title | translate }}">
<form name="theForm" ng-submit="vm.save()">
<md-toolbar>
<div class="md-toolbar-tools">
<h2>{{ vm.isSingleEntityAlias ? ('entity.configure-alias' | translate:vm.singleEntityAlias ) : (vm.title | translate) }}</h2>
<span flex></span>
<md-button class="md-icon-button" ng-click="vm.cancel()">
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">
<fieldset ng-disabled="loading">
<div ng-show="vm.isSingleEntityAlias" layout="row">
<tb-entity-type-select style="min-width: 100px;"
ng-model="vm.singleEntityAlias.entityType"
allowed-entity-types="vm.allowedEntityTypes">
</tb-entity-type-select>
<tb-entity-filter flex entity-type="vm.singleEntityAlias.entityType" ng-model="vm.singleEntityAlias.entityFilter">
</tb-entity-filter>
</div>
<div ng-show="!vm.isSingleEntityAlias" flex layout="row" layout-align="start center">
<span flex="5"></span>
<div flex layout="row" layout-align="start center"
style="padding: 0 0 0 10px; margin: 5px;">
<span translate flex="20" style="min-width: 100px;">entity.alias</span>
<span translate flex="20" style="min-width: 100px;">entity.type</span>
<span translate flex="60" style="min-width: 190px; padding-left: 10px;">entity.entities</span>
<span style="min-width: 40px;"></span>
</div>
</div>
<div ng-show="!vm.isSingleEntityAlias" style="max-height: 500px; overflow: auto; padding-bottom: 20px;">
<div ng-form name="aliasForm" flex layout="row" layout-align="start center" ng-repeat="entityAlias in vm.entityAliases track by $index">
<span flex="5">{{$index + 1}}.</span>
<div class="md-whiteframe-4dp tb-alias" flex layout="row" layout-align="start center">
<md-input-container flex="20" style="min-width: 100px;" md-no-float class="md-block">
<input required ng-change="entityAlias.changed=true" name="alias" placeholder="{{ 'entity.alias' | translate }}" ng-model="entityAlias.alias">
<div ng-messages="aliasForm.alias.$error">
<div translate ng-message="required">entity.alias-required</div>
</div>
</md-input-container>
<section flex="20" layout="column" style="min-width: 100px;" >
<tb-entity-type-select hide-label style="padding-left: 10px;"
ng-model="entityAlias.entityType"
allowed-entity-types="vm.allowedEntityTypes">
</tb-entity-type-select>
</section>
<section flex="60" layout="column">
<tb-entity-filter style="padding-left: 10px;"
entity-type="entityAlias.entityType"
ng-model="entityAlias.entityFilter"
on-matching-entity-change="vm.onFilterEntityChanged(entity, entityAlias)">
</tb-entity-filter>
</section>
<md-button ng-disabled="loading" class="md-icon-button md-primary" style="min-width: 40px;"
ng-click="vm.removeAlias($event, entityAlias)" aria-label="{{ 'action.remove' | translate }}">
<md-tooltip md-direction="top">
{{ 'entity.remove-alias' | translate }}
</md-tooltip>
<md-icon aria-label="{{ 'action.delete' | translate }}" class="material-icons">
close
</md-icon>
</md-button>
</div>
</div>
</div>
<div ng-show="!vm.isSingleEntityAlias && !vm.disableAdd" style="padding-bottom: 10px;">
<md-button ng-disabled="loading" class="md-primary md-raised" ng-click="vm.addAlias($event)" aria-label="{{ 'action.add' | translate }}">
<md-tooltip md-direction="top">
{{ 'entity.add-alias' | translate }}
</md-tooltip>
<span translate>action.add</span>
</md-button>
</div>
</fieldset>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
<span flex></span>
<md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary">
{{ 'action.save' | translate }}
</md-button>
<md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button>
</md-dialog-actions>
</form>
</md-dialog>

205
ui/src/app/entity/entity-filter-view.directive.js

@ -0,0 +1,205 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable import/no-unresolved, import/default */
import entityFilterViewTemplate from './entity-filter-view.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
import './entity-filter-view.scss';
/*@ngInject*/
export default function EntityFilterViewDirective($compile, $templateCache, $q, $document, $mdDialog, $translate, types/*, entityService*/) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(entityFilterViewTemplate);
element.html(template);
scope.ngModelCtrl = ngModelCtrl;
scope.types = types;
scope.filterDisplayValue = '';
scope.$watch('filter', function () {
scope.updateDisplayValue();
});
scope.updateDisplayValue = function() {
if (scope.filter && scope.filter.type) {
var entityType;
var prefix;
switch (scope.filter.type) {
case types.aliasFilterType.entityList.value:
entityType = scope.filter.entityType;
var count = scope.filter.entityList.length;
scope.filterDisplayValue = $translate.instant(types.entityTypeTranslations[entityType].list, {count: count}, 'messageformat');
break;
case types.aliasFilterType.entityName.value:
entityType = scope.filter.entityType;
prefix = scope.filter.entityNameFilter;
scope.filterDisplayValue = $translate.instant(types.entityTypeTranslations[entityType].nameStartsWith, {prefix: prefix});
break;
case types.aliasFilterType.stateEntity.value:
scope.filterDisplayValue = $translate.instant('alias.filter-type-state-entity-description');
break;
case types.aliasFilterType.assetType.value:
var assetType = scope.filter.assetType;
prefix = scope.filter.assetNameFilter;
if (prefix && prefix.length) {
scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-type-and-name-description', {assetType: assetType, prefix: prefix});
} else {
scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-type-description', {assetType: assetType});
}
break;
case types.aliasFilterType.deviceType.value:
var deviceType = scope.filter.deviceType;
prefix = scope.filter.deviceNameFilter;
if (prefix && prefix.length) {
scope.filterDisplayValue = $translate.instant('alias.filter-type-device-type-and-name-description', {deviceType: deviceType, prefix: prefix});
} else {
scope.filterDisplayValue = $translate.instant('alias.filter-type-device-type-description', {deviceType: deviceType});
}
break;
case types.aliasFilterType.relationsQuery.value:
var rootEntityText;
var directionText;
var allEntitiesText = $translate.instant('alias.all-entities');
var anyRelationText = $translate.instant('alias.any-relation');
if (scope.filter.rootStateEntity) {
rootEntityText = $translate.instant('alias.state-entity');
} else {
rootEntityText = $translate.instant(types.entityTypeTranslations[scope.filter.rootEntity.entityType].type);
}
directionText = $translate.instant('relation.direction-type.' + scope.filter.direction);
var relationFilters = scope.filter.filters;
if (relationFilters && relationFilters.length) {
var relationFiltersDisplayValues = [];
relationFilters.forEach(function(relationFilter) {
var entitiesText;
if (relationFilter.entityTypes && relationFilter.entityTypes.length) {
var entitiesNamesList = [];
relationFilter.entityTypes.forEach(function(entityType) {
entitiesNamesList.push(
$translate.instant(types.entityTypeTranslations[entityType].typePlural)
);
});
entitiesText = entitiesNamesList.join(', ');
} else {
entitiesText = allEntitiesText;
}
var relationTypeText;
if (relationFilter.relationType && relationFilter.relationType.length) {
relationTypeText = "'" + relationFilter.relationType + "'";
} else {
relationTypeText = anyRelationText;
}
var relationFilterDisplayValue = $translate.instant('alias.filter-type-relations-query-description',
{
entities: entitiesText,
relationType: relationTypeText,
direction: directionText,
rootEntity: rootEntityText
}
);
relationFiltersDisplayValues.push(relationFilterDisplayValue);
});
scope.filterDisplayValue = relationFiltersDisplayValues.join(', ');
} else {
scope.filterDisplayValue = $translate.instant('alias.filter-type-relations-query-description',
{
entities: allEntitiesText,
relationType: anyRelationText,
direction: directionText,
rootEntity: rootEntityText
}
);
}
break;
case types.aliasFilterType.assetSearchQuery.value:
case types.aliasFilterType.deviceSearchQuery.value:
allEntitiesText = $translate.instant('alias.all-entities');
anyRelationText = $translate.instant('alias.any-relation');
if (scope.filter.rootStateEntity) {
rootEntityText = $translate.instant('alias.state-entity');
} else {
rootEntityText = $translate.instant(types.entityTypeTranslations[scope.filter.rootEntity.entityType].type);
}
directionText = $translate.instant('relation.direction-type.' + scope.filter.direction);
var relationTypeText;
if (scope.filter.relationType && scope.filter.relationType.length) {
relationTypeText = "'" + scope.filter.relationType + "'";
} else {
relationTypeText = anyRelationText;
}
var translationValues = {
relationType: relationTypeText,
direction: directionText,
rootEntity: rootEntityText
}
if (scope.filter.type == types.aliasFilterType.assetSearchQuery.value) {
var assetTypesQuoted = [];
scope.filter.assetTypes.forEach(function(assetType) {
assetTypesQuoted.push("'"+assetType+"'");
});
var assetTypesText = assetTypesQuoted.join(', ');
translationValues.assetTypes = assetTypesText;
scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-search-query-description',
translationValues
);
} else {
var deviceTypesQuoted = [];
scope.filter.deviceTypes.forEach(function(deviceType) {
deviceTypesQuoted.push("'"+deviceType+"'");
});
var deviceTypesText = deviceTypesQuoted.join(', ');
translationValues.deviceTypes = deviceTypesText;
scope.filterDisplayValue = $translate.instant('alias.filter-type-device-search-query-description',
translationValues
);
}
break;
default:
scope.filterDisplayValue = scope.filter.type;
break;
}
} else {
scope.filterDisplayValue = '';
}
}
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
scope.filter = ngModelCtrl.$viewValue;
} else {
scope.filter = null;
}
}
$compile(element.contents())(scope);
}
return {
restrict: "E",
require: "^ngModel",
link: linker,
scope: true
};
}

34
ui/src/app/entity/aliases-entity-select-panel.controller.js → ui/src/app/entity/entity-filter-view.scss

@ -1,4 +1,4 @@
/*
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -14,18 +14,20 @@
* limitations under the License.
*/
/*@ngInject*/
export default function AliasesEntitySelectPanelController(mdPanelRef, $scope, types, entityAliases, entityAliasesInfo, onEntityAliasesUpdate) {
var vm = this;
vm._mdPanelRef = mdPanelRef;
vm.entityAliases = entityAliases;
vm.entityAliasesInfo = entityAliasesInfo;
vm.onEntityAliasesUpdate = onEntityAliasesUpdate;
$scope.$watch('vm.entityAliases', function () {
if (onEntityAliasesUpdate) {
onEntityAliasesUpdate(vm.entityAliases);
}
}, true);
}
.tb-entity-filter-view {
.entity-filter-empty {
color: rgba(221, 44, 0, 0.87);
font-size: 14px;
line-height: 16px;
}
.entity-filter-type {
font-size: 14px;
line-height: 16px;
color: rgba(0, 0, 0, 0.570588);
}
.entity-filter-value {
font-size: 14px;
line-height: 16px;
color: rgba(0, 0, 0, 0.570588);
}
}

24
ui/src/app/entity/entity-filter-view.tpl.html

@ -0,0 +1,24 @@
<!--
Copyright © 2016-2017 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div layout='column' class="tb-entity-filter-view">
<div ng-if="!filter || !filter.type" class="entity-filter-empty" translate>alias.no-entity-filter-specified</div>
<div ng-if="filter && filter.type" layout="column">
<div class="entity-filter-value">{{ filterDisplayValue }}</div>
</div>
</div>

232
ui/src/app/entity/entity-filter.directive.js

@ -23,7 +23,7 @@ import entityFilterTemplate from './entity-filter.tpl.html';
import './entity-filter.scss';
/*@ngInject*/
export default function EntityFilterDirective($compile, $templateCache, $q, entityService) {
export default function EntityFilterDirective($compile, $templateCache, $q, $document, $mdDialog, types, entityService) {
var linker = function (scope, element, attrs, ngModelCtrl) {
@ -31,181 +31,76 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti
element.html(template);
scope.ngModelCtrl = ngModelCtrl;
scope.types = types;
scope.aliasFilterTypes = entityService.getAliasFilterTypesByEntityTypes(scope.allowedEntityTypes);
scope.fetchEntities = function(searchText, limit) {
var deferred = $q.defer();
entityService.getEntitiesByNameFilter(scope.entityType, searchText, limit).then(function success(result) {
if (result) {
deferred.resolve(result);
} else {
deferred.resolve([]);
}
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
scope.updateValidity = function() {
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
var valid;
if (value.useFilter) {
ngModelCtrl.$setValidity('entityList', true);
if (angular.isDefined(value.entityNameFilter) && value.entityNameFilter.length > 0) {
ngModelCtrl.$setValidity('entityNameFilter', true);
valid = angular.isDefined(scope.model.matchingFilterEntity) && scope.model.matchingFilterEntity != null;
ngModelCtrl.$setValidity('entityNameFilterEntityMatch', valid);
} else {
ngModelCtrl.$setValidity('entityNameFilter', false);
}
} else {
ngModelCtrl.$setValidity('entityNameFilter', true);
ngModelCtrl.$setValidity('entityNameFilterDeviceMatch', true);
valid = angular.isDefined(value.entityList) && value.entityList.length > 0;
ngModelCtrl.$setValidity('entityList', valid);
}
}
}
ngModelCtrl.$render = function () {
destroyWatchers();
scope.model = {
useFilter: false,
entityList: [],
entityNameFilter: ''
scope.$watch('filter.type', function (newType, prevType) {
if (newType && newType != prevType) {
updateFilter();
}
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
var model = scope.model;
model.useFilter = value.useFilter === true ? true: false;
model.entityList = [];
model.entityNameFilter = value.entityNameFilter || '';
processEntityNameFilter(model.entityNameFilter).then(
function(entity) {
scope.model.matchingFilterEntity = entity;
if (value.entityList && value.entityList.length > 0) {
entityService.getEntities(scope.entityType, value.entityList).then(function (entities) {
model.entityList = entities;
updateMatchingEntity();
initWatchers();
});
} else {
updateMatchingEntity();
initWatchers();
}
});
function updateFilter() {
var filter = {};
filter.type = scope.filter.type;
filter.resolveMultiple = scope.filter.resolveMultiple;
switch (filter.type) {
case types.aliasFilterType.entityList.value:
filter.entityType = null;
filter.entityList = [];
break;
case types.aliasFilterType.entityName.value:
filter.entityType = null;
filter.entityNameFilter = '';
break;
case types.aliasFilterType.stateEntity.value:
break;
case types.aliasFilterType.assetType.value:
filter.assetType = null;
filter.assetNameFilter = '';
break;
case types.aliasFilterType.deviceType.value:
filter.deviceType = null;
filter.deviceNameFilter = '';
break;
case types.aliasFilterType.relationsQuery.value:
case types.aliasFilterType.assetSearchQuery.value:
case types.aliasFilterType.deviceSearchQuery.value:
filter.rootStateEntity = false;
filter.rootEntity = null;
filter.direction = types.entitySearchDirection.from;
filter.maxLevel = 1;
if (filter.type === types.aliasFilterType.relationsQuery.value) {
filter.filters = [];
} else if (filter.type === types.aliasFilterType.assetSearchQuery.value) {
filter.relationType = null;
filter.assetTypes = [];
} else if (filter.type === types.aliasFilterType.deviceSearchQuery.value) {
filter.relationType = null;
filter.deviceTypes = [];
}
)
break;
}
scope.filter = filter;
}
function updateMatchingEntity() {
if (scope.model.useFilter) {
scope.model.matchingEntity = scope.model.matchingFilterEntity;
} else {
if (scope.model.entityList && scope.model.entityList.length > 0) {
scope.model.matchingEntity = scope.model.entityList[0];
} else {
scope.model.matchingEntity = null;
}
}
}
function processEntityNameFilter(entityNameFilter) {
var deferred = $q.defer();
if (angular.isDefined(entityNameFilter) && entityNameFilter.length > 0) {
scope.fetchEntities(entityNameFilter, 1).then(function (entities) {
if (entities && entities.length > 0) {
deferred.resolve(entities[0]);
} else {
deferred.resolve(null);
}
});
} else {
deferred.resolve(null);
}
return deferred.promise;
}
scope.$watch('filter', function () {
scope.updateView();
});
function destroyWatchers() {
if (scope.entityTypeDeregistration) {
scope.entityTypeDeregistration();
scope.entityTypeDeregistration = null;
}
if (scope.entityListDeregistration) {
scope.entityListDeregistration();
scope.entityListDeregistration = null;
}
if (scope.useFilterDeregistration) {
scope.useFilterDeregistration();
scope.useFilterDeregistration = null;
}
if (scope.entityNameFilterDeregistration) {
scope.entityNameFilterDeregistration();
scope.entityNameFilterDeregistration = null;
}
if (scope.matchingEntityDeregistration) {
scope.matchingEntityDeregistration();
scope.matchingEntityDeregistration = null;
}
scope.updateView = function() {
ngModelCtrl.$setViewValue(scope.filter);
}
function initWatchers() {
scope.entityTypeDeregistration = scope.$watch('entityType', function (newEntityType, prevEntityType) {
if (!angular.equals(newEntityType, prevEntityType)) {
scope.model.entityList = [];
scope.model.entityNameFilter = '';
}
});
scope.entityListDeregistration = scope.$watch('model.entityList', function () {
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
value.entityList = [];
if (scope.model.entityList && scope.model.entityList.length > 0) {
for (var i=0;i<scope.model.entityList.length;i++) {
value.entityList.push(scope.model.entityList[i].id.id);
}
}
updateMatchingEntity();
ngModelCtrl.$setViewValue(value);
scope.updateValidity();
}
}, true);
scope.useFilterDeregistration = scope.$watch('model.useFilter', function () {
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
value.useFilter = scope.model.useFilter;
updateMatchingEntity();
ngModelCtrl.$setViewValue(value);
scope.updateValidity();
}
});
scope.entityNameFilterDeregistration = scope.$watch('model.entityNameFilter', function (newNameFilter, prevNameFilter) {
if (ngModelCtrl.$viewValue) {
if (!angular.equals(newNameFilter, prevNameFilter)) {
var value = ngModelCtrl.$viewValue;
value.entityNameFilter = scope.model.entityNameFilter;
processEntityNameFilter(value.entityNameFilter).then(
function(entity) {
scope.model.matchingFilterEntity = entity;
updateMatchingEntity();
ngModelCtrl.$setViewValue(value);
scope.updateValidity();
}
);
}
}
});
scope.matchingEntityDeregistration = scope.$watch('model.matchingEntity', function (newMatchingEntity, prevMatchingEntity) {
if (!angular.equals(newMatchingEntity, prevMatchingEntity)) {
if (scope.onMatchingEntityChange) {
scope.onMatchingEntityChange({entity: newMatchingEntity});
}
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
scope.filter = ngModelCtrl.$viewValue;
} else {
scope.filter = {
type: null,
resolveMultiple: false
}
});
}
}
$compile(element.contents())(scope);
@ -217,9 +112,8 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti
require: "^ngModel",
link: linker,
scope: {
entityType: '=',
isEdit: '=',
onMatchingEntityChange: '&'
theForm: '=',
allowedEntityTypes: '=?'
}
};

31
ui/src/app/entity/entity-filter.scss

@ -13,33 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-entity-filter {
#entity_list_chips {
.md-chips {
padding-bottom: 1px;
#relationsQueryFilter {
padding-top: 20px;
tb-entity-select {
min-height: 92px;
}
}
.entity-name-filter-input {
margin-top: 10px;
margin-bottom: 0px;
.md-errors-spacer {
min-height: 0px;
}
}
.tb-filter-switch {
.tb-root-state-entity-switch {
padding-left: 10px;
.filter-switch {
.root-state-entity-switch {
margin: 0;
}
.filter-label {
.root-state-entity-label {
margin: 5px 0;
}
}
.tb-error-messages {
margin-top: -11px;
height: 35px;
.tb-error-message {
padding-left: 1px;
}
}
}

262
ui/src/app/entity/entity-filter.tpl.html

@ -15,53 +15,219 @@
limitations under the License.
-->
<section layout='column' class="tb-entity-filter">
<section layout='row'>
<section layout="column" flex ng-show="!model.useFilter">
<md-chips flex
id="entity_list_chips"
ng-required="!useFilter"
ng-model="model.entityList" md-autocomplete-snap
md-require-match="true">
<md-autocomplete
md-no-cache="true"
id="entity"
md-selected-item="selectedEntity"
md-search-text="entitySearchText"
md-items="item in fetchEntities(entitySearchText, 10)"
md-item-text="item.name"
md-min-length="0"
placeholder="{{ 'entity.entity-list' | translate }}">
<md-item-template>
<span md-highlight-text="entitySearchText" md-highlight-flags="^i">{{item.name}}</span>
</md-item-template>
<md-not-found>
<span translate translate-values='{ entity: entitySearchText }'>entity.no-entities-matching</span>
</md-not-found>
</md-autocomplete>
<md-chip-template>
<span>
<strong>{{$chip.name}}</strong>
</span>
</md-chip-template>
</md-chips>
</section>
<section layout="row" flex ng-show="model.useFilter">
<md-input-container flex class="entity-name-filter-input">
<label translate>entity.name-starts-with</label>
<input ng-model="model.entityNameFilter" aria-label="{{ 'entity.name-starts-with' | translate }}">
<div layout='column' class="tb-entity-filter">
<md-input-container class="md-block">
<label>{{ 'alias.filter-type' | translate }}</label>
<md-select required name="filterType"
ng-model="filter.type" aria-label="{{ 'alias.filter-type' | translate }}">
<md-option ng-repeat="type in aliasFilterTypes" ng-value="type.value">
{{type.name | translate}}
</md-option>
</md-select>
<div ng-messages="theForm.filterType.$error">
<div ng-message="required" translate>alias.filter-type-required</div>
</div>
</md-input-container>
<section layout="column" ng-if="filter.type == types.aliasFilterType.entityList.value" id="entityListFilter">
<tb-entity-type-select
ng-model="filter.entityType"
the-form="theForm"
tb-required="true"
allowed-entity-types="allowedEntityTypes">
</tb-entity-type-select>
<tb-entity-list
ng-model="filter.entityList"
tb-required="true"
entity-type="filter.entityType">
</tb-entity-list>
</section>
<section flex layout="column" ng-if="filter.type == types.aliasFilterType.entityName.value" id="entityNameFilter">
<tb-entity-type-select
ng-model="filter.entityType"
the-form="theForm"
tb-required="true"
allowed-entity-types="allowedEntityTypes">
</tb-entity-type-select>
<md-input-container class="md-block">
<label translate>entity.name-starts-with</label>
<input required name="entityNameFilter"
ng-model="filter.entityNameFilter"
aria-label="{{ 'entity.name-starts-with' | translate }}">
<div ng-messages="theForm.entityNameFilter.$error">
<div ng-message="required" translate>entity.entity-name-filter-required</div>
</div>
</md-input-container>
</section>
<section layout="column" ng-if="filter.type == types.aliasFilterType.stateEntity.value" id="stateEntityFilter">
</section>
<section layout="column" ng-if="filter.type == types.aliasFilterType.assetType.value" id="assetTypeFilter">
<tb-entity-subtype-autocomplete
tb-required="true"
the-form="theForm"
ng-model="filter.assetType"
entity-type="types.entityType.asset">
</tb-entity-subtype-autocomplete>
<md-input-container class="md-block">
<label translate>asset.name-starts-with</label>
<input name="assetNameFilter"
ng-model="filter.assetNameFilter"
aria-label="{{ 'asset.name-starts-with' | translate }}">
</md-input-container>
</section>
<section layout="column" ng-if="filter.type == types.aliasFilterType.deviceType.value" id="deviceTypeFilter">
<tb-entity-subtype-autocomplete
tb-required="true"
the-form="theForm"
ng-model="filter.deviceType"
entity-type="types.entityType.device">
</tb-entity-subtype-autocomplete>
<md-input-container class="md-block">
<label translate>device.name-starts-with</label>
<input name="deviceNameFilter"
ng-model="filter.deviceNameFilter"
aria-label="{{ 'device.name-starts-with' | translate }}">
</md-input-container>
</section>
<section layout="column" ng-if="filter.type == types.aliasFilterType.relationsQuery.value" id="relationsQueryFilter">
<label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
<div flex layout="row">
<tb-entity-select flex
the-form="theForm"
tb-required="!filter.rootStateEntity"
ng-disabled="filter.rootStateEntity"
ng-model="filter.rootEntity">
</tb-entity-select>
<section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
<label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
<md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
aria-label="{{ 'alias.root-state-entity' | translate }}">
</md-switch>
</section>
</div>
<div flex layout="row">
<md-input-container class="md-block" style="min-width: 100px;">
<label translate>relation.direction</label>
<md-select required ng-model="filter.direction">
<md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction">
{{ ('relation.search-direction.' + direction) | translate}}
</md-option>
</md-select>
</md-input-container>
<md-input-container flex class="md-block">
<label translate>alias.max-relation-level</label>
<input name="maxRelationLevel"
type="number"
min="1"
step="1"
placeholder="{{ 'alias.unlimited-level' | translate }}"
ng-model="filter.maxLevel"
aria-label="{{ 'alias.max-relation-level' | translate }}">
</md-input-container>
</div>
<div class="md-caption" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>relation.relation-filters</div>
<tb-relation-filters
ng-model="filter.filters"
allowed-entity-types="allowedEntityTypes">
</tb-relation-filters>
</section>
<section layout="column" ng-if="filter.type == types.aliasFilterType.assetSearchQuery.value" id="assetSearchQueryFilter">
<label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
<div flex layout="row">
<tb-entity-select flex
the-form="theForm"
tb-required="!filter.rootStateEntity"
ng-disabled="filter.rootStateEntity"
ng-model="filter.rootEntity">
</tb-entity-select>
<section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
<label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
<md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
aria-label="{{ 'alias.root-state-entity' | translate }}">
</md-switch>
</section>
</div>
<div flex layout="row">
<md-input-container class="md-block" style="min-width: 100px;">
<label translate>relation.direction</label>
<md-select required ng-model="filter.direction">
<md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction">
{{ ('relation.search-direction.' + direction) | translate}}
</md-option>
</md-select>
</md-input-container>
<md-input-container flex class="md-block">
<label translate>alias.max-relation-level</label>
<input name="maxRelationLevel"
type="number"
min="1"
step="1"
placeholder="{{ 'alias.unlimited-level' | translate }}"
ng-model="filter.maxLevel"
aria-label="{{ 'alias.max-relation-level' | translate }}">
</md-input-container>
</div>
<div class="md-caption" style="color: rgba(0,0,0,0.57);" translate>relation.relation-type</div>
<tb-relation-type-autocomplete flex
hide-label
the-form="theForm"
ng-model="filter.relationType"
tb-required="false">
</tb-relation-type-autocomplete>
<div class="md-caption tb-required" style="color: rgba(0,0,0,0.57);" translate>asset.asset-types</div>
<tb-entity-subtype-list
tb-required="true"
entity-type="types.entityType.asset"
ng-model="filter.assetTypes">
</tb-entity-subtype-list>
</section>
<section layout="column" ng-if="filter.type == types.aliasFilterType.deviceSearchQuery.value" id="deviceSearchQueryFilter">
<label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
<div flex layout="row">
<tb-entity-select flex
the-form="theForm"
tb-required="!filter.rootStateEntity"
ng-disabled="filter.rootStateEntity"
ng-model="filter.rootEntity">
</tb-entity-select>
<section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
<label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
<md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
aria-label="{{ 'alias.root-state-entity' | translate }}">
</md-switch>
</section>
</div>
<div flex layout="row">
<md-input-container class="md-block" style="min-width: 100px;">
<label translate>relation.direction</label>
<md-select required ng-model="filter.direction">
<md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction">
{{ ('relation.search-direction.' + direction) | translate}}
</md-option>
</md-select>
</md-input-container>
<md-input-container flex class="md-block">
<label translate>alias.max-relation-level</label>
<input name="maxRelationLevel"
type="number"
min="1"
step="1"
placeholder="{{ 'alias.unlimited-level' | translate }}"
ng-model="filter.maxLevel"
aria-label="{{ 'alias.max-relation-level' | translate }}">
</md-input-container>
</section>
<section class="tb-filter-switch" layout="column" layout-align="center center">
<label class="tb-small filter-label" translate>entity.use-entity-name-filter</label>
<md-switch class="filter-switch" ng-model="model.useFilter" aria-label="use-filter-switcher">
</md-switch>
</section>
</div>
<div class="md-caption" style="color: rgba(0,0,0,0.57);" translate>relation.relation-type</div>
<tb-relation-type-autocomplete flex
hide-label
the-form="theForm"
ng-model="filter.relationType"
tb-required="false">
</tb-relation-type-autocomplete>
<div class="md-caption tb-required" style="color: rgba(0,0,0,0.57);" translate>device.device-types</div>
<tb-entity-subtype-list
tb-required="true"
entity-type="types.entityType.device"
ng-model="filter.deviceTypes">
</tb-entity-subtype-list>
</section>
<div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
<div translate ng-message="entityList" class="tb-error-message">entity.entity-list-empty</div>
<div translate ng-message="entityNameFilter" class="tb-error-message">entity.entity-name-filter-required</div>
<div translate translate-values='{ entity: model.entityNameFilter }' ng-message="entityNameFilterEntityMatch"
class="tb-error-message">entity.entity-name-filter-no-entity-matched</div>
</div>
</section>
</div>

130
ui/src/app/entity/entity-list.directive.js

@ -0,0 +1,130 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable import/no-unresolved, import/default */
import entityListTemplate from './entity-list.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
import './entity-list.scss';
/*@ngInject*/
export default function EntityListDirective($compile, $templateCache, $q, $mdUtil, entityService) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(entityListTemplate);
element.html(template);
scope.ngModelCtrl = ngModelCtrl;
scope.$watch('tbRequired', function () {
scope.updateValidity();
});
scope.fetchEntities = function(searchText, limit) {
var deferred = $q.defer();
entityService.getEntitiesByNameFilter(scope.entityType, searchText, limit).then(
function success(result) {
if (result) {
deferred.resolve(result);
} else {
deferred.resolve([]);
}
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
scope.updateValidity = function() {
var value = ngModelCtrl.$viewValue;
var valid = !scope.tbRequired || value && value.length > 0;
ngModelCtrl.$setValidity('entityList', valid);
}
ngModelCtrl.$render = function () {
destroyWatchers();
var value = ngModelCtrl.$viewValue;
scope.entityList = [];
if (value && value.length > 0) {
entityService.getEntities(scope.entityType, value).then(function (entities) {
scope.entityList = entities;
initWatchers();
});
} else {
initWatchers();
}
}
function initWatchers() {
scope.entityTypeDeregistration = scope.$watch('entityType', function (newEntityType, prevEntityType) {
if (!angular.equals(newEntityType, prevEntityType)) {
scope.entityList = [];
}
});
scope.entityListDeregistration = scope.$watch('entityList', function () {
var ids = [];
if (scope.entityList && scope.entityList.length > 0) {
for (var i=0;i<scope.entityList.length;i++) {
ids.push(scope.entityList[i].id.id);
}
}
var value = ngModelCtrl.$viewValue;
if (!angular.equals(ids, value)) {
ngModelCtrl.$setViewValue(ids);
}
scope.updateValidity();
}, true);
}
function destroyWatchers() {
if (scope.entityTypeDeregistration) {
scope.entityTypeDeregistration();
scope.entityTypeDeregistration = null;
}
if (scope.entityListDeregistration) {
scope.entityListDeregistration();
scope.entityListDeregistration = null;
}
}
$compile(element.contents())(scope);
$mdUtil.nextTick(function(){
var inputElement = angular.element('input', element);
inputElement.on('blur', function() {
scope.inputTouched = true;
} );
});
}
return {
restrict: "E",
require: "^ngModel",
link: linker,
scope: {
disabled:'=ngDisabled',
tbRequired: '=?',
entityType: '='
}
};
}

30
ui/src/app/entity/entity-list.scss

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*.tb-entity-list {
#entity_list_chips {
.md-chips {
padding-bottom: 1px;
}
}
.tb-error-messages {
margin-top: -11px;
height: 35px;
.tb-error-message {
padding-left: 1px;
}
}
}*/

52
ui/src/app/entity/entity-list.tpl.html

@ -0,0 +1,52 @@
<!--
Copyright © 2016-2017 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<section flex layout='column' class="tb-entity-list">
<md-chips flex
readonly="disabled"
id="entity_list_chips"
ng-required="tbRequired"
ng-model="entityList"
md-autocomplete-snap
md-require-match="true">
<md-autocomplete
md-no-cache="true"
id="entity"
md-selected-item="selectedEntity"
md-search-text="entitySearchText"
md-items="item in fetchEntities(entitySearchText, 10)"
md-item-text="item.name"
md-min-length="0"
placeholder="{{ 'entity.entity-list' | translate }}">
<md-item-template>
<span md-highlight-text="entitySearchText" md-highlight-flags="^i">{{item.name}}</span>
</md-item-template>
<md-not-found>
<span translate translate-values='{ entity: entitySearchText }'>entity.no-entities-matching</span>
</md-not-found>
</md-autocomplete>
<md-chip-template>
<span>
<strong>{{$chip.name}}</strong>
</span>
</md-chip-template>
</md-chips>
<div class="tb-error-messages" ng-messages="ngModelCtrl.$error" ng-if="inputTouched" role="alert">
<div translate ng-message="entityList" class="tb-error-message">entity.entity-list-empty</div>
</div>
</section>

3
ui/src/app/entity/entity-subtype-autocomplete.directive.js

@ -114,6 +114,9 @@ export default function EntitySubtypeAutocomplete($compile, $templateCache, $q,
scope.selectEntitySubtypeText = 'asset.select-asset-type';
scope.entitySubtypeText = 'asset.asset-type';
scope.entitySubtypeRequiredText = 'asset.asset-type-required';
scope.$on('assetSaved', function() {
scope.entitySubtypes = null;
});
} else if (scope.entityType == types.entityType.device) {
scope.selectEntitySubtypeText = 'device.select-device-type';
scope.entitySubtypeText = 'device.device-type';

146
ui/src/app/entity/entity-subtype-list.directive.js

@ -0,0 +1,146 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable import/no-unresolved, import/default */
import entitySubtypeListTemplate from './entity-subtype-list.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
import './entity-subtype-list.scss';
/*@ngInject*/
export default function EntitySubtypeListDirective($compile, $templateCache, $q, $mdUtil, $translate, $filter, types, assetService, deviceService) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(entitySubtypeListTemplate);
element.html(template);
scope.ngModelCtrl = ngModelCtrl;
scope.entitySubtypesList = [];
scope.entitySubtypes = null;
if (scope.entityType == types.entityType.asset) {
scope.placeholder = scope.tbRequired ? $translate.instant('asset.enter-asset-type')
: $translate.instant('asset.any-asset');
scope.secondaryPlaceholder = '+' + $translate.instant('asset.asset-type');
scope.noSubtypesMathingText = 'asset.no-asset-types-matching';
scope.subtypeListEmptyText = 'asset.asset-type-list-empty';
} else if (scope.entityType == types.entityType.device) {
scope.placeholder = scope.tbRequired ? $translate.instant('device.enter-device-type')
: $translate.instant('device.any-device');
scope.secondaryPlaceholder = '+' + $translate.instant('device.device-type');
scope.noSubtypesMathingText = 'device.no-device-types-matching';
scope.subtypeListEmptyText = 'device.device-type-list-empty';
}
scope.$watch('tbRequired', function () {
scope.updateValidity();
});
scope.fetchEntitySubtypes = function(searchText) {
var deferred = $q.defer();
loadSubTypes().then(
function success(subTypes) {
var result = $filter('filter')(subTypes, {'$': searchText});
if (result && result.length) {
deferred.resolve(result);
} else {
deferred.resolve([searchText]);
}
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
scope.updateValidity = function() {
var value = ngModelCtrl.$viewValue;
var valid = !scope.tbRequired || value && value.length > 0;
ngModelCtrl.$setValidity('entitySubtypesList', valid);
}
ngModelCtrl.$render = function () {
scope.entitySubtypesList = ngModelCtrl.$viewValue;
if (!scope.entitySubtypesList) {
scope.entitySubtypesList = [];
}
}
scope.$watch('entitySubtypesList', function () {
ngModelCtrl.$setViewValue(scope.entitySubtypesList);
scope.updateValidity();
}, true);
function loadSubTypes() {
var deferred = $q.defer();
if (!scope.entitySubtypes) {
var entitySubtypesPromise;
if (scope.entityType == types.entityType.asset) {
entitySubtypesPromise = assetService.getAssetTypes();
} else if (scope.entityType == types.entityType.device) {
entitySubtypesPromise = deviceService.getDeviceTypes();
}
if (entitySubtypesPromise) {
entitySubtypesPromise.then(
function success(types) {
scope.entitySubtypes = [];
types.forEach(function (type) {
scope.entitySubtypes.push(type.type);
});
deferred.resolve(scope.entitySubtypes);
},
function fail() {
deferred.reject();
}
);
} else {
deferred.reject();
}
} else {
deferred.resolve(scope.entitySubtypes);
}
return deferred.promise;
}
$compile(element.contents())(scope);
$mdUtil.nextTick(function(){
var inputElement = angular.element('input', element);
inputElement.on('blur', function() {
scope.inputTouched = true;
} );
});
}
return {
restrict: "E",
require: "^ngModel",
link: linker,
scope: {
disabled:'=ngDisabled',
tbRequired: '=?',
entityType: "="
}
};
}

30
ui/src/app/entity/entity-subtype-list.scss

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*.tb-entity-subtype-list {
#entity_subtype_list_chips {
.md-chips {
padding-bottom: 1px;
}
}
.tb-error-messages {
margin-top: -11px;
height: 35px;
.tb-error-message {
padding-left: 1px;
}
}
}*/

54
ui/src/app/entity/entity-subtype-list.tpl.html

@ -0,0 +1,54 @@
<!--
Copyright © 2016-2017 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<section flex layout='column' class="tb-entity-subtype-list">
<md-chips flex
readonly="disabled"
id="entity_subtype_list_chips"
ng-required="tbRequired"
ng-model="entitySubtypesList"
placeholder="{{placeholder}}"
secondary-placeholder="{{secondaryPlaceholder}}"
md-autocomplete-snap
md-require-match="false">
<md-autocomplete
md-no-cache="true"
id="entitySubtype"
md-selected-item="selectedEntitySubtype"
md-search-text="entitySubtypeSearchText"
md-items="item in fetchEntitySubtypes(entitySubtypeSearchText)"
md-item-text="item"
md-min-length="0"
placeholder="{{ (!entitySubtypesList || !entitySubtypesList.length) ? placeholder : secondaryPlaceholder }}">
<md-item-template>
<span md-highlight-text="entitySubtypeSearchText" md-highlight-flags="^i">{{item}}</span>
</md-item-template>
<md-not-found>
<span translate translate-values='{ entitySubtype: entitySubtypeSearchText }'>{{noSubtypesMathingText}}</span>
</md-not-found>
</md-autocomplete>
<md-chip-template>
<span>
<strong>{{$chip}}</strong>
</span>
</md-chip-template>
</md-chips>
<div class="tb-error-messages" ng-messages="ngModelCtrl.$error" ng-if="inputTouched && tbRequired" role="alert">
<div translate ng-message="entitySubtypesList" class="tb-error-message">{{subtypeListEmptyText}}</div>
</div>
</section>

111
ui/src/app/entity/entity-type-list.directive.js

@ -0,0 +1,111 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable import/no-unresolved, import/default */
import entityTypeListTemplate from './entity-type-list.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
import './entity-type-list.scss';
/*@ngInject*/
export default function EntityTypeListDirective($compile, $templateCache, $q, $mdUtil, $translate, $filter, types, entityService) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(entityTypeListTemplate);
element.html(template);
scope.ngModelCtrl = ngModelCtrl;
scope.placeholder = scope.tbRequired ? $translate.instant('entity.enter-entity-type')
: $translate.instant('entity.any-entity');
scope.secondaryPlaceholder = '+' + $translate.instant('entity.entity-type');
var entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes);
scope.entityTypesList = [];
for (var type in entityTypes) {
var entityTypeInfo = {};
entityTypeInfo.value = entityTypes[type];
entityTypeInfo.name = $translate.instant(types.entityTypeTranslations[entityTypeInfo.value].type) + '';
scope.entityTypesList.push(entityTypeInfo);
}
scope.$watch('tbRequired', function () {
scope.updateValidity();
});
scope.fetchEntityTypes = function(searchText) {
var deferred = $q.defer();
var entityTypes = $filter('filter')(scope.entityTypesList, {name: searchText});
deferred.resolve(entityTypes);
return deferred.promise;
}
scope.updateValidity = function() {
var value = ngModelCtrl.$viewValue;
var valid = !scope.tbRequired || value && value.length > 0;
ngModelCtrl.$setValidity('entityTypeList', valid);
}
ngModelCtrl.$render = function () {
scope.entityTypeList = [];
var value = ngModelCtrl.$viewValue;
if (value && value.length) {
value.forEach(function(type) {
var entityTypeInfo = {};
entityTypeInfo.value = type;
entityTypeInfo.name = $translate.instant(types.entityTypeTranslations[entityTypeInfo.value].type) + '';
scope.entityTypeList.push(entityTypeInfo);
});
}
}
scope.$watch('entityTypeList', function () {
var values = [];
if (scope.entityTypeList && scope.entityTypeList.length) {
scope.entityTypeList.forEach(function(entityType) {
values.push(entityType.value);
});
}
ngModelCtrl.$setViewValue(values);
scope.updateValidity();
}, true);
$compile(element.contents())(scope);
$mdUtil.nextTick(function(){
var inputElement = angular.element('input', element);
inputElement.on('blur', function() {
scope.inputTouched = true;
} );
});
}
return {
restrict: "E",
require: "^ngModel",
link: linker,
scope: {
disabled:'=ngDisabled',
tbRequired: '=?',
allowedEntityTypes: '=?'
}
};
}

30
ui/src/app/entity/entity-type-list.scss

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*.tb-entity-type-list {
#entity_type_list_chips {
.md-chips {
padding-bottom: 1px;
}
}
.tb-error-messages {
margin-top: -11px;
height: 35px;
.tb-error-message {
padding-left: 1px;
}
}
}*/

54
ui/src/app/entity/entity-type-list.tpl.html

@ -0,0 +1,54 @@
<!--
Copyright © 2016-2017 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<section flex layout='column' class="tb-entity-type-list">
<md-chips flex
readonly="disabled"
id="entity_type_list_chips"
ng-required="tbRequired"
ng-model="entityTypeList"
placeholder="{{placeholder}}"
secondary-placeholder="{{secondaryPlaceholder}}"
md-autocomplete-snap
md-require-match="true">
<md-autocomplete
md-no-cache="true"
id="entityType"
md-selected-item="selectedEntityType"
md-search-text="entityTypeSearchText"
md-items="item in fetchEntityTypes(entityTypeSearchText)"
md-item-text="item.name"
md-min-length="0"
placeholder="{{ (!entityTypeList || !entityTypeList.length) ? placeholder : secondaryPlaceholder }}">
<md-item-template>
<span md-highlight-text="entityTypeSearchText" md-highlight-flags="^i">{{item.name}}</span>
</md-item-template>
<md-not-found>
<span translate translate-values='{ entityType: entityTypeSearchText }'>entity.no-entity-types-matching</span>
</md-not-found>
</md-autocomplete>
<md-chip-template>
<span>
<strong>{{$chip.name}}</strong>
</span>
</md-chip-template>
</md-chips>
<div class="tb-error-messages" ng-messages="ngModelCtrl.$error" ng-if="inputTouched && tbRequired" role="alert">
<div translate ng-message="entityTypeList" class="tb-error-message">entity.entity-type-list-empty</div>
</div>
</section>

35
ui/src/app/entity/entity-type-select.directive.js

@ -23,7 +23,7 @@ import entityTypeSelectTemplate from './entity-type-select.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function EntityTypeSelect($compile, $templateCache, utils, userService, types) {
export default function EntityTypeSelect($compile, $templateCache, utils, entityService, userService, types) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(entityTypeSelectTemplate);
@ -39,39 +39,10 @@ export default function EntityTypeSelect($compile, $templateCache, utils, userSe
scope.ngModelCtrl = ngModelCtrl;
var authority = userService.getAuthority();
scope.entityTypes = {};
switch(authority) {
case 'SYS_ADMIN':
scope.entityTypes.tenant = types.entityType.tenant;
scope.entityTypes.rule = types.entityType.rule;
scope.entityTypes.plugin = types.entityType.plugin;
break;
case 'TENANT_ADMIN':
scope.entityTypes.device = types.entityType.device;
scope.entityTypes.asset = types.entityType.asset;
scope.entityTypes.customer = types.entityType.customer;
scope.entityTypes.rule = types.entityType.rule;
scope.entityTypes.plugin = types.entityType.plugin;
scope.entityTypes.dashboard = types.entityType.dashboard;
break;
case 'CUSTOMER_USER':
scope.entityTypes.device = types.entityType.device;
scope.entityTypes.asset = types.entityType.asset;
scope.entityTypes.dashboard = types.entityType.dashboard;
break;
}
if (scope.allowedEntityTypes) {
for (var entityType in scope.entityTypes) {
if (scope.allowedEntityTypes.indexOf(scope.entityTypes[entityType]) === -1) {
delete scope.entityTypes[entityType];
}
}
}
scope.entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes);
scope.typeName = function(type) {
return utils.entityTypeName(type);
return type ? types.entityTypeTranslations[type].type : '';
}
scope.updateValidity = function () {

18
ui/src/app/entity/index.js

@ -14,34 +14,46 @@
* limitations under the License.
*/
import EntityAliasesController from './entity-aliases.controller';
import EntityAliasesController from './alias/entity-aliases.controller';
import EntityAliasDialogController from './alias/entity-alias-dialog.controller';
import EntityTypeSelectDirective from './entity-type-select.directive';
import EntityTypeListDirective from './entity-type-list.directive';
import EntitySubtypeListDirective from './entity-subtype-list.directive';
import EntitySubtypeSelectDirective from './entity-subtype-select.directive';
import EntitySubtypeAutocompleteDirective from './entity-subtype-autocomplete.directive';
import EntityAutocompleteDirective from './entity-autocomplete.directive';
import EntityListDirective from './entity-list.directive';
import EntitySelectDirective from './entity-select.directive';
import EntityFilterDirective from './entity-filter.directive';
import AliasesEntitySelectPanelController from './aliases-entity-select-panel.controller';
import AliasesEntitySelectDirective from './aliases-entity-select.directive';
import EntityFilterViewDirective from './entity-filter-view.directive';
import AliasesEntitySelectPanelController from './alias/aliases-entity-select-panel.controller';
import AliasesEntitySelectDirective from './alias/aliases-entity-select.directive';
import AddAttributeDialogController from './attribute/add-attribute-dialog.controller';
import AddWidgetToDashboardDialogController from './attribute/add-widget-to-dashboard-dialog.controller';
import AttributeTableDirective from './attribute/attribute-table.directive';
import RelationFiltersDirective from './relation/relation-filters.directive';
import RelationTableDirective from './relation/relation-table.directive';
import RelationTypeAutocompleteDirective from './relation/relation-type-autocomplete.directive';
export default angular.module('thingsboard.entity', [])
.controller('EntityAliasesController', EntityAliasesController)
.controller('EntityAliasDialogController', EntityAliasDialogController)
.controller('AliasesEntitySelectPanelController', AliasesEntitySelectPanelController)
.controller('AddAttributeDialogController', AddAttributeDialogController)
.controller('AddWidgetToDashboardDialogController', AddWidgetToDashboardDialogController)
.directive('tbEntityTypeSelect', EntityTypeSelectDirective)
.directive('tbEntityTypeList', EntityTypeListDirective)
.directive('tbEntitySubtypeList', EntitySubtypeListDirective)
.directive('tbEntitySubtypeSelect', EntitySubtypeSelectDirective)
.directive('tbEntitySubtypeAutocomplete', EntitySubtypeAutocompleteDirective)
.directive('tbEntityAutocomplete', EntityAutocompleteDirective)
.directive('tbEntityList', EntityListDirective)
.directive('tbEntitySelect', EntitySelectDirective)
.directive('tbEntityFilter', EntityFilterDirective)
.directive('tbEntityFilterView', EntityFilterViewDirective)
.directive('tbAliasesEntitySelect', AliasesEntitySelectDirective)
.directive('tbAttributeTable', AttributeTableDirective)
.directive('tbRelationFilters', RelationFiltersDirective)
.directive('tbRelationTable', RelationTableDirective)
.directive('tbRelationTypeAutocomplete', RelationTypeAutocompleteDirective)
.name;

85
ui/src/app/entity/relation/relation-filters.directive.js

@ -0,0 +1,85 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import './relation-filters.scss';
/* eslint-disable import/no-unresolved, import/default */
import relationFiltersTemplate from './relation-filters.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function RelationFilters($compile, $templateCache) {
return {
restrict: "E",
require: "^ngModel",
scope: {
allowedEntityTypes: '=?'
},
link: linker
};
function linker( scope, element, attrs, ngModelCtrl ) {
var template = $templateCache.get(relationFiltersTemplate);
element.html(template);
scope.relationFilters = [];
scope.addFilter = addFilter;
scope.removeFilter = removeFilter;
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
value.forEach(function (filter) {
scope.relationFilters.push(filter);
});
}
scope.$watch('relationFilters', function (newVal, prevVal) {
if (!angular.equals(newVal, prevVal)) {
updateValue();
}
}, true);
}
function addFilter() {
var filter = {
relationType: null,
entityTypes: []
};
scope.relationFilters.push(filter);
}
function removeFilter($event, filter) {
var index = scope.relationFilters.indexOf(filter);
if (index > -1) {
scope.relationFilters.splice(index, 1);
}
}
function updateValue() {
var value = [];
scope.relationFilters.forEach(function (filter) {
value.push(filter);
});
ngModelCtrl.$setViewValue(value);
}
$compile(element.contents())(scope);
}
}

77
ui/src/app/entity/relation/relation-filters.scss

@ -0,0 +1,77 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-relation-filters {
.header {
padding-left: 5px;
padding-right: 5px;
padding-bottom: 5px;
.cell {
padding-left: 5px;
padding-right: 5px;
color: rgba(0,0,0,.54);
font-size: 12px;
font-weight: 700;
white-space: nowrap;
}
}
.body {
padding-left: 5px;
padding-right: 5px;
max-height: 300px;
overflow: auto;
padding-bottom: 20px;
.row {
padding-top: 5px;
}
.cell {
padding-left: 5px;
padding-right: 5px;
md-select {
margin: 0 0 24px;
}
md-input-container {
margin: 0;
}
md-chips-wrap {
padding: 0px;
margin: 0 0 24px;
.md-chip-input-container {
margin: 0;
}
md-autocomplete {
height: 30px;
md-autocomplete-wrap {
height: 30px;
}
}
}
.md-chips .md-chip-input-container input {
padding: 2px 2px 2px;
height: 26px;
line-height: 26px;
}
}
.md-button {
margin: 0;
}
}
}

67
ui/src/app/entity/relation/relation-filters.tpl.html

@ -0,0 +1,67 @@
<!--
Copyright © 2016-2017 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="tb-relation-filters">
<div class="header" ng-show="relationFilters.length">
<div layout="row" layout-align="start center">
<span class="cell" style="width: 200px; min-width: 200px;" translate>relation.type</span>
<span class="cell" flex translate>entity.entity-types</span>
<span class="cell" style="width: 40px; min-width: 40px;">&nbsp</span>
</div>
</div>
<div class="body" ng-show="relationFilters.length">
<div class="row" ng-form name="relationFilterForm" flex layout="row" layout-align="start center" ng-repeat="filter in relationFilters track by $index">
<div class="md-whiteframe-1dp" flex layout="row" layout-align="start center">
<tb-relation-type-autocomplete class="cell" style="width: 200px; min-width: 200px;"
hide-label
the-form="relationFilterForm"
ng-model="filter.relationType"
tb-required="false">
</tb-relation-type-autocomplete>
<tb-entity-type-list class="cell" flex
ng-model="filter.entityTypes"
allowed-entity-types="allowedEntityTypes"
tb-required="false">
</tb-entity-type-list>
<md-button ng-disabled="loading" class="md-icon-button md-primary" style="width: 40px; min-width: 40px;"
ng-click="removeFilter($event, filter)" aria-label="{{ 'action.remove' | translate }}">
<md-tooltip md-direction="top">
{{ 'relation.remove-relation-filter' | translate }}
</md-tooltip>
<md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">
close
</md-icon>
</md-button>
</div>
</div>
</div>
<div class="any-filter" ng-show="!relationFilters.length">
<span layout-align="center center"
class="tb-prompt" translate>relation.any-relation</span>
</div>
<div>
<md-button ng-disabled="loading" class="md-primary md-raised" ng-click="addFilter($event)" aria-label="{{ 'action.add' | translate }}">
<md-tooltip md-direction="top">
{{ 'relation.add-relation-filter' | translate }}
</md-tooltip>
<md-icon aria-label="{{ 'action.add' | translate }}" class="material-icons">
add
</md-icon>
{{ 'action.add' | translate }}
</md-button>
</div>
</div>

4
ui/src/app/entity/relation/relation-table.directive.js

@ -218,9 +218,9 @@ function RelationTableController($scope, $q, $mdDialog, $document, $translate, $
function success(allRelations) {
allRelations.forEach(function(relation) {
if (vm.direction == vm.types.entitySearchDirection.from) {
relation.toEntityTypeName = $translate.instant(utils.entityTypeName(relation.to.entityType));
relation.toEntityTypeName = $translate.instant(types.entityTypeTranslations[relation.to.entityType].type);
} else {
relation.fromEntityTypeName = $translate.instant(utils.entityTypeName(relation.from.entityType));
relation.fromEntityTypeName = $translate.instant(types.entityTypeTranslations[relation.from.entityType].type);
}
});
vm.allRelations = allRelations;

2
ui/src/app/entity/relation/relation-type-autocomplete.directive.js

@ -29,6 +29,8 @@ export default function RelationTypeAutocomplete($compile, $templateCache, $q, $
element.html(template);
scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
scope.hideLabel = angular.isDefined(attrs.hideLabel) ? true : false;
scope.relationType = null;
scope.relationTypeSearchText = '';
scope.relationTypes = [];

2
ui/src/app/entity/relation/relation-type-autocomplete.tpl.html

@ -26,7 +26,7 @@
md-items="item in fetchRelationTypes(relationTypeSearchText)"
md-item-text="item"
md-min-length="0"
md-floating-label="{{ 'relation.relation-type' | translate }}"
md-floating-label="{{ tbRequired ? ('relation.relation-type' | translate) : ( !relationType ? ('relation.any-relation-type' | translate) : ' ') }}"
md-select-on-match="true"
md-menu-class="tb-relation-type-autocomplete">
<md-item-template>

76
ui/src/app/import-export/import-export.service.js

@ -16,7 +16,7 @@
/* eslint-disable import/no-unresolved, import/default */
import importDialogTemplate from './import-dialog.tpl.html';
import entityAliasesTemplate from '../entity/entity-aliases.tpl.html';
import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
@ -24,7 +24,7 @@ import entityAliasesTemplate from '../entity/entity-aliases.tpl.html';
/* eslint-disable no-undef, angular/window-service, angular/document-service */
/*@ngInject*/
export default function ImportExport($log, $translate, $q, $mdDialog, $document, itembuffer, types, dashboardUtils,
export default function ImportExport($log, $translate, $q, $mdDialog, $document, itembuffer, utils, types, dashboardUtils,
entityService, dashboardService, pluginService, ruleService, widgetService, toast) {
@ -359,30 +359,47 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
}
function prepareEntityAlias(aliasInfo) {
var entityFilter;
var entityType;
var alias;
var filter;
if (aliasInfo.deviceId) {
entityFilter = {
useFilter: false,
entityNameFilter: '',
entityList: [aliasInfo.deviceId]
}
entityType = types.entityType.device;
alias = aliasInfo.aliasName;
filter = {
type: types.aliasFilterType.entityList.value,
entityType: types.entityType.device,
entityList: [aliasInfo.deviceId],
resolveMultiple: false
};
} else if (aliasInfo.deviceFilter) {
entityFilter = {
useFilter: aliasInfo.deviceFilter.useFilter,
entityNameFilter: aliasInfo.deviceFilter.deviceNameFilter,
entityList: aliasInfo.deviceFilter.deviceList
alias = aliasInfo.aliasName;
filter = {
type: aliasInfo.deviceFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value,
entityType: types.entityType.device,
resolveMultiple: false
}
if (filter.type == types.aliasFilterType.entityList.value) {
filter.entityList = aliasInfo.deviceFilter.deviceList
} else {
filter.entityNameFilter = aliasInfo.deviceFilter.deviceNameFilter;
}
} else if (aliasInfo.entityFilter) {
alias = aliasInfo.aliasName;
filter = {
type: aliasInfo.entityFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value,
entityType: aliasInfo.entityType,
resolveMultiple: false
}
if (filter.type == types.aliasFilterType.entityList.value) {
filter.entityList = aliasInfo.entityFilter.entityList;
} else {
filter.entityNameFilter = aliasInfo.entityFilter.entityNameFilter;
}
entityType = types.entityType.device;
} else {
entityFilter = aliasInfo.entityFilter;
entityType = aliasInfo.entityType;
alias = aliasInfo.alias;
filter = aliasInfo.filter;
}
return {
aliasName: aliasInfo.aliasName,
entityType: entityType,
entityFilter: entityFilter
alias: alias,
filter: filter
};
}
@ -395,6 +412,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
deferred.reject();
} else {
var widget = widgetItem.widget;
widget = dashboardUtils.validateAndUpdateWidget(widget);
var aliasesInfo = prepareAliasesInfo(widgetItem.aliasesInfo);
var originalColumns = widgetItem.originalColumns;
var originalSize = widgetItem.originalSize;
@ -405,20 +423,22 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
var entityAliases = {};
var datasourceAliasesMap = {};
var targetDeviceAliasesMap = {};
var aliasId = 1;
var aliasId;
var datasourceIndex;
if (datasourceAliases) {
for (datasourceIndex in datasourceAliases) {
aliasId = utils.guid();
datasourceAliasesMap[aliasId] = datasourceIndex;
entityAliases[aliasId] = datasourceAliases[datasourceIndex];
aliasId++;
entityAliases[aliasId].id = aliasId;
}
}
if (targetDeviceAliases) {
for (datasourceIndex in targetDeviceAliases) {
aliasId = utils.guid();
targetDeviceAliasesMap[aliasId] = datasourceIndex;
entityAliases[aliasId] = targetDeviceAliases[datasourceIndex];
aliasId++;
entityAliases[aliasId].id = aliasId;
}
}
@ -435,12 +455,10 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
var datasourceIndex;
if (datasourceAliasesMap[aliasId]) {
datasourceIndex = datasourceAliasesMap[aliasId];
datasourceAliases[datasourceIndex].entityType = entityAlias.entityType;
datasourceAliases[datasourceIndex].entityFilter = entityAlias.entityFilter;
datasourceAliases[datasourceIndex] = entityAlias;
} else if (targetDeviceAliasesMap[aliasId]) {
datasourceIndex = targetDeviceAliasesMap[aliasId];
targetDeviceAliases[datasourceIndex].entityType = entityAlias.entityType;
targetDeviceAliases[datasourceIndex].entityFilter = entityAlias.entityFilter;
targetDeviceAliases[datasourceIndex] = entityAlias;
}
}
addImportedWidget(dashboard, targetState, targetLayoutFunction, $event, widget,
@ -622,7 +640,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
checkNextEntityAliasOrComplete(index, aliasIds, entityAliases, missingEntityAliases, deferred);
} else {
var missingEntityAlias = angular.copy(entityAlias);
missingEntityAlias.entityFilter = null;
missingEntityAlias.filter = null;
missingEntityAliases[aliasId] = missingEntityAlias;
checkNextEntityAliasOrComplete(index, aliasIds, entityAliases, missingEntityAliases, deferred);
}
@ -641,8 +659,6 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
entityAliases: missingEntityAliases,
widgets: widgets,
isSingleWidget: isSingleWidget,
isSingleEntityAlias: false,
singleEntityAlias: null,
customTitle: customTitle,
disableAdd: true
}

112
ui/src/app/locale/locale.constant.js

@ -112,6 +112,42 @@ export default angular.module('thingsboard.locale', [])
"no-alarms-matching": "No alarms matching '{{entity}}' were found.",
"alarm-required": "Alarm is required"
},
"alias": {
"add": "Add alias",
"edit": "Edit alias",
"name": "Alias name",
"name-required": "Alias name is required",
"duplicate-alias": "Alias with same name is already exists.",
"filter-type-entity-list": "Entity list",
"filter-type-entity-name": "Entity name",
"filter-type-state-entity": "Entity from dashboard state",
"filter-type-state-entity-description": "Entity taken from dashboard state parameters",
"filter-type-asset-type": "Asset type",
"filter-type-asset-type-description": "Assets of type '{{assetType}}'",
"filter-type-asset-type-and-name-description": "Assets of type '{{assetType}}' and with name starting with '{{prefix}}'",
"filter-type-device-type": "Device type",
"filter-type-device-type-description": "Devices of type '{{deviceType}}'",
"filter-type-device-type-and-name-description": "Devices of type '{{deviceType}}' and with name starting with '{{prefix}}'",
"filter-type-relations-query": "Relations query",
"filter-type-relations-query-description": "{{entities}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
"filter-type-asset-search-query": "Asset search query",
"filter-type-asset-search-query-description": "Assets with types {{assetTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
"filter-type-device-search-query": "Device search query",
"filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
"entity-filter": "Entity filter",
"resolve-multiple": "Resolve as multiple entities",
"filter-type": "Filter type",
"filter-type-required": "Filter type is required.",
"entity-filter-no-entity-matched": "No entities matching specified filter were found.",
"no-entity-filter-specified": "No entity filter specified",
"root-state-entity": "Use dashboard state entity as root",
"root-entity": "Root entity",
"max-relation-level": "Max relation level",
"unlimited-level": "Unlimited level",
"state-entity": "Dashboard state entity",
"all-entities": "All entities",
"any-relation": "any"
},
"asset": {
"asset": "Asset",
"assets": "Assets",
@ -133,6 +169,11 @@ export default angular.module('thingsboard.locale', [])
"asset-type": "Asset type",
"asset-type-required": "Asset type is required.",
"select-asset-type": "Select asset type",
"enter-asset-type": "Enter asset type",
"any-asset": "Any asset",
"no-asset-types-matching": "No asset types matching '{{entitySubtype}}' were found.",
"asset-type-list-empty": "No asset types selected.",
"asset-types": "Asset types",
"name": "Name",
"name-required": "Name is required.",
"description": "Description",
@ -166,7 +207,8 @@ export default angular.module('thingsboard.locale', [])
"idCopiedMessage": "Asset Id has been copied to clipboard",
"select-asset": "Select asset",
"no-assets-matching": "No assets matching '{{entity}}' were found.",
"asset-required": "Asset is required"
"asset-required": "Asset is required",
"name-starts-with": "Asset name starts with"
},
"attribute": {
"attributes": "Attributes",
@ -417,6 +459,7 @@ export default angular.module('thingsboard.locale', [])
},
"datasource": {
"type": "Datasource type",
"name": "Name",
"add-datasource-prompt": "Please add datasource"
},
"details": {
@ -444,7 +487,7 @@ export default angular.module('thingsboard.locale', [])
"alias-required": "Device alias is required.",
"remove-alias": "Remove device alias",
"add-alias": "Add device alias",
"name-starts-with": "Name starts with",
"name-starts-with": "Device name starts with",
"device-list": "Device list",
"use-device-name-filter": "Use filter",
"device-list-empty": "No devices selected.",
@ -497,6 +540,11 @@ export default angular.module('thingsboard.locale', [])
"device-type": "Device type",
"device-type-required": "Device type is required.",
"select-device-type": "Select device type",
"enter-device-type": "Enter device type",
"any-device": "Any device",
"no-device-types-matching": "No device types matching '{{entitySubtype}}' were found.",
"device-type-list-empty": "No device types selected.",
"device-types": "Device types",
"name": "Name",
"name-required": "Name is required.",
"description": "Description",
@ -530,37 +578,72 @@ export default angular.module('thingsboard.locale', [])
"unable-delete-entity-alias-title": "Unable to delete entity alias",
"unable-delete-entity-alias-text": "Entity alias '{{entityAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",
"duplicate-alias-error": "Duplicate alias found '{{alias}}'.<br>Entity aliases must be unique whithin the dashboard.",
"missing-entity-filter-error": "Filter is missing for alias '{{alias}}'.",
"configure-alias": "Configure '{{alias}}' alias",
"alias": "Alias",
"alias-required": "Entity alias is required.",
"remove-alias": "Remove entity alias",
"add-alias": "Add entity alias",
"entity-list": "Entity list",
"entity-type": "Entity type",
"entity-types": "Entity types",
"entity-type-list": "Entity type list",
"any-entity": "Any entity",
"enter-entity-type": "Enter entity type",
"no-entities-matching": "No entities matching '{{entity}}' were found.",
"no-entity-types-matching": "No entity types matching '{{entityType}}' were found.",
"name-starts-with": "Name starts with",
"use-entity-name-filter": "Use filter",
"entity-list-empty": "No entities selected.",
"entity-type-list-empty": "No entity types selected.",
"entity-name-filter-required": "Entity name filter is required.",
"entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.",
"all-subtypes": "All",
"select-entities": "Select entities",
"no-aliases-found": "No aliases found.",
"no-alias-matching": "'{{alias}}' not found.",
"create-new-alias": "Create a new one!",
"no-keys-found": "No keys found.",
"no-key-matching": "'{{key}}' not found.",
"create-new-key": "Create a new one!",
"type": "Type",
"type-required": "Entity type is required.",
"type-device": "Device",
"type-devices": "Devices",
"list-of-devices": "{ count, select, 1 {One device} other {List of # devices} }",
"device-name-starts-with": "Devices whose names start with '{{prefix}}'",
"type-asset": "Asset",
"type-assets": "Assets",
"list-of-assets": "{ count, select, 1 {One asset} other {List of # assets} }",
"asset-name-starts-with": "Assets whose names start with '{{prefix}}'",
"type-rule": "Rule",
"type-rules": "Rules",
"list-of-rules": "{ count, select, 1 {One rule} other {List of # rules} }",
"rule-name-starts-with": "Rules whose names start with '{{prefix}}'",
"type-plugin": "Plugin",
"type-plugins": "Plugins",
"list-of-plugins": "{ count, select, 1 {One plugin} other {List of # plugins} }",
"plugin-name-starts-with": "Plugins whose names start with '{{prefix}}'",
"type-tenant": "Tenant",
"type-tenants": "Tenants",
"list-of-tenants": "{ count, select, 1 {One tenant} other {List of # tenants} }",
"tenant-name-starts-with": "Tenants whose names start with '{{prefix}}'",
"type-customer": "Customer",
"type-customers": "Customers",
"list-of-customers": "{ count, select, 1 {One customer} other {List of # customers} }",
"customer-name-starts-with": "Customers whose names start with '{{prefix}}'",
"type-user": "User",
"type-users": "Users",
"list-of-users": "{ count, select, 1 {One user} other {List of # users} }",
"user-name-starts-with": "Users whose names start with '{{prefix}}'",
"type-dashboard": "Dashboard",
"type-dashboards": "Dashboards",
"list-of-dashboards": "{ count, select, 1 {One dashboard} other {List of # dashboards} }",
"dashboard-name-starts-with": "Dashboards whose names start with '{{prefix}}'",
"type-alarm": "Alarm",
"select-entities": "Select entities",
"no-aliases-found": "No aliases found.",
"no-alias-matching": "'{{alias}}' not found.",
"create-new-alias": "Create a new one!",
"no-keys-found": "No keys found.",
"no-key-matching": "'{{key}}' not found.",
"create-new-key": "Create a new one!"
"type-alarms": "Alarms",
"list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }",
"alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'"
},
"event": {
"event-type": "Event type",
@ -724,6 +807,10 @@ export default angular.module('thingsboard.locale', [])
"FROM": "From",
"TO": "To"
},
"direction-type": {
"FROM": "from",
"TO": "to"
},
"from-relations": "Outbound relations",
"to-relations": "Inbound relations",
"selected-relations": "{ count, select, 1 {1 relation} other {# relations} } selected",
@ -737,6 +824,7 @@ export default angular.module('thingsboard.locale', [])
"delete": "Delete relation",
"relation-type": "Relation type",
"relation-type-required": "Relation type is required.",
"any-relation-type": "Any type",
"add": "Add relation",
"delete-to-relation-title": "Are you sure you want to delete relation to the entity '{{entityName}}'?",
"delete-to-relation-text": "Be careful, after the confirmation the entity '{{entityName}}' will be unrelated from the current entity.",
@ -745,7 +833,11 @@ export default angular.module('thingsboard.locale', [])
"delete-from-relation-title": "Are you sure you want to delete relation from the entity '{{entityName}}'?",
"delete-from-relation-text": "Be careful, after the confirmation current entity will be unrelated from the entity '{{entityName}}'.",
"delete-from-relations-title": "Are you sure you want to delete { count, select, 1 {1 relation} other {# relations} }?",
"delete-from-relations-text": "Be careful, after the confirmation all selected relations will be removed and current entity will be unrelated from the corresponding entities."
"delete-from-relations-text": "Be careful, after the confirmation all selected relations will be removed and current entity will be unrelated from the corresponding entities.",
"remove-relation-filter": "Remove relation filter",
"add-relation-filter": "Add relation filter",
"any-relation": "Any relation",
"relation-filters": "Relation filters"
},
"rule": {
"rule": "Rule",

37
ui/src/app/services/item-buffer.service.js

@ -46,16 +46,14 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) {
aliasesInfo {
datasourceAliases: {
datasourceIndex: {
aliasName: "...",
entityType: "...",
entityFilter: "..."
alias: "...",
filter: "..."
}
}
targetDeviceAliases: {
targetDeviceAliasIndex: {
aliasName: "...",
entityType: "...",
entityFilter: "..."
alias: "...",
filter: "..."
}
}
....
@ -64,9 +62,8 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) {
function prepareAliasInfo(entityAlias) {
return {
aliasName: entityAlias.alias,
entityType: entityAlias.entityType,
entityFilter: entityAlias.entityFilter
alias: entityAlias.alias,
filter: entityAlias.filter
};
}
@ -264,14 +261,9 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) {
}
dashboardUtils.addWidgetToLayout(theDashboard, targetState, targetLayout, widget, originalColumns, originalSize, row, column);
if (callAliasUpdateFunction) {
onAliasesUpdateFunction().then(
function() {
deferred.resolve(theDashboard);
}
);
} else {
deferred.resolve(theDashboard);
onAliasesUpdateFunction();
}
deferred.resolve(theDashboard);
return deferred.promise;
}
@ -293,8 +285,7 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) {
}
function isEntityAliasEqual(alias1, alias2) {
return alias1.entityType === alias2.entityType &&
angular.equals(alias1.entityFilter, alias2.entityFilter);
return angular.equals(alias1.filter, alias2.filter);
}
function getEntityAliasId(entityAliases, aliasInfo) {
@ -306,13 +297,9 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) {
}
}
if (!newAliasId) {
var newAliasName = createEntityAliasName(entityAliases, aliasInfo.aliasName);
newAliasId = 0;
for (aliasId in entityAliases) {
newAliasId = Math.max(newAliasId, aliasId);
}
newAliasId++;
entityAliases[newAliasId] = {alias: newAliasName, entityType: aliasInfo.entityType, entityFilter: aliasInfo.entityFilter};
var newAliasName = createEntityAliasName(entityAliases, aliasInfo.alias);
newAliasId = utils.guid();
entityAliases[newAliasId] = {id: newAliasId, alias: newAliasName, filter: aliasInfo.filter};
}
return newAliasId;
}

9
ui/src/scss/main.scss

@ -236,6 +236,15 @@ div {
}
}
.md-caption {
&.tb-required:after {
content: ' *';
font-size: 10px;
vertical-align: top;
color: rgba(0,0,0,0.54);
}
}
pre.tb-highlight {
background-color: #f7f7f7;
display: block;

Loading…
Cancel
Save