Browse Source

TB-61: Improve Aliases management. Implement Asset type and Device type alias filter.

pull/164/head
Igor Kulikov 9 years ago
parent
commit
9ef646765e
  1. 3
      ui/src/app/api/alias-controller.js
  2. 245
      ui/src/app/api/entity.service.js
  3. 6
      ui/src/app/api/subscription.js
  4. 4
      ui/src/app/common/dashboard-utils.service.js
  5. 51
      ui/src/app/common/types.constant.js
  6. 26
      ui/src/app/common/utils.service.js
  7. 17
      ui/src/app/dashboard/add-widget.controller.js
  8. 17
      ui/src/app/dashboard/edit-widget.directive.js
  9. 69
      ui/src/app/entity/entity-alias-dialog.controller.js
  10. 27
      ui/src/app/entity/entity-alias-dialog.scss
  11. 73
      ui/src/app/entity/entity-alias-dialog.tpl.html
  12. 133
      ui/src/app/entity/entity-aliases.controller.js
  13. 12
      ui/src/app/entity/entity-aliases.scss
  14. 98
      ui/src/app/entity/entity-aliases.tpl.html
  15. 100
      ui/src/app/entity/entity-filter-dialog.tpl.html
  16. 106
      ui/src/app/entity/entity-filter-view.directive.js
  17. 33
      ui/src/app/entity/entity-filter-view.scss
  18. 24
      ui/src/app/entity/entity-filter-view.tpl.html
  19. 100
      ui/src/app/entity/entity-filter.directive.js
  20. 19
      ui/src/app/entity/entity-filter.scss
  21. 104
      ui/src/app/entity/entity-filter.tpl.html
  22. 2
      ui/src/app/entity/entity-type-select.directive.js
  23. 4
      ui/src/app/entity/index.js
  24. 4
      ui/src/app/entity/relation/relation-table.directive.js
  25. 5
      ui/src/app/import-export/import-export.service.js
  26. 55
      ui/src/app/locale/locale.constant.js

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

