From ecb5c50d61add5a92c33ee91afef5244109e8480 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 30 May 2017 10:53:50 +0300 Subject: [PATCH] UI: Implement Entity relations management. --- .../controller/EntityRelationController.java | 46 +++++--- .../data/relation/EntityRelationInfo.java | 9 ++ .../dao/relation/BaseRelationService.java | 65 ++++++----- .../server/dao/relation/RelationService.java | 2 + ui/src/app/api/entity-relation.service.js | 13 +++ ui/src/app/customer/customers.tpl.html | 6 + ui/src/app/device/devices.tpl.html | 6 + ui/src/app/entity/entity-filter.tpl.html | 2 +- ui/src/app/entity/entity-select.tpl.html | 3 + .../entity/entity-type-select.directive.js | 5 + ui/src/app/entity/entity-type-select.tpl.html | 8 +- ui/src/app/entity/index.js | 2 + .../add-relation-dialog.controller.js | 15 ++- .../relation/add-relation-dialog.tpl.html | 17 ++- .../relation/relation-table.directive.js | 104 +++++++++++++++--- .../entity/relation/relation-table.tpl.html | 42 ++++--- .../relation-type-autocomplete.directive.js | 86 +++++++++++++++ .../relation/relation-type-autocomplete.scss | 25 +++++ .../relation-type-autocomplete.tpl.html | 40 +++++++ ui/src/app/locale/locale.constant.js | 33 ++++-- ui/src/app/plugin/plugins.tpl.html | 6 + ui/src/app/rule/rules.tpl.html | 6 + ui/src/app/tenant/tenants.tpl.html | 6 + ui/src/scss/main.scss | 1 + 24 files changed, 454 insertions(+), 94 deletions(-) create mode 100644 ui/src/app/entity/relation/relation-type-autocomplete.directive.js create mode 100644 ui/src/app/entity/relation/relation-type-autocomplete.scss create mode 100644 ui/src/app/entity/relation/relation-type-autocomplete.tpl.html diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java index 1a08d56a7f..3ddc5975a7 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java @@ -34,7 +34,7 @@ import java.util.List; @RequestMapping("/api") public class EntityRelationController extends BaseController { - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relation", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) public void saveRelation(@RequestBody EntityRelation relation) throws ThingsboardException { @@ -42,31 +42,33 @@ public class EntityRelationController extends BaseController { checkNotNull(relation); checkEntityId(relation.getFrom()); checkEntityId(relation.getTo()); + if (relation.getTypeGroup() == null) { + relation.setTypeGroup(RelationTypeGroup.COMMON); + } relationService.saveRelation(relation).get(); } catch (Exception e) { throw handleException(e); } } - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relation", method = RequestMethod.DELETE, params = {"fromId", "fromType", "relationType", "toId", "toType"}) @ResponseStatus(value = HttpStatus.OK) public void deleteRelation(@RequestParam("fromId") String strFromId, @RequestParam("fromType") String strFromType, @RequestParam("relationType") String strRelationType, - @RequestParam("relationTypeGroup") String strRelationTypeGroup, + @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup, @RequestParam("toId") String strToId, @RequestParam("toType") String strToType) throws ThingsboardException { checkParameter("fromId", strFromId); checkParameter("fromType", strFromType); checkParameter("relationType", strRelationType); - checkParameter("relationTypeGroup", strRelationTypeGroup); checkParameter("toId", strToId); checkParameter("toType", strToType); EntityId fromId = EntityIdFactory.getByTypeAndId(strFromType, strFromId); EntityId toId = EntityIdFactory.getByTypeAndId(strToType, strToId); checkEntityId(fromId); checkEntityId(toId); - RelationTypeGroup relationTypeGroup = RelationTypeGroup.valueOf(strRelationTypeGroup); + RelationTypeGroup relationTypeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); try { Boolean found = relationService.deleteRelation(fromId, toId, strRelationType, relationTypeGroup).get(); if (!found) { @@ -77,7 +79,7 @@ public class EntityRelationController extends BaseController { } } - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relations", method = RequestMethod.DELETE, params = {"id", "type"}) @ResponseStatus(value = HttpStatus.OK) public void deleteRelations(@RequestParam("entityId") String strId, @@ -93,7 +95,7 @@ public class EntityRelationController extends BaseController { } } - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relation", method = RequestMethod.GET, params = {"fromId", "fromType", "relationType", "toId", "toType"}) @ResponseStatus(value = HttpStatus.OK) public void checkRelation(@RequestParam("fromId") String strFromId, @@ -121,7 +123,7 @@ public class EntityRelationController extends BaseController { } } - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relations", method = RequestMethod.GET, params = {"fromId", "fromType"}) @ResponseBody public List findByFrom(@RequestParam("fromId") String strFromId, @@ -139,7 +141,7 @@ public class EntityRelationController extends BaseController { } } - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relations/info", method = RequestMethod.GET, params = {"fromId", "fromType"}) @ResponseBody public List findInfoByFrom(@RequestParam("fromId") String strFromId, @@ -157,7 +159,7 @@ public class EntityRelationController extends BaseController { } } - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relations", method = RequestMethod.GET, params = {"fromId", "fromType", "relationType"}) @ResponseBody public List findByFrom(@RequestParam("fromId") String strFromId, @@ -177,7 +179,7 @@ public class EntityRelationController extends BaseController { } } - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relations", method = RequestMethod.GET, params = {"toId", "toType"}) @ResponseBody public List findByTo(@RequestParam("toId") String strToId, @@ -195,7 +197,25 @@ public class EntityRelationController extends BaseController { } } - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/relations/info", method = RequestMethod.GET, params = {"toId", "toType"}) + @ResponseBody + public List findInfoByTo(@RequestParam("toId") String strToId, + @RequestParam("toType") String strToType, + @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup) throws ThingsboardException { + checkParameter("toId", strToId); + checkParameter("toType", strToType); + EntityId entityId = EntityIdFactory.getByTypeAndId(strToType, strToId); + checkEntityId(entityId); + RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); + try { + return checkNotNull(relationService.findInfoByTo(entityId, typeGroup).get()); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relations", method = RequestMethod.GET, params = {"toId", "toType", "relationType"}) @ResponseBody public List findByTo(@RequestParam("toId") String strToId, @@ -215,7 +235,7 @@ public class EntityRelationController extends BaseController { } } - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relations", method = RequestMethod.POST) @ResponseBody public List findByQuery(@RequestBody EntityRelationsQuery query) throws ThingsboardException { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationInfo.java index 709ad7937a..50126910ec 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationInfo.java @@ -20,6 +20,7 @@ public class EntityRelationInfo extends EntityRelation { private static final long serialVersionUID = 2807343097519543363L; + private String fromName; private String toName; public EntityRelationInfo() { @@ -30,6 +31,14 @@ public class EntityRelationInfo extends EntityRelation { super(entityRelation); } + public String getFromName() { + return fromName; + } + + public void setFromName(String fromName) { + this.fromName = fromName; + } + public String getToName() { return toName; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java index 9559cd3432..36ec56735b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java @@ -23,29 +23,17 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.BaseData; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.asset.Asset; -import org.thingsboard.server.common.data.id.AssetId; -import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelationInfo; import org.thingsboard.server.common.data.relation.RelationTypeGroup; -import org.thingsboard.server.dao.asset.AssetService; -import org.thingsboard.server.dao.customer.CustomerService; -import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.plugin.PluginService; -import org.thingsboard.server.dao.rule.RuleService; -import org.thingsboard.server.dao.tenant.TenantService; import javax.annotation.Nullable; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; /** * Created by ashvayka on 28.04.17. @@ -133,23 +121,16 @@ public class BaseRelationService implements RelationService { ListenableFuture> relationsInfo = Futures.transform(relations, (AsyncFunction, List>) relations1 -> { List> futures = new ArrayList<>(); - relations1.stream().forEach(relation -> futures.add(fetchRelationInfoAsync(relation))); - return Futures.successfulAsList(futures); + relations1.stream().forEach(relation -> + futures.add(fetchRelationInfoAsync(relation, + relation2 -> relation2.getTo(), + (EntityRelationInfo relationInfo, String entityName) -> relationInfo.setToName(entityName))) + ); + return Futures.successfulAsList(futures); }); return relationsInfo; } - private ListenableFuture fetchRelationInfoAsync(EntityRelation relation) { - ListenableFuture entityName = entityService.fetchEntityNameAsync(relation.getTo()); - ListenableFuture entityRelationInfo = - Futures.transform(entityName, (Function) entityName1 -> { - EntityRelationInfo entityRelationInfo1 = new EntityRelationInfo(relation); - entityRelationInfo1.setToName(entityName1); - return entityRelationInfo1; - }); - return entityRelationInfo; - } - @Override public ListenableFuture> findByFromAndType(EntityId from, String relationType, RelationTypeGroup typeGroup) { log.trace("Executing findByFromAndType [{}][{}][{}]", from, relationType, typeGroup); @@ -167,6 +148,38 @@ public class BaseRelationService implements RelationService { return relationDao.findAllByTo(to, typeGroup); } + @Override + public ListenableFuture> findInfoByTo(EntityId to, RelationTypeGroup typeGroup) { + log.trace("Executing findInfoByTo [{}][{}]", to, typeGroup); + validate(to); + validateTypeGroup(typeGroup); + ListenableFuture> relations = relationDao.findAllByTo(to, typeGroup); + ListenableFuture> relationsInfo = Futures.transform(relations, + (AsyncFunction, List>) relations1 -> { + List> futures = new ArrayList<>(); + relations1.stream().forEach(relation -> + futures.add(fetchRelationInfoAsync(relation, + relation2 -> relation2.getFrom(), + (EntityRelationInfo relationInfo, String entityName) -> relationInfo.setFromName(entityName))) + ); + return Futures.successfulAsList(futures); + }); + return relationsInfo; + } + + private ListenableFuture fetchRelationInfoAsync(EntityRelation relation, + Function entityIdGetter, + BiConsumer entityNameSetter) { + ListenableFuture entityName = entityService.fetchEntityNameAsync(entityIdGetter.apply(relation)); + ListenableFuture entityRelationInfo = + Futures.transform(entityName, (Function) entityName1 -> { + EntityRelationInfo entityRelationInfo1 = new EntityRelationInfo(relation); + entityNameSetter.accept(entityRelationInfo1, entityName1); + return entityRelationInfo1; + }); + return entityRelationInfo; + } + @Override public ListenableFuture> findByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup) { log.trace("Executing findByToAndType [{}][{}][{}]", to, relationType, typeGroup); diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java index 868769ffe8..a810454f2a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java @@ -46,6 +46,8 @@ public interface RelationService { ListenableFuture> findByTo(EntityId to, RelationTypeGroup typeGroup); + ListenableFuture> findInfoByTo(EntityId to, RelationTypeGroup typeGroup); + ListenableFuture> findByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup); ListenableFuture> findByQuery(EntityRelationsQuery query); diff --git a/ui/src/app/api/entity-relation.service.js b/ui/src/app/api/entity-relation.service.js index 703964594b..875b2fac38 100644 --- a/ui/src/app/api/entity-relation.service.js +++ b/ui/src/app/api/entity-relation.service.js @@ -28,6 +28,7 @@ function EntityRelationService($http, $q) { findInfoByFrom: findInfoByFrom, findByFromAndType: findByFromAndType, findByTo: findByTo, + findInfoByTo: findInfoByTo, findByToAndType: findByToAndType, findByQuery: findByQuery } @@ -122,6 +123,18 @@ function EntityRelationService($http, $q) { return deferred.promise; } + function findInfoByTo(toId, toType) { + var deferred = $q.defer(); + var url = '/api/relations/info?toId=' + toId; + url += '&toType=' + toType; + $http.get(url, null).then(function success(response) { + deferred.resolve(response.data); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + function findByToAndType(toId, toType, relationType) { var deferred = $q.defer(); var url = '/api/relations?toId=' + toId; diff --git a/ui/src/app/customer/customers.tpl.html b/ui/src/app/customer/customers.tpl.html index 92219844ee..6c70a70dad 100644 --- a/ui/src/app/customer/customers.tpl.html +++ b/ui/src/app/customer/customers.tpl.html @@ -55,5 +55,11 @@ default-event-type="{{vm.types.eventType.alarm.value}}"> + + + + diff --git a/ui/src/app/device/devices.tpl.html b/ui/src/app/device/devices.tpl.html index 2f90db814e..3ff6c1ce39 100644 --- a/ui/src/app/device/devices.tpl.html +++ b/ui/src/app/device/devices.tpl.html @@ -56,4 +56,10 @@ default-event-type="{{vm.types.eventType.alarm.value}}"> + + + + diff --git a/ui/src/app/entity/entity-filter.tpl.html b/ui/src/app/entity/entity-filter.tpl.html index cbb997e40b..7e49ba943f 100644 --- a/ui/src/app/entity/entity-filter.tpl.html +++ b/ui/src/app/entity/entity-filter.tpl.html @@ -41,7 +41,7 @@ - {{itemName($chip)}} + {{$chip.name}} diff --git a/ui/src/app/entity/entity-select.tpl.html b/ui/src/app/entity/entity-select.tpl.html index ce0a16b483..13e17e7af1 100644 --- a/ui/src/app/entity/entity-select.tpl.html +++ b/ui/src/app/entity/entity-select.tpl.html @@ -17,6 +17,9 @@ -->
- + {{typeName(type) | translate}} - \ No newline at end of file +
+
entity.type-required
+
+ diff --git a/ui/src/app/entity/index.js b/ui/src/app/entity/index.js index bed956208e..e8cc43797d 100644 --- a/ui/src/app/entity/index.js +++ b/ui/src/app/entity/index.js @@ -27,6 +27,7 @@ import AddAttributeDialogController from './attribute/add-attribute-dialog.contr import AddWidgetToDashboardDialogController from './attribute/add-widget-to-dashboard-dialog.controller'; import AttributeTableDirective from './attribute/attribute-table.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) @@ -42,4 +43,5 @@ export default angular.module('thingsboard.entity', []) .directive('tbAliasesEntitySelect', AliasesEntitySelectDirective) .directive('tbAttributeTable', AttributeTableDirective) .directive('tbRelationTable', RelationTableDirective) + .directive('tbRelationTypeAutocomplete', RelationTypeAutocompleteDirective) .name; diff --git a/ui/src/app/entity/relation/add-relation-dialog.controller.js b/ui/src/app/entity/relation/add-relation-dialog.controller.js index c331946f7f..e05be526c7 100644 --- a/ui/src/app/entity/relation/add-relation-dialog.controller.js +++ b/ui/src/app/entity/relation/add-relation-dialog.controller.js @@ -14,14 +14,20 @@ * limitations under the License. */ /*@ngInject*/ -export default function AddRelationDialogController($scope, $mdDialog, types, entityRelationService, from) { +export default function AddRelationDialogController($scope, $mdDialog, types, entityRelationService, direction, entityId) { var vm = this; vm.types = types; + vm.direction = direction; + vm.targetEntityId = {}; vm.relation = {}; - vm.relation.from = from; + if (vm.direction == vm.types.entitySearchDirection.from) { + vm.relation.from = entityId; + } else { + vm.relation.to = entityId; + } vm.relation.type = types.entityRelationType.contains; vm.add = add; @@ -32,6 +38,11 @@ export default function AddRelationDialogController($scope, $mdDialog, types, en } function add() { + if (vm.direction == vm.types.entitySearchDirection.from) { + vm.relation.to = vm.targetEntityId; + } else { + vm.relation.from = vm.targetEntityId; + } $scope.theForm.$setPristine(); entityRelationService.saveRelation(vm.relation).then( function success() { diff --git a/ui/src/app/entity/relation/add-relation-dialog.tpl.html b/ui/src/app/entity/relation/add-relation-dialog.tpl.html index b32d23a249..b45013bded 100644 --- a/ui/src/app/entity/relation/add-relation-dialog.tpl.html +++ b/ui/src/app/entity/relation/add-relation-dialog.tpl.html @@ -32,19 +32,16 @@
- - - - - {{('relation.relation-types.' + type) | translate}} - - - - {{'entity.entity' | translate }} + + + {{(vm.direction == vm.types.entitySearchDirection.from ? + 'relation.to-entity' : 'relation.from-entity') | translate}} + ng-model="vm.targetEntityId">
diff --git a/ui/src/app/entity/relation/relation-table.directive.js b/ui/src/app/entity/relation/relation-table.directive.js index bf4aa52ebe..729ff36bc6 100644 --- a/ui/src/app/entity/relation/relation-table.directive.js +++ b/ui/src/app/entity/relation/relation-table.directive.js @@ -45,13 +45,17 @@ function RelationTableController($scope, $q, $mdDialog, $document, $translate, $ let vm = this; + vm.types = types; + + vm.direction = vm.types.entitySearchDirection.from; + vm.relations = []; vm.relationsCount = 0; vm.allRelations = []; vm.selectedRelations = []; vm.query = { - order: 'typeName', + order: 'type', limit: 5, page: 1, search: null @@ -62,19 +66,23 @@ function RelationTableController($scope, $q, $mdDialog, $document, $translate, $ vm.onReorder = onReorder; vm.onPaginate = onPaginate; vm.addRelation = addRelation; - vm.editRelation = editRelation; vm.deleteRelation = deleteRelation; vm.deleteRelations = deleteRelations; vm.reloadRelations = reloadRelations; vm.updateRelations = updateRelations; - $scope.$watch("vm.entityId", function(newVal, prevVal) { if (newVal && !angular.equals(newVal, prevVal)) { reloadRelations(); } }); + $scope.$watch("vm.direction", function(newVal, prevVal) { + if (newVal && !angular.equals(newVal, prevVal)) { + reloadRelations(); + } + }); + $scope.$watch("vm.query.search", function(newVal, prevVal) { if (!angular.equals(newVal, prevVal) && vm.query.search != null) { updateRelations(); @@ -102,7 +110,7 @@ function RelationTableController($scope, $q, $mdDialog, $document, $translate, $ if ($event) { $event.stopPropagation(); } - var from = { + var entityId = { id: vm.entityId, entityType: vm.entityType }; @@ -111,7 +119,7 @@ function RelationTableController($scope, $q, $mdDialog, $document, $translate, $ controllerAs: 'vm', templateUrl: addRelationTemplate, parent: angular.element($document[0].body), - locals: { from: from }, + locals: { direction: vm.direction, entityId: entityId }, fullscreen: true, targetEvent: $event }).then(function () { @@ -120,36 +128,100 @@ function RelationTableController($scope, $q, $mdDialog, $document, $translate, $ }); } - function editRelation($event, /*relation*/) { + function deleteRelation($event, relation) { if ($event) { $event.stopPropagation(); } - //TODO: - } + if (relation) { + var title; + var content; + if (vm.direction == vm.types.entitySearchDirection.from) { + title = $translate.instant('relation.delete-to-relation-title', {entityName: relation.toName}); + content = $translate.instant('relation.delete-to-relation-text', {entityName: relation.toName}); + } else { + title = $translate.instant('relation.delete-from-relation-title', {entityName: relation.fromName}); + content = $translate.instant('relation.delete-from-relation-text', {entityName: relation.fromName}); + } - function deleteRelation($event, /*relation*/) { - if ($event) { - $event.stopPropagation(); + var confirm = $mdDialog.confirm() + .targetEvent($event) + .title(title) + .htmlContent(content) + .ariaLabel(title) + .cancel($translate.instant('action.no')) + .ok($translate.instant('action.yes')); + $mdDialog.show(confirm).then(function () { + entityRelationService.deleteRelation( + relation.from.id, + relation.from.entityType, + relation.type, + relation.to.id, + relation.to.entityType).then( + function success() { + reloadRelations(); + } + ); + }); } - //TODO: } function deleteRelations($event) { if ($event) { $event.stopPropagation(); } - //TODO: + if (vm.selectedRelations && vm.selectedRelations.length > 0) { + var title; + var content; + if (vm.direction == vm.types.entitySearchDirection.from) { + title = $translate.instant('relation.delete-to-relations-title', {count: vm.selectedRelations.length}, 'messageformat'); + content = $translate.instant('relation.delete-to-relations-text'); + } else { + title = $translate.instant('relation.delete-from-relations-title', {count: vm.selectedRelations.length}, 'messageformat'); + content = $translate.instant('relation.delete-from-relations-text'); + } + var confirm = $mdDialog.confirm() + .targetEvent($event) + .title(title) + .htmlContent(content) + .ariaLabel(title) + .cancel($translate.instant('action.no')) + .ok($translate.instant('action.yes')); + $mdDialog.show(confirm).then(function () { + var tasks = []; + for (var i=0;i +
+ + + + + {{ ('relation.search-direction.' + direction) | translate}} + + + +
- relation.entity-relations + {{(vm.direction == vm.types.entitySearchDirection.from ? + 'relation.from-relations' : 'relation.to-relations') | translate}} add @@ -66,7 +77,7 @@
relation.selected-relations @@ -81,25 +92,26 @@ - - - + + + + + - - - + + + + +
relation.typerelation.to-entity-typerelation.to-entity-namerelation.typerelation.to-entity-typerelation.from-entity-typerelation.to-entity-namerelation.from-entity-name  
{{ relation.typeName }}{{ relation.toEntityTypeName }}{{ relation.toName }}{{ relation.type }}{{ relation.toEntityTypeName }}{{ relation.fromEntityTypeName }}{{ relation.toName }}{{ relation.fromName }} - - edit - - {{ 'relation.edit' | translate }} - - delete diff --git a/ui/src/app/entity/relation/relation-type-autocomplete.directive.js b/ui/src/app/entity/relation/relation-type-autocomplete.directive.js new file mode 100644 index 0000000000..a7e5ea45bb --- /dev/null +++ b/ui/src/app/entity/relation/relation-type-autocomplete.directive.js @@ -0,0 +1,86 @@ +/* + * 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-type-autocomplete.scss'; + +/* eslint-disable import/no-unresolved, import/default */ + +import relationTypeAutocompleteTemplate from './relation-type-autocomplete.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function RelationTypeAutocomplete($compile, $templateCache, $q, $filter, assetService, deviceService, types) { + + var linker = function (scope, element, attrs, ngModelCtrl) { + var template = $templateCache.get(relationTypeAutocompleteTemplate); + element.html(template); + + scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false; + scope.relationType = null; + scope.relationTypeSearchText = ''; + scope.relationTypes = []; + for (var type in types.entityRelationType) { + scope.relationTypes.push(types.entityRelationType[type]); + } + + scope.fetchRelationTypes = function(searchText) { + var deferred = $q.defer(); + var result = $filter('filter')(scope.relationTypes, {'$': searchText}); + if (result && result.length) { + deferred.resolve(result); + } else { + deferred.resolve([searchText]); + } + return deferred.promise; + } + + scope.relationTypeSearchTextChanged = function() { + } + + scope.updateView = function () { + if (!scope.disabled) { + ngModelCtrl.$setViewValue(scope.relationType); + } + } + + ngModelCtrl.$render = function () { + scope.relationType = ngModelCtrl.$viewValue; + } + + scope.$watch('relationType', function (newValue, prevValue) { + if (!angular.equals(newValue, prevValue)) { + scope.updateView(); + } + }); + + scope.$watch('disabled', function () { + scope.updateView(); + }); + + $compile(element.contents())(scope); + } + + return { + restrict: "E", + require: "^ngModel", + link: linker, + scope: { + theForm: '=?', + tbRequired: '=?', + disabled:'=ngDisabled' + } + }; +} diff --git a/ui/src/app/entity/relation/relation-type-autocomplete.scss b/ui/src/app/entity/relation/relation-type-autocomplete.scss new file mode 100644 index 0000000000..9fd0fe0259 --- /dev/null +++ b/ui/src/app/entity/relation/relation-type-autocomplete.scss @@ -0,0 +1,25 @@ +/** + * 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-type-autocomplete { + .tb-relation-type-item { + display: block; + height: 48px; + } + li { + height: auto !important; + white-space: normal !important; + } +} diff --git a/ui/src/app/entity/relation/relation-type-autocomplete.tpl.html b/ui/src/app/entity/relation/relation-type-autocomplete.tpl.html new file mode 100644 index 0000000000..f71c13418a --- /dev/null +++ b/ui/src/app/entity/relation/relation-type-autocomplete.tpl.html @@ -0,0 +1,40 @@ + + + +
+ {{item}} +
+
+
+
relation.relation-type-required
+
+
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index d25db5da32..fcde13c9a4 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -544,6 +544,7 @@ export default angular.module('thingsboard.locale', []) "entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.", "all-subtypes": "All", "type": "Type", + "type-required": "Entity type is required.", "type-device": "Device", "type-asset": "Asset", "type-rule": "Rule", @@ -718,19 +719,33 @@ export default angular.module('thingsboard.locale', []) }, "relation": { "relations": "Relations", - "entity-relations": "Entity relations", + "direction": "Direction", + "search-direction": { + "FROM": "From", + "TO": "To" + }, + "from-relations": "Outbound relations", + "to-relations": "Inbound relations", "selected-relations": "{ count, select, 1 {1 relation} other {# relations} } selected", "type": "Type", - "to-entity-type": "Entity type", - "to-entity-name": "Entity name", - "edit": "Edit relation", + "to-entity-type": "To entity type", + "to-entity-name": "To entity name", + "from-entity-type": "From entity type", + "from-entity-name": "From entity name", + "to-entity": "To entity", + "from-entity": "From entity", "delete": "Delete relation", "relation-type": "Relation type", - "relation-types": { - "Contains": "Contains", - "Manages": "Manages" - }, - "add": "Add relation" + "relation-type-required": "Relation type is required.", + "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.", + "delete-to-relations-title": "Are you sure you want to delete { count, select, 1 {1 relation} other {# relations} }?", + "delete-to-relations-text": "Be careful, after the confirmation all selected relations will be removed and corresponding entities will be unrelated from the current entity.", + "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." }, "rule": { "rule": "Rule", diff --git a/ui/src/app/plugin/plugins.tpl.html b/ui/src/app/plugin/plugins.tpl.html index bd8cf74cb5..d04ebcbea5 100644 --- a/ui/src/app/plugin/plugins.tpl.html +++ b/ui/src/app/plugin/plugins.tpl.html @@ -56,5 +56,11 @@ disabled-event-types="{{vm.types.eventType.alarm.value}}"> + + + + diff --git a/ui/src/app/rule/rules.tpl.html b/ui/src/app/rule/rules.tpl.html index bd7ea4829a..16097ccd77 100644 --- a/ui/src/app/rule/rules.tpl.html +++ b/ui/src/app/rule/rules.tpl.html @@ -56,5 +56,11 @@ disabled-event-types="{{vm.types.eventType.alarm.value}}"> + + + + diff --git a/ui/src/app/tenant/tenants.tpl.html b/ui/src/app/tenant/tenants.tpl.html index ada2a3fb7e..00350a4e02 100644 --- a/ui/src/app/tenant/tenants.tpl.html +++ b/ui/src/app/tenant/tenants.tpl.html @@ -53,5 +53,11 @@ default-event-type="{{vm.types.eventType.alarm.value}}"> + + + + diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss index c062d4ea83..dbe3543efa 100644 --- a/ui/src/scss/main.scss +++ b/ui/src/scss/main.scss @@ -436,6 +436,7 @@ md-tabs.tb-headless { ***********************/ section.tb-header-buttons { + pointer-events: none; position: absolute; right: 0px; top: 86px;