@ -95,8 +95,7 @@ export default class AliasController {
this.entityService.resolveAlias(entityAlias, this.stateController.getStateParams()).then(
function success(aliasInfo) {
aliasCtrl.resolvedAliases[aliasId] = aliasInfo;
if (entityAlias.filter.stateEntity) {
aliasInfo.stateEntity = true;
if (aliasInfo.stateEntity) {
aliasCtrl.resolvedAliasesToStateEntities[aliasId] =
aliasCtrl.stateController.getStateParams().entityId;
}

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

@ -31,6 +31,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
resolveAliasFilter: resolveAliasFilter,
checkEntityAlias: checkEntityAlias,
filterAliasByEntityTypes: filterAliasByEntityTypes,
getAliasFilterTypesByEntityTypes: getAliasFilterTypesByEntityTypes,
getEntityKeys: getEntityKeys,
createDatasourcesFromSubscriptionsInfo: createDatasourcesFromSubscriptionsInfo,
getRelatedEntities: getRelatedEntities,
@ -223,17 +224,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() {
@ -243,6 +248,34 @@ 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;
}
@ -261,11 +294,12 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
function resolveAlias(entityAlias, stateParams) {
var deferred = $q.defer();
var filter = entityAlias.filter;
resolveAliasFilter(filter, stateParams, 100).then(
resolveAliasFilter(filter, stateParams, -1).then(
function (result) {
var entities = result.entities;
var aliasInfo = {
alias: entityAlias.alias,
stateEntity: result.stateEntity,
resolveMultiple: filter.resolveMultiple
};
var resolvedEntities = entitiesToEntitiesInfo(entities);
@ -291,39 +325,68 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
};
switch (filter.type) {
case types.aliasFilterType.entityList.value:
if (filter.stateEntity) {
result.stateEntity = true;
if (stateParams && stateParams.entityId) {
getEntity(stateParams.entityId.entityType, stateParams.entityId.id).then(
function success(entity) {
result.entities = [entity];
deferred.resolve(result);
},
function fail() {
deferred.reject();
}
);
} else {
deferred.resolve(result);
getEntities(filter.entityType, filter.entityList).then(
function success(entities) {
if (entities && entities.length) {
result.entities = entities;
deferred.resolve(result);
} else {
deferred.reject();
}
},
function fail() {
deferred.reject();
}
} else {
getEntities(filter.entityType, filter.entityList).then(
function success(entities) {
if (entities && entities.length) {
result.entities = entities;
deferred.resolve(result);
} else {
deferred.reject();
}
);
break;
case types.aliasFilterType.entityName.value:
getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems).then(
function success(entities) {
if (entities && entities.length) {
result.entities = entities;
deferred.resolve(result);
} else {
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 = [entity];
deferred.resolve(result);
},
function fail() {
deferred.reject();
}
);
} else {
deferred.resolve(result);
}
break;
case types.aliasFilterType.entityName.value:
getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems).then(
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 = entities;
deferred.resolve(result);
} else {
deferred.reject();
}
},
function fail() {
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 = entities;
@ -337,27 +400,81 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
}
);
break;
//TODO:
//TODO: Alias filter
}
return deferred.promise;
}
function filterAliasByEntityTypes(entityAlias, entityTypes) {
var filter = entityAlias.filter;
switch (filter.type) {
case types.aliasFilterType.entityList.value:
if (filter.stateEntity) {
return true;
} else {
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;
}
}
//TODO: Alias filter
return false;
}
function filterAliasFilterTypeByEntityType(aliasFilterType, entityType) {
switch (aliasFilterType) {
case types.aliasFilterType.entityList.value:
return true;
case types.aliasFilterType.entityName.value:
return entityTypes.indexOf(filter.entityType) > -1 ? true : false;
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;
}
//TODO:
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;
}
}
return result;
}
function checkEntityAlias(entityAlias) {
var deferred = $q.defer();
resolveAliasFilter(entityAlias.filter, null, 1).then(
@ -380,50 +497,6 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
return deferred.promise;
}
/*function processEntityAlias(index, aliasIds, entityAliases, stateParams, resolution, deferred) {
if (index < aliasIds.length) {
var aliasId = aliasIds[index];
var entityAlias = entityAliases[aliasId];
var alias = entityAlias.alias;
var filter = entityAlias.filter;
resolveAliasFilter(filter, stateParams).then(
function (entities) {
if (entities && entities.length) {
var entity = entities[0];
var resolvedAlias = {alias: alias, entityType: entity.id.entityType, entityId: entity.id.id};
resolution.aliasesInfo.entityAliases[aliasId] = resolvedAlias;
resolution.aliasesInfo.entityAliasesInfo[aliasId] = entitiesToEntitiesInfo(entities);
index++;
processEntityAlias(index, aliasIds, entityAliases, stateParams, resolution, deferred);
} else {
if (!resolution.error) {
resolution.error = 'dashboard.invalid-aliases-config';
}
index++;
processEntityAlias(index, aliasIds, entityAliases, stateParams, resolution, deferred);
}
}
);
} else {
deferred.resolve(resolution);
}
}*/
/*function processEntityAliases(entityAliases, stateParams) {
var deferred = $q.defer();
var resolution = {
aliasesInfo: {}
};
var aliasIds = [];
if (entityAliases) {
for (var aliasId in entityAliases) {
aliasIds.push(aliasId);
}
}
processEntityAlias(0, aliasIds, entityAliases, stateParams, resolution, deferred);
return deferred.promise;
}*/
function getEntityKeys(entityType, entityId, query, type) {
var deferred = $q.defer();
var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/keys/';
@ -889,4 +962,4 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
}
}
}
}

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

@ -258,17 +258,17 @@ export default class Subscription {
} else {
subscription.rpcEnabled = subscription.ctx.$scope.widgetEditMode ? true : false;
}
subscription.callbacks.rpcStateChanged(this);
subscription.callbacks.rpcStateChanged(subscription);
deferred.resolve();
} else {
subscription.rpcEnabled = false;
subscription.callbacks.rpcStateChanged(this);
subscription.callbacks.rpcStateChanged(subscription);
deferred.resolve();
}
},
function fail () {
subscription.rpcEnabled = false;
subscription.callbacks.rpcStateChanged(this);
subscription.callbacks.rpcStateChanged(subscription);
deferred.resolve();
}
);

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

@ -110,14 +110,12 @@ function DashboardUtils(types, utils, timeService) {
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;
entityAlias.filter.stateEntity = false;
} else {
entityAlias.filter.entityNameFilter = deviceAlias.deviceFilter.deviceNameFilter;
}
} else {
entityAlias.filter.type = types.aliasFilterType.entityList.value;
entityAlias.filter.entityList = [deviceAlias.deviceId];
entityAlias.filter.stateEntity = false;
}
return entityAlias;
}
@ -132,7 +130,6 @@ function DashboardUtils(types, utils, timeService) {
}
if (entityAlias.filter.type == types.aliasFilterType.entityList.value) {
entityAlias.filter.entityList = entityAlias.entityFilter.entityList;
entityAlias.filter.stateEntity = false;
} else {
entityAlias.filter.entityNameFilter = entityAlias.entityFilter.entityNameFilter;
}
@ -342,7 +339,6 @@ function DashboardUtils(types, utils, timeService) {
function createSingleEntityFilter(entityType, entityId) {
return {
type: types.aliasFilterType.entityList.value,
stateEntity: false,
entityList: [entityId],
entityType: entityType,
resolveMultiple: false

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

@ -74,6 +74,10 @@ export default angular.module('thingsboard.types', [])
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'
@ -139,6 +143,53 @@ export default angular.module('thingsboard.types', [])
dashboard: "DASHBOARD",
alarm: "ALARM"
},
entityTypeTranslations: {
"DEVICE": {
type: 'entity.type-device',
list: 'entity.list-of-devices',
nameStartsWith: 'entity.device-name-starts-with'
},
"ASSET": {
type: 'entity.type-asset',
list: 'entity.list-of-assets',
nameStartsWith: 'entity.asset-name-starts-with'
},
"RULE": {
type: 'entity.type-rule',
list: 'entity.list-of-rules',
nameStartsWith: 'entity.rule-name-starts-with'
},
"PLUGIN": {
type: 'entity.type-plugin',
list: 'entity.list-of-plugins',
nameStartsWith: 'entity.plugin-name-starts-with'
},
"TENANT": {
type: 'entity.type-tenant',
list: 'entity.list-of-tenants',
nameStartsWith: 'entity.tenant-name-starts-with'
},
"CUSTOMER": {
type: 'entity.type-customer',
list: 'entity.list-of-customers',
nameStartsWith: 'entity.customer-name-starts-with'
},
"USER": {
type: 'entity.type-user',
list: 'entity.list-of-users',
nameStartsWith: 'entity.user-name-starts-with'
},
"DASHBOARD": {
type: 'entity.type-dashboard',
list: 'entity.list-of-dashboards',
nameStartsWith: 'entity.dashboard-name-starts-with'
},
"ALARM": {
type: 'entity.type-alarm',
list: 'entity.list-of-alarms',
nameStartsWith: 'entity.alarm-name-starts-with'
}
},
entitySearchDirection: {
from: "FROM",
to: "TO"

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

@ -109,8 +109,7 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
cleanCopy: cleanCopy,
isLocalUrl: isLocalUrl,
validateDatasources: validateDatasources,
createKey: createKey,
entityTypeName: entityTypeName
createKey: createKey
}
return service;
@ -358,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';
}
}
}

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

@ -15,7 +15,7 @@
*/
/* eslint-disable import/no-unresolved, import/default */
import entityAliasesTemplate from '../entity/entity-aliases.tpl.html';
import entityAliasDialogTemplate from '../entity/entity-alias-dialog.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
@ -130,17 +130,14 @@ export default function AddWidgetController($scope, widgetService, entityService
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,

17
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/entity-alias-dialog.tpl.html';
import editWidgetTemplate from './edit-widget.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
@ -98,17 +98,14 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
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,

69
ui/src/app/entity/entity-filter-dialog.controller.js → ui/src/app/entity/entity-alias-dialog.controller.js

@ -14,22 +14,48 @@
* limitations under the License.
*/
import './entity-alias-dialog.scss';
/*@ngInject*/
export default function EntityFilterDialogController($scope, $mdDialog, $q, entityService, types, isAdd, allowedEntityTypes, filter) {
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;
vm.filter = filter;
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.filter.type', function (newType, prevType) {
if (newType && newType != prevType) {
updateFilter();
$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);
}
});
@ -39,32 +65,13 @@ export default function EntityFilterDialogController($scope, $mdDialog, $q, enti
}
});
function updateFilter() {
var filter = {};
filter.type = vm.filter.type;
filter.resolveMultiple = vm.filter.resolveMultiple;
switch (filter.type) {
case types.aliasFilterType.entityList.value:
filter.entityType = null;
filter.entityList = [];
filter.stateEntity = false;
break;
case types.aliasFilterType.entityName.value:
filter.entityType = null;
filter.entityNameFilter = '';
break;
//TODO:
}
vm.filter = filter;
}
function validate() {
var deferred = $q.defer();
var validationResult = {
entity: null,
stateEntity: false
}
entityService.resolveAliasFilter(vm.filter, null, 1).then(
entityService.resolveAliasFilter(vm.alias.filter, null, 1).then(
function success(result) {
validationResult.stateEntity = result.stateEntity;
var entities = result.entities;
@ -87,12 +94,11 @@ export default function EntityFilterDialogController($scope, $mdDialog, $q, enti
function save() {
$scope.theForm.$setPristine();
validate().then(
function success(validationResult) {
$mdDialog.hide({
filter: vm.filter,
entity: validationResult.entity,
stateEntity: validationResult.stateEntity
});
function success() {
if (vm.isAdd) {
vm.alias.id = utils.guid();
}
$mdDialog.hide(vm.alias);
},
function fail() {
$scope.theForm.$setValidity('entityFilter', false);
@ -101,4 +107,3 @@ export default function EntityFilterDialogController($scope, $mdDialog, $q, enti
}
}

27
ui/src/app/entity/entity-alias-dialog.scss

@ -0,0 +1,27 @@
/**
* 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-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/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>

133
ui/src/app/entity/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,42 +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, filter: entityAlias.filter, 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.filter || entityAlias.filter == null) {
entityAlias.filter = {};
}
function addAlias($event) {
openAliasDialog($event);
}
function onFilterEntityChanged(entity, stateEntity, entityAlias) {
if (entityAlias) {
if (!entityAlias.alias || entityAlias.alias.length == 0) {
entityAlias.changed = false;
}
if (!entityAlias.changed && entityAlias.filter && entityAlias.filter.type) {
if (stateEntity) {
entityAlias.alias = $translate.instant('alias.state-entity');
} else {
entityAlias.alias = entity.name;
}
}
}
function editAlias($event, entityAlias) {
openAliasDialog($event, entityAlias);
}
function addAlias() {
var entityAlias = {id: utils.guid(), alias: '', filter: {}, changed: false};
vm.entityAliases.push(entityAlias);
function openAliasDialog($event, entityAlias) {
var isAdd = entityAlias ? false : true;
var aliasIndex;
if (!isAdd) {
aliasIndex = vm.entityAliases.indexOf(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) {
@ -157,43 +181,30 @@ export default function EntityAliasesController(utils, entityService, toast, $sc
var uniqueAliasList = {};
var valid = true;
var aliasId;
var alias;
var i;
if (vm.isSingleEntityAlias) {
if (!vm.singleEntityAlias.id) {
vm.singleEntityAlias.id = utils.guid();
}
for (i = 0; i < vm.entityAliases.length; i ++) {
alias = vm.entityAliases[i].alias;
if (alias === vm.singleEntityAlias.alias) {
valid = false;
break;
}
}
} 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] = {id: aliasId, alias: alias, filter: vm.entityAliases[i].filter};
} 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);
}
}

12
ui/src/app/entity/entity-aliases.scss

@ -17,6 +17,7 @@
.tb-aliases-dialog {
.md-dialog-content {
padding-bottom: 0px;
padding-top: 0px;
}
.tb-aliases-header {
min-height: 40px;
@ -33,5 +34,16 @@
md-input-container {
margin: 0px;
}
.tb-resolve-multiple-switch {
padding-left: 10px;
.resolve-multiple-switch {
margin: 0;
}
}
.md-button {
&.md-icon-button {
margin: 0px;
}
}
}
}

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

@ -19,7 +19,7 @@
<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>
<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>
@ -28,66 +28,72 @@
</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" ng-show="!vm.isSingleEntityAlias" flex layout="row" layout-align="start center">
<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;">entity.alias</span>
<div flex="80" layout="row" layout-align="start center" style="min-width: 240px; padding-left: 10px;">
<span class="tb-header-label" translate flex="70">alias.entity-filter</span>
<span class="tb-header-label" translate flex="30" style="padding-left: 10px;" >alias.resolve-multiple</span>
</div>
<span style="min-width: 40px; margin: 0 6px;"></span>
<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 ng-show="!vm.isSingleEntityAlias"></md-divider>
<md-divider></md-divider>
<md-dialog-content>
<div class="md-dialog-content">
<fieldset ng-disabled="loading">
<div ng-show="vm.isSingleEntityAlias" layout="row">
<tb-entity-filter flex
allowed-entity-types="vm.allowedEntityTypes"
ng-model="vm.singleEntityAlias.filter">
</tb-entity-filter>
</div>
<div ng-show="!vm.isSingleEntityAlias" style="height: 100%; 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: 150px;" 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>
<tb-entity-filter flex="80" style="min-width: 240px; padding-left: 10px;"
hide-labels
allowed-entity-types="vm.allowedEntityTypes"
ng-model="entityAlias.filter"
on-matching-entity-change="vm.onFilterEntityChanged(entity, stateEntity, entityAlias)">
</tb-entity-filter>
<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 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.isSingleEntityAlias && !vm.disableAdd" ng-disabled="loading" class="md-primary md-raised"
<md-button ng-show="!vm.disableAdd" ng-disabled="loading" class="md-primary md-raised"
ng-click="vm.addAlias($event)"
aria-label="{{ 'action.add' | translate }}">
aria-label="{{ 'alias.add' | translate }}">
<md-tooltip md-direction="top">
{{ 'entity.add-alias' | translate }}
{{ 'alias.add' | translate }}
</md-tooltip>
<span translate>action.add</span>
<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">

100
ui/src/app/entity/entity-filter-dialog.tpl.html

@ -1,100 +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-entity-filter-dialog" style="width: 600px;" aria-label="{{ 'alias.entity-filter' | translate }}">
<form name="theForm" ng-submit="vm.save()">
<md-toolbar>
<div class="md-toolbar-tools">
<h2>{{ (vm.isAdd ? 'alias.create-entity-filter' : 'alias.edit-entity-filter') | 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">
<md-input-container>
<label>{{ 'alias.filter-type' | translate }}</label>
<md-select required name="filterType"
ng-model="vm.filter.type" aria-label="{{ 'alias.filter-type' | translate }}">
<md-option ng-repeat="type in vm.types.aliasFilterType" 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="vm.filter.type == vm.types.aliasFilterType.entityList.value" id="entityListFilter">
<md-checkbox flex aria-label="{{ 'alias.use-state-entity' | translate }}"
ng-model="vm.filter.stateEntity">{{ 'alias.use-state-entity' | translate }}
</md-checkbox>
<tb-entity-type-select
ng-if="!vm.filter.stateEntity"
ng-model="vm.filter.entityType"
the-form="theForm"
ng-disabled="vm.filter.stateEntity"
tb-required="!vm.filter.stateEntity"
allowed-entity-types="vm.allowedEntityTypes">
</tb-entity-type-select>
<tb-entity-list
ng-if="!vm.filter.stateEntity"
ng-model="vm.filter.entityList"
ng-disabled="vm.filter.stateEntity"
tb-required="!vm.filter.stateEntity"
entity-type="vm.filter.entityType">
</tb-entity-list>
</section>
<section flex layout="column" ng-if="vm.filter.type == vm.types.aliasFilterType.entityName.value" id="entityNameFilter">
<tb-entity-type-select
ng-model="vm.filter.entityType"
the-form="theForm"
tb-required="true"
allowed-entity-types="vm.allowedEntityTypes">
</tb-entity-type-select>
<md-input-container flex>
<label translate>entity.name-starts-with</label>
<input required name="entityNameFilter"
ng-model="vm.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>
<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">
{{ '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>

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

@ -0,0 +1,106 @@
/*
* 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;
//TODO: Alias filter
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
};
}

33
ui/src/app/entity/entity-filter-view.scss

@ -0,0 +1,33 @@
/**
* 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-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>

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

@ -17,16 +17,13 @@
/* eslint-disable import/no-unresolved, import/default */
import entityFilterTemplate from './entity-filter.tpl.html';
import entityFilterDialogTemplate from './entity-filter-dialog.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
import EntityFilterDialogController from './entity-filter-dialog.controller';
import './entity-filter.scss';
/*@ngInject*/
export default function EntityFilterDirective($compile, $templateCache, $q, $document, $mdDialog, types) {
export default function EntityFilterDirective($compile, $templateCache, $q, $document, $mdDialog, types, entityService) {
var linker = function (scope, element, attrs, ngModelCtrl) {
@ -35,66 +32,59 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
scope.ngModelCtrl = ngModelCtrl;
scope.types = types;
scope.hideLabels = angular.isDefined(attrs.hideLabels);
scope.aliasFilterTypes = entityService.getAliasFilterTypesByEntityTypes(scope.allowedEntityTypes);
scope.updateValidity = function() {
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
ngModelCtrl.$setValidity('filter', value.type ? true : false);
scope.$watch('filter.type', function (newType, prevType) {
if (newType && newType != prevType) {
updateFilter();
}
}
});
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
scope.model = angular.copy(ngModelCtrl.$viewValue);
} else {
scope.model = {
type: null,
resolveMultiple: false
}
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;
//TODO: Alias filter
}
scope.filter = filter;
}
scope.$watch('model.resolveMultiple', function () {
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
value.resolveMultiple = scope.model.resolveMultiple;
ngModelCtrl.$setViewValue(value);
scope.updateValidity();
}
scope.$watch('filter', function () {
scope.updateView();
});
scope.editFilter = function($event) {
openEntityFilterDialog($event, false);
scope.updateView = function() {
ngModelCtrl.$setViewValue(scope.filter);
}
scope.createFilter = function($event) {
openEntityFilterDialog($event, true);
}
function openEntityFilterDialog($event, isAdd) {
$mdDialog.show({
controller: EntityFilterDialogController,
controllerAs: 'vm',
templateUrl: entityFilterDialogTemplate,
locals: {
isAdd: isAdd,
allowedEntityTypes: scope.allowedEntityTypes,
filter: angular.copy(scope.model)
},
parent: angular.element($document[0].body),
fullscreen: true,
skipHide: true,
targetEvent: $event
}).then(function (result) {
scope.model = result.filter;
ngModelCtrl.$setViewValue(result.filter);
scope.updateValidity();
if (scope.onMatchingEntityChange) {
scope.onMatchingEntityChange({entity: result.entity, stateEntity: result.stateEntity});
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
scope.filter = ngModelCtrl.$viewValue;
} else {
scope.filter = {
type: null,
resolveMultiple: false
}
}, function () {
});
}
}
$compile(element.contents())(scope);
@ -106,8 +96,8 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
require: "^ngModel",
link: linker,
scope: {
allowedEntityTypes: '=?',
onMatchingEntityChange: '&'
theForm: '=',
allowedEntityTypes: '=?'
}
};

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

@ -13,22 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-entity-filter {
.tb-filter-switch {
padding-left: 10px;
.filter-switch {
margin: 0;
}
.filter-label {
margin: 5px 0;
}
}
.tb-error-messages {
margin-top: -11px;
height: 35px;
.tb-error-message {
padding-left: 8px;
padding-top: 14px;
}
}
}

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

@ -15,37 +15,77 @@
limitations under the License.
-->
<section layout='row' class="tb-entity-filter">
<section layout="row" flex="70">
<section flex layout="column" layout-align="center start">
<div ng-if="model.type">{{ types.aliasFilterType[model.type].name | translate }}</div>
<md-button ng-if="!model.type"
ng-disabled="loading" class="md-primary"
ng-click="createFilter($event)"
aria-label="{{ 'alias.create-entity-filter' | translate }}">
<md-icon aria-label="{{ 'alias.create-entity-filter' | translate }}"
class="material-icons">
add
</md-icon>
{{ 'alias.create-entity-filter' | translate }}
</md-button>
</section>
<md-button ng-if="model.type" ng-disabled="loading" class="md-icon-button md-primary"
style="min-width: 40px;"
ng-click="editFilter($event)"
aria-label="{{ 'alias.edit-entity-filter' | translate }}">
<md-tooltip md-direction="top">
{{ 'alias.edit-entity-filter' | translate }}
</md-tooltip>
<md-icon aria-label="{{ 'alias.edit-entity-filter' | translate }}"
class="material-icons">
edit
</md-icon>
</md-button>
<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 class="tb-filter-switch" layout="column" flex="30" layout-align="center center">
<label ng-if="!hideLabels" class="tb-small filter-label" translate>alias.resolve-multiple</label>
<md-switch class="filter-switch" ng-model="model.resolveMultiple" aria-label="resolve-multiple-switcher">
</md-switch>
<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>
<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>
</div>

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

@ -71,7 +71,7 @@ export default function EntityTypeSelect($compile, $templateCache, utils, userSe
}
scope.typeName = function(type) {
return utils.entityTypeName(type);
return type ? types.entityTypeTranslations[type].type : '';
}
scope.updateValidity = function () {

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

@ -15,6 +15,7 @@
*/
import EntityAliasesController from './entity-aliases.controller';
import EntityAliasDialogController from './entity-alias-dialog.controller';
import EntityTypeSelectDirective from './entity-type-select.directive';
import EntitySubtypeSelectDirective from './entity-subtype-select.directive';
import EntitySubtypeAutocompleteDirective from './entity-subtype-autocomplete.directive';
@ -22,6 +23,7 @@ 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 EntityFilterViewDirective from './entity-filter-view.directive';
import AliasesEntitySelectPanelController from './aliases-entity-select-panel.controller';
import AliasesEntitySelectDirective from './aliases-entity-select.directive';
import AddAttributeDialogController from './attribute/add-attribute-dialog.controller';
@ -32,6 +34,7 @@ import RelationTypeAutocompleteDirective from './relation/relation-type-autocomp
export default angular.module('thingsboard.entity', [])
.controller('EntityAliasesController', EntityAliasesController)
.controller('EntityAliasDialogController', EntityAliasDialogController)
.controller('AliasesEntitySelectPanelController', AliasesEntitySelectPanelController)
.controller('AddAttributeDialogController', AddAttributeDialogController)
.controller('AddWidgetToDashboardDialogController', AddWidgetToDashboardDialogController)
@ -42,6 +45,7 @@ export default angular.module('thingsboard.entity', [])
.directive('tbEntityList', EntityListDirective)
.directive('tbEntitySelect', EntitySelectDirective)
.directive('tbEntityFilter', EntityFilterDirective)
.directive('tbEntityFilterView', EntityFilterViewDirective)
.directive('tbAliasesEntitySelect', AliasesEntitySelectDirective)
.directive('tbAttributeTable', AttributeTableDirective)
.directive('tbRelationTable', RelationTableDirective)

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;

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

@ -365,7 +365,6 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
alias = aliasInfo.aliasName;
filter = {
type: types.aliasFilterType.entityList.value,
stateEntity: false,
entityType: types.entityType.device,
entityList: [aliasInfo.deviceId],
resolveMultiple: false
@ -378,7 +377,6 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
resolveMultiple: false
}
if (filter.type == types.aliasFilterType.entityList.value) {
filter.stateEntity = false;
filter.entityList = aliasInfo.deviceFilter.deviceList
} else {
filter.entityNameFilter = aliasInfo.deviceFilter.deviceNameFilter;
@ -391,7 +389,6 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
resolveMultiple: false
}
if (filter.type == types.aliasFilterType.entityList.value) {
filter.stateEntity = false;
filter.entityList = aliasInfo.entityFilter.entityList;
} else {
filter.entityNameFilter = aliasInfo.entityFilter.entityNameFilter;
@ -662,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
}

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

@ -113,23 +113,30 @@ export default angular.module('thingsboard.locale', [])
"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-asset-search-query": "Asset search query",
"filter-type-device-search-query": "Device search query",
"entity-filter": "Entity filter",
"create-entity-filter": "Create entity filter",
"edit-entity-filter": "Edit entity filter",
"entity-filter-required": "Entity filter is required.",
"resolve-multiple": "Resolve as multiple entities",
"filter-type": "Filter type",
"filter-type-required": "Filter type is required.",
"use-state-entity": "Use state entity",
"state-entity": "State entity",
"entity-filter-no-entity-matched": "No entities matching specified filter were found.",
"no-entity-filter-specified": "No entity filter specified"
},
"asset": {
"asset": "Asset",
@ -185,7 +192,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",
@ -463,7 +471,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.",
@ -549,6 +557,7 @@ 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.",
@ -562,24 +571,42 @@ export default angular.module('thingsboard.locale', [])
"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",
"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",
"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",
"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",
"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",
"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",
"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",
"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",
"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!"
"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",

Loading…
Cancel
Save