Commit 2f799501789eebcd84d9c29887fc773a54ea24ff

Authored by volodymyr-babak
2 parents 4af67822 ecb5c50d

Merge remote-tracking branch 'upstream/master' into dao-refactoring-vs

... ... @@ -34,7 +34,7 @@ import java.util.List;
34 34 @RequestMapping("/api")
35 35 public class EntityRelationController extends BaseController {
36 36
37   - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  37 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
38 38 @RequestMapping(value = "/relation", method = RequestMethod.POST)
39 39 @ResponseStatus(value = HttpStatus.OK)
40 40 public void saveRelation(@RequestBody EntityRelation relation) throws ThingsboardException {
... ... @@ -42,31 +42,33 @@ public class EntityRelationController extends BaseController {
42 42 checkNotNull(relation);
43 43 checkEntityId(relation.getFrom());
44 44 checkEntityId(relation.getTo());
  45 + if (relation.getTypeGroup() == null) {
  46 + relation.setTypeGroup(RelationTypeGroup.COMMON);
  47 + }
45 48 relationService.saveRelation(relation).get();
46 49 } catch (Exception e) {
47 50 throw handleException(e);
48 51 }
49 52 }
50 53
51   - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  54 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
52 55 @RequestMapping(value = "/relation", method = RequestMethod.DELETE, params = {"fromId", "fromType", "relationType", "toId", "toType"})
53 56 @ResponseStatus(value = HttpStatus.OK)
54 57 public void deleteRelation(@RequestParam("fromId") String strFromId,
55 58 @RequestParam("fromType") String strFromType,
56 59 @RequestParam("relationType") String strRelationType,
57   - @RequestParam("relationTypeGroup") String strRelationTypeGroup,
  60 + @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup,
58 61 @RequestParam("toId") String strToId, @RequestParam("toType") String strToType) throws ThingsboardException {
59 62 checkParameter("fromId", strFromId);
60 63 checkParameter("fromType", strFromType);
61 64 checkParameter("relationType", strRelationType);
62   - checkParameter("relationTypeGroup", strRelationTypeGroup);
63 65 checkParameter("toId", strToId);
64 66 checkParameter("toType", strToType);
65 67 EntityId fromId = EntityIdFactory.getByTypeAndId(strFromType, strFromId);
66 68 EntityId toId = EntityIdFactory.getByTypeAndId(strToType, strToId);
67 69 checkEntityId(fromId);
68 70 checkEntityId(toId);
69   - RelationTypeGroup relationTypeGroup = RelationTypeGroup.valueOf(strRelationTypeGroup);
  71 + RelationTypeGroup relationTypeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON);
70 72 try {
71 73 Boolean found = relationService.deleteRelation(fromId, toId, strRelationType, relationTypeGroup).get();
72 74 if (!found) {
... ... @@ -77,7 +79,7 @@ public class EntityRelationController extends BaseController {
77 79 }
78 80 }
79 81
80   - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  82 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN', 'CUSTOMER_USER')")
81 83 @RequestMapping(value = "/relations", method = RequestMethod.DELETE, params = {"id", "type"})
82 84 @ResponseStatus(value = HttpStatus.OK)
83 85 public void deleteRelations(@RequestParam("entityId") String strId,
... ... @@ -93,7 +95,7 @@ public class EntityRelationController extends BaseController {
93 95 }
94 96 }
95 97
96   - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  98 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
97 99 @RequestMapping(value = "/relation", method = RequestMethod.GET, params = {"fromId", "fromType", "relationType", "toId", "toType"})
98 100 @ResponseStatus(value = HttpStatus.OK)
99 101 public void checkRelation(@RequestParam("fromId") String strFromId,
... ... @@ -121,7 +123,7 @@ public class EntityRelationController extends BaseController {
121 123 }
122 124 }
123 125
124   - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  126 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
125 127 @RequestMapping(value = "/relations", method = RequestMethod.GET, params = {"fromId", "fromType"})
126 128 @ResponseBody
127 129 public List<EntityRelation> findByFrom(@RequestParam("fromId") String strFromId,
... ... @@ -139,7 +141,7 @@ public class EntityRelationController extends BaseController {
139 141 }
140 142 }
141 143
142   - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  144 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
143 145 @RequestMapping(value = "/relations/info", method = RequestMethod.GET, params = {"fromId", "fromType"})
144 146 @ResponseBody
145 147 public List<EntityRelationInfo> findInfoByFrom(@RequestParam("fromId") String strFromId,
... ... @@ -157,7 +159,7 @@ public class EntityRelationController extends BaseController {
157 159 }
158 160 }
159 161
160   - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  162 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
161 163 @RequestMapping(value = "/relations", method = RequestMethod.GET, params = {"fromId", "fromType", "relationType"})
162 164 @ResponseBody
163 165 public List<EntityRelation> findByFrom(@RequestParam("fromId") String strFromId,
... ... @@ -177,7 +179,7 @@ public class EntityRelationController extends BaseController {
177 179 }
178 180 }
179 181
180   - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  182 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
181 183 @RequestMapping(value = "/relations", method = RequestMethod.GET, params = {"toId", "toType"})
182 184 @ResponseBody
183 185 public List<EntityRelation> findByTo(@RequestParam("toId") String strToId,
... ... @@ -195,7 +197,25 @@ public class EntityRelationController extends BaseController {
195 197 }
196 198 }
197 199
198   - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  200 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
  201 + @RequestMapping(value = "/relations/info", method = RequestMethod.GET, params = {"toId", "toType"})
  202 + @ResponseBody
  203 + public List<EntityRelationInfo> findInfoByTo(@RequestParam("toId") String strToId,
  204 + @RequestParam("toType") String strToType,
  205 + @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup) throws ThingsboardException {
  206 + checkParameter("toId", strToId);
  207 + checkParameter("toType", strToType);
  208 + EntityId entityId = EntityIdFactory.getByTypeAndId(strToType, strToId);
  209 + checkEntityId(entityId);
  210 + RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON);
  211 + try {
  212 + return checkNotNull(relationService.findInfoByTo(entityId, typeGroup).get());
  213 + } catch (Exception e) {
  214 + throw handleException(e);
  215 + }
  216 + }
  217 +
  218 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
199 219 @RequestMapping(value = "/relations", method = RequestMethod.GET, params = {"toId", "toType", "relationType"})
200 220 @ResponseBody
201 221 public List<EntityRelation> findByTo(@RequestParam("toId") String strToId,
... ... @@ -215,7 +235,7 @@ public class EntityRelationController extends BaseController {
215 235 }
216 236 }
217 237
218   - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  238 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
219 239 @RequestMapping(value = "/relations", method = RequestMethod.POST)
220 240 @ResponseBody
221 241 public List<EntityRelation> findByQuery(@RequestBody EntityRelationsQuery query) throws ThingsboardException {
... ...
... ... @@ -20,6 +20,7 @@ public class EntityRelationInfo extends EntityRelation {
20 20
21 21 private static final long serialVersionUID = 2807343097519543363L;
22 22
  23 + private String fromName;
23 24 private String toName;
24 25
25 26 public EntityRelationInfo() {
... ... @@ -30,6 +31,14 @@ public class EntityRelationInfo extends EntityRelation {
30 31 super(entityRelation);
31 32 }
32 33
  34 + public String getFromName() {
  35 + return fromName;
  36 + }
  37 +
  38 + public void setFromName(String fromName) {
  39 + this.fromName = fromName;
  40 + }
  41 +
33 42 public String getToName() {
34 43 return toName;
35 44 }
... ...
... ... @@ -23,29 +23,17 @@ import lombok.extern.slf4j.Slf4j;
23 23 import org.springframework.beans.factory.annotation.Autowired;
24 24 import org.springframework.stereotype.Service;
25 25 import org.springframework.util.StringUtils;
26   -import org.thingsboard.server.common.data.BaseData;
27   -import org.thingsboard.server.common.data.Device;
28   -import org.thingsboard.server.common.data.EntityType;
29   -import org.thingsboard.server.common.data.asset.Asset;
30   -import org.thingsboard.server.common.data.id.AssetId;
31   -import org.thingsboard.server.common.data.id.DeviceId;
32 26 import org.thingsboard.server.common.data.id.EntityId;
33   -import org.thingsboard.server.common.data.id.UUIDBased;
34 27 import org.thingsboard.server.common.data.relation.EntityRelation;
35 28 import org.thingsboard.server.common.data.relation.EntityRelationInfo;
36 29 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
37   -import org.thingsboard.server.dao.asset.AssetService;
38   -import org.thingsboard.server.dao.customer.CustomerService;
39   -import org.thingsboard.server.dao.device.DeviceService;
40 30 import org.thingsboard.server.dao.entity.EntityService;
41 31 import org.thingsboard.server.dao.exception.DataValidationException;
42   -import org.thingsboard.server.dao.plugin.PluginService;
43   -import org.thingsboard.server.dao.rule.RuleService;
44   -import org.thingsboard.server.dao.tenant.TenantService;
45 32
46 33 import javax.annotation.Nullable;
47 34 import java.util.*;
48 35 import java.util.concurrent.ConcurrentHashMap;
  36 +import java.util.function.BiConsumer;
49 37
50 38 /**
51 39 * Created by ashvayka on 28.04.17.
... ... @@ -133,23 +121,16 @@ public class BaseRelationService implements RelationService {
133 121 ListenableFuture<List<EntityRelationInfo>> relationsInfo = Futures.transform(relations,
134 122 (AsyncFunction<List<EntityRelation>, List<EntityRelationInfo>>) relations1 -> {
135 123 List<ListenableFuture<EntityRelationInfo>> futures = new ArrayList<>();
136   - relations1.stream().forEach(relation -> futures.add(fetchRelationInfoAsync(relation)));
137   - return Futures.successfulAsList(futures);
  124 + relations1.stream().forEach(relation ->
  125 + futures.add(fetchRelationInfoAsync(relation,
  126 + relation2 -> relation2.getTo(),
  127 + (EntityRelationInfo relationInfo, String entityName) -> relationInfo.setToName(entityName)))
  128 + );
  129 + return Futures.successfulAsList(futures);
138 130 });
139 131 return relationsInfo;
140 132 }
141 133
142   - private ListenableFuture<EntityRelationInfo> fetchRelationInfoAsync(EntityRelation relation) {
143   - ListenableFuture<String> entityName = entityService.fetchEntityNameAsync(relation.getTo());
144   - ListenableFuture<EntityRelationInfo> entityRelationInfo =
145   - Futures.transform(entityName, (Function<String, EntityRelationInfo>) entityName1 -> {
146   - EntityRelationInfo entityRelationInfo1 = new EntityRelationInfo(relation);
147   - entityRelationInfo1.setToName(entityName1);
148   - return entityRelationInfo1;
149   - });
150   - return entityRelationInfo;
151   - }
152   -
153 134 @Override
154 135 public ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType, RelationTypeGroup typeGroup) {
155 136 log.trace("Executing findByFromAndType [{}][{}][{}]", from, relationType, typeGroup);
... ... @@ -168,6 +149,38 @@ public class BaseRelationService implements RelationService {
168 149 }
169 150
170 151 @Override
  152 + public ListenableFuture<List<EntityRelationInfo>> findInfoByTo(EntityId to, RelationTypeGroup typeGroup) {
  153 + log.trace("Executing findInfoByTo [{}][{}]", to, typeGroup);
  154 + validate(to);
  155 + validateTypeGroup(typeGroup);
  156 + ListenableFuture<List<EntityRelation>> relations = relationDao.findAllByTo(to, typeGroup);
  157 + ListenableFuture<List<EntityRelationInfo>> relationsInfo = Futures.transform(relations,
  158 + (AsyncFunction<List<EntityRelation>, List<EntityRelationInfo>>) relations1 -> {
  159 + List<ListenableFuture<EntityRelationInfo>> futures = new ArrayList<>();
  160 + relations1.stream().forEach(relation ->
  161 + futures.add(fetchRelationInfoAsync(relation,
  162 + relation2 -> relation2.getFrom(),
  163 + (EntityRelationInfo relationInfo, String entityName) -> relationInfo.setFromName(entityName)))
  164 + );
  165 + return Futures.successfulAsList(futures);
  166 + });
  167 + return relationsInfo;
  168 + }
  169 +
  170 + private ListenableFuture<EntityRelationInfo> fetchRelationInfoAsync(EntityRelation relation,
  171 + Function<EntityRelation, EntityId> entityIdGetter,
  172 + BiConsumer<EntityRelationInfo, String> entityNameSetter) {
  173 + ListenableFuture<String> entityName = entityService.fetchEntityNameAsync(entityIdGetter.apply(relation));
  174 + ListenableFuture<EntityRelationInfo> entityRelationInfo =
  175 + Futures.transform(entityName, (Function<String, EntityRelationInfo>) entityName1 -> {
  176 + EntityRelationInfo entityRelationInfo1 = new EntityRelationInfo(relation);
  177 + entityNameSetter.accept(entityRelationInfo1, entityName1);
  178 + return entityRelationInfo1;
  179 + });
  180 + return entityRelationInfo;
  181 + }
  182 +
  183 + @Override
171 184 public ListenableFuture<List<EntityRelation>> findByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup) {
172 185 log.trace("Executing findByToAndType [{}][{}][{}]", to, relationType, typeGroup);
173 186 validate(to);
... ...
... ... @@ -46,6 +46,8 @@ public interface RelationService {
46 46
47 47 ListenableFuture<List<EntityRelation>> findByTo(EntityId to, RelationTypeGroup typeGroup);
48 48
  49 + ListenableFuture<List<EntityRelationInfo>> findInfoByTo(EntityId to, RelationTypeGroup typeGroup);
  50 +
49 51 ListenableFuture<List<EntityRelation>> findByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup);
50 52
51 53 ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query);
... ...
... ... @@ -28,6 +28,7 @@ function EntityRelationService($http, $q) {
28 28 findInfoByFrom: findInfoByFrom,
29 29 findByFromAndType: findByFromAndType,
30 30 findByTo: findByTo,
  31 + findInfoByTo: findInfoByTo,
31 32 findByToAndType: findByToAndType,
32 33 findByQuery: findByQuery
33 34 }
... ... @@ -122,6 +123,18 @@ function EntityRelationService($http, $q) {
122 123 return deferred.promise;
123 124 }
124 125
  126 + function findInfoByTo(toId, toType) {
  127 + var deferred = $q.defer();
  128 + var url = '/api/relations/info?toId=' + toId;
  129 + url += '&toType=' + toType;
  130 + $http.get(url, null).then(function success(response) {
  131 + deferred.resolve(response.data);
  132 + }, function fail() {
  133 + deferred.reject();
  134 + });
  135 + return deferred.promise;
  136 + }
  137 +
125 138 function findByToAndType(toId, toType, relationType) {
126 139 var deferred = $q.defer();
127 140 var url = '/api/relations?toId=' + toId;
... ...
... ... @@ -55,5 +55,11 @@
55 55 default-event-type="{{vm.types.eventType.alarm.value}}">
56 56 </tb-event-table>
57 57 </md-tab>
  58 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
  59 + <tb-relation-table flex
  60 + entity-id="vm.grid.operatingItem().id.id"
  61 + entity-type="{{vm.types.entityType.customer}}">
  62 + </tb-relation-table>
  63 + </md-tab>
58 64 </md-tabs>
59 65 </tb-grid>
... ...
... ... @@ -56,4 +56,10 @@
56 56 default-event-type="{{vm.types.eventType.alarm.value}}">
57 57 </tb-event-table>
58 58 </md-tab>
  59 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
  60 + <tb-relation-table flex
  61 + entity-id="vm.grid.operatingItem().id.id"
  62 + entity-type="{{vm.types.entityType.device}}">
  63 + </tb-relation-table>
  64 + </md-tab>
59 65 </tb-grid>
... ...
... ... @@ -41,7 +41,7 @@
41 41 </md-autocomplete>
42 42 <md-chip-template>
43 43 <span>
44   - <strong>{{itemName($chip)}}</strong>
  44 + <strong>{{$chip.name}}</strong>
45 45 </span>
46 46 </md-chip-template>
47 47 </md-chips>
... ...
... ... @@ -17,6 +17,9 @@
17 17 -->
18 18 <div layout='row' class="tb-entity-select">
19 19 <tb-entity-type-select style="min-width: 100px;"
  20 + the-form="theForm"
  21 + ng-disabled="disabled"
  22 + tb-required="tbRequired"
20 23 ng-model="model.entityType">
21 24 </tb-entity-type-select>
22 25 <tb-entity-autocomplete flex ng-if="model.entityType"
... ...
... ... @@ -29,6 +29,8 @@ export default function EntityTypeSelect($compile, $templateCache, utils, userSe
29 29 var template = $templateCache.get(entityTypeSelectTemplate);
30 30 element.html(template);
31 31
  32 + scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
  33 +
32 34 if (angular.isDefined(attrs.hideLabel)) {
33 35 scope.showLabel = false;
34 36 } else {
... ... @@ -103,6 +105,9 @@ export default function EntityTypeSelect($compile, $templateCache, utils, userSe
103 105 require: "^ngModel",
104 106 link: linker,
105 107 scope: {
  108 + theForm: '=?',
  109 + tbRequired: '=?',
  110 + disabled:'=ngDisabled',
106 111 allowedEntityTypes: "=?"
107 112 }
108 113 };
... ...
... ... @@ -17,9 +17,13 @@
17 17 -->
18 18 <md-input-container>
19 19 <label ng-if="showLabel">{{ 'entity.type' | translate }}</label>
20   - <md-select ng-model="entityType" class="tb-entity-type-select" aria-label="{{ 'entity.type' | translate }}">
  20 + <md-select ng-required="tbRequired" ng-disabled="disabled" name="entityType"
  21 + ng-model="entityType" class="tb-entity-type-select" aria-label="{{ 'entity.type' | translate }}">
21 22 <md-option ng-repeat="type in entityTypes" ng-value="type">
22 23 {{typeName(type) | translate}}
23 24 </md-option>
24 25 </md-select>
25   -</md-input-container>
\ No newline at end of file
  26 + <div ng-messages="theForm.entityType.$error">
  27 + <div ng-message="required" translate>entity.type-required</div>
  28 + </div>
  29 +</md-input-container>
... ...
... ... @@ -27,6 +27,7 @@ import AddAttributeDialogController from './attribute/add-attribute-dialog.contr
27 27 import AddWidgetToDashboardDialogController from './attribute/add-widget-to-dashboard-dialog.controller';
28 28 import AttributeTableDirective from './attribute/attribute-table.directive';
29 29 import RelationTableDirective from './relation/relation-table.directive';
  30 +import RelationTypeAutocompleteDirective from './relation/relation-type-autocomplete.directive';
30 31
31 32 export default angular.module('thingsboard.entity', [])
32 33 .controller('EntityAliasesController', EntityAliasesController)
... ... @@ -42,4 +43,5 @@ export default angular.module('thingsboard.entity', [])
42 43 .directive('tbAliasesEntitySelect', AliasesEntitySelectDirective)
43 44 .directive('tbAttributeTable', AttributeTableDirective)
44 45 .directive('tbRelationTable', RelationTableDirective)
  46 + .directive('tbRelationTypeAutocomplete', RelationTypeAutocompleteDirective)
45 47 .name;
... ...
... ... @@ -14,14 +14,20 @@
14 14 * limitations under the License.
15 15 */
16 16 /*@ngInject*/
17   -export default function AddRelationDialogController($scope, $mdDialog, types, entityRelationService, from) {
  17 +export default function AddRelationDialogController($scope, $mdDialog, types, entityRelationService, direction, entityId) {
18 18
19 19 var vm = this;
20 20
21 21 vm.types = types;
  22 + vm.direction = direction;
  23 + vm.targetEntityId = {};
22 24
23 25 vm.relation = {};
24   - vm.relation.from = from;
  26 + if (vm.direction == vm.types.entitySearchDirection.from) {
  27 + vm.relation.from = entityId;
  28 + } else {
  29 + vm.relation.to = entityId;
  30 + }
25 31 vm.relation.type = types.entityRelationType.contains;
26 32
27 33 vm.add = add;
... ... @@ -32,6 +38,11 @@ export default function AddRelationDialogController($scope, $mdDialog, types, en
32 38 }
33 39
34 40 function add() {
  41 + if (vm.direction == vm.types.entitySearchDirection.from) {
  42 + vm.relation.to = vm.targetEntityId;
  43 + } else {
  44 + vm.relation.from = vm.targetEntityId;
  45 + }
35 46 $scope.theForm.$setPristine();
36 47 entityRelationService.saveRelation(vm.relation).then(
37 48 function success() {
... ...
... ... @@ -32,19 +32,16 @@
32 32 <div class="md-dialog-content">
33 33 <md-content class="md-padding" layout="column">
34 34 <fieldset ng-disabled="loading">
35   - <md-input-container class="md-block">
36   - <label translate>relation.relation-type</label>
37   - <md-select required ng-model="vm.relation.type" ng-disabled="loading">
38   - <md-option ng-repeat="type in vm.types.entityRelationType" ng-value="type">
39   - <span>{{('relation.relation-types.' + type) | translate}}</span>
40   - </md-option>
41   - </md-select>
42   - </md-input-container>
43   - <span class="tb-small">{{'entity.entity' | translate }}</span>
  35 + <tb-relation-type-autocomplete ng-model="vm.relation.type"
  36 + tb-required="true"
  37 + ng-disabled="loading">
  38 + </tb-relation-type-autocomplete>
  39 + <small>{{(vm.direction == vm.types.entitySearchDirection.from ?
  40 + 'relation.to-entity' : 'relation.from-entity') | translate}}</small>
44 41 <tb-entity-select flex
45 42 the-form="theForm"
46 43 tb-required="true"
47   - ng-model="vm.relation.to">
  44 + ng-model="vm.targetEntityId">
48 45 </tb-entity-select>
49 46 </fieldset>
50 47 </md-content>
... ...
... ... @@ -45,13 +45,17 @@ function RelationTableController($scope, $q, $mdDialog, $document, $translate, $
45 45
46 46 let vm = this;
47 47
  48 + vm.types = types;
  49 +
  50 + vm.direction = vm.types.entitySearchDirection.from;
  51 +
48 52 vm.relations = [];
49 53 vm.relationsCount = 0;
50 54 vm.allRelations = [];
51 55 vm.selectedRelations = [];
52 56
53 57 vm.query = {
54   - order: 'typeName',
  58 + order: 'type',
55 59 limit: 5,
56 60 page: 1,
57 61 search: null
... ... @@ -62,19 +66,23 @@ function RelationTableController($scope, $q, $mdDialog, $document, $translate, $
62 66 vm.onReorder = onReorder;
63 67 vm.onPaginate = onPaginate;
64 68 vm.addRelation = addRelation;
65   - vm.editRelation = editRelation;
66 69 vm.deleteRelation = deleteRelation;
67 70 vm.deleteRelations = deleteRelations;
68 71 vm.reloadRelations = reloadRelations;
69 72 vm.updateRelations = updateRelations;
70 73
71   -
72 74 $scope.$watch("vm.entityId", function(newVal, prevVal) {
73 75 if (newVal && !angular.equals(newVal, prevVal)) {
74 76 reloadRelations();
75 77 }
76 78 });
77 79
  80 + $scope.$watch("vm.direction", function(newVal, prevVal) {
  81 + if (newVal && !angular.equals(newVal, prevVal)) {
  82 + reloadRelations();
  83 + }
  84 + });
  85 +
78 86 $scope.$watch("vm.query.search", function(newVal, prevVal) {
79 87 if (!angular.equals(newVal, prevVal) && vm.query.search != null) {
80 88 updateRelations();
... ... @@ -102,7 +110,7 @@ function RelationTableController($scope, $q, $mdDialog, $document, $translate, $
102 110 if ($event) {
103 111 $event.stopPropagation();
104 112 }
105   - var from = {
  113 + var entityId = {
106 114 id: vm.entityId,
107 115 entityType: vm.entityType
108 116 };
... ... @@ -111,7 +119,7 @@ function RelationTableController($scope, $q, $mdDialog, $document, $translate, $
111 119 controllerAs: 'vm',
112 120 templateUrl: addRelationTemplate,
113 121 parent: angular.element($document[0].body),
114   - locals: { from: from },
  122 + locals: { direction: vm.direction, entityId: entityId },
115 123 fullscreen: true,
116 124 targetEvent: $event
117 125 }).then(function () {
... ... @@ -120,36 +128,100 @@ function RelationTableController($scope, $q, $mdDialog, $document, $translate, $
120 128 });
121 129 }
122 130
123   - function editRelation($event, /*relation*/) {
  131 + function deleteRelation($event, relation) {
124 132 if ($event) {
125 133 $event.stopPropagation();
126 134 }
127   - //TODO:
128   - }
  135 + if (relation) {
  136 + var title;
  137 + var content;
  138 + if (vm.direction == vm.types.entitySearchDirection.from) {
  139 + title = $translate.instant('relation.delete-to-relation-title', {entityName: relation.toName});
  140 + content = $translate.instant('relation.delete-to-relation-text', {entityName: relation.toName});
  141 + } else {
  142 + title = $translate.instant('relation.delete-from-relation-title', {entityName: relation.fromName});
  143 + content = $translate.instant('relation.delete-from-relation-text', {entityName: relation.fromName});
  144 + }
129 145
130   - function deleteRelation($event, /*relation*/) {
131   - if ($event) {
132   - $event.stopPropagation();
  146 + var confirm = $mdDialog.confirm()
  147 + .targetEvent($event)
  148 + .title(title)
  149 + .htmlContent(content)
  150 + .ariaLabel(title)
  151 + .cancel($translate.instant('action.no'))
  152 + .ok($translate.instant('action.yes'));
  153 + $mdDialog.show(confirm).then(function () {
  154 + entityRelationService.deleteRelation(
  155 + relation.from.id,
  156 + relation.from.entityType,
  157 + relation.type,
  158 + relation.to.id,
  159 + relation.to.entityType).then(
  160 + function success() {
  161 + reloadRelations();
  162 + }
  163 + );
  164 + });
133 165 }
134   - //TODO:
135 166 }
136 167
137 168 function deleteRelations($event) {
138 169 if ($event) {
139 170 $event.stopPropagation();
140 171 }
141   - //TODO:
  172 + if (vm.selectedRelations && vm.selectedRelations.length > 0) {
  173 + var title;
  174 + var content;
  175 + if (vm.direction == vm.types.entitySearchDirection.from) {
  176 + title = $translate.instant('relation.delete-to-relations-title', {count: vm.selectedRelations.length}, 'messageformat');
  177 + content = $translate.instant('relation.delete-to-relations-text');
  178 + } else {
  179 + title = $translate.instant('relation.delete-from-relations-title', {count: vm.selectedRelations.length}, 'messageformat');
  180 + content = $translate.instant('relation.delete-from-relations-text');
  181 + }
  182 + var confirm = $mdDialog.confirm()
  183 + .targetEvent($event)
  184 + .title(title)
  185 + .htmlContent(content)
  186 + .ariaLabel(title)
  187 + .cancel($translate.instant('action.no'))
  188 + .ok($translate.instant('action.yes'));
  189 + $mdDialog.show(confirm).then(function () {
  190 + var tasks = [];
  191 + for (var i=0;i<vm.selectedRelations.length;i++) {
  192 + var relation = vm.selectedRelations[i];
  193 + tasks.push( entityRelationService.deleteRelation(
  194 + relation.from.id,
  195 + relation.from.entityType,
  196 + relation.type,
  197 + relation.to.id,
  198 + relation.to.entityType));
  199 + }
  200 + $q.all(tasks).then(function () {
  201 + reloadRelations();
  202 + });
  203 +
  204 + });
  205 + }
142 206 }
143 207
144 208 function reloadRelations () {
145 209 vm.allRelations.length = 0;
146 210 vm.relations.length = 0;
147   - vm.relationsPromise = entityRelationService.findInfoByFrom(vm.entityId, vm.entityType);
  211 + vm.relationsPromise;
  212 + if (vm.direction == vm.types.entitySearchDirection.from) {
  213 + vm.relationsPromise = entityRelationService.findInfoByFrom(vm.entityId, vm.entityType);
  214 + } else {
  215 + vm.relationsPromise = entityRelationService.findInfoByTo(vm.entityId, vm.entityType);
  216 + }
148 217 vm.relationsPromise.then(
149 218 function success(allRelations) {
150 219 allRelations.forEach(function(relation) {
151   - relation.typeName = $translate.instant('relation.relation-type.' + relation.type);
152   - relation.toEntityTypeName = $translate.instant(utils.entityTypeName(relation.to.entityType));
  220 + if (vm.direction == vm.types.entitySearchDirection.from) {
  221 + relation.toEntityTypeName = $translate.instant(utils.entityTypeName(relation.to.entityType));
  222 + } else {
  223 + relation.fromEntityTypeName = $translate.instant(utils.entityTypeName(relation.from.entityType));
  224 + }
153 225 });
154 226 vm.allRelations = allRelations;
155 227 vm.selectedRelations = [];
... ...
... ... @@ -16,11 +16,22 @@
16 16
17 17 -->
18 18 <md-content flex class="md-padding tb-absolute-fill tb-relation-table tb-data-table" layout="column">
  19 + <section layout="row">
  20 + <md-input-container class="md-block" style="width: 200px;">
  21 + <label translate>relation.direction</label>
  22 + <md-select ng-model="vm.direction" ng-disabled="loading">
  23 + <md-option ng-repeat="direction in vm.types.entitySearchDirection" ng-value="direction">
  24 + {{ ('relation.search-direction.' + direction) | translate}}
  25 + </md-option>
  26 + </md-select>
  27 + </md-input-container>
  28 + </section>
19 29 <div layout="column" class="md-whiteframe-z1">
20 30 <md-toolbar class="md-table-toolbar md-default" ng-show="!vm.selectedRelations.length
21 31 && vm.query.search === null">
22 32 <div class="md-toolbar-tools">
23   - <span translate>relation.entity-relations</span>
  33 + <span>{{(vm.direction == vm.types.entitySearchDirection.from ?
  34 + 'relation.from-relations' : 'relation.to-relations') | translate}}</span>
24 35 <span flex></span>
25 36 <md-button class="md-icon-button" ng-click="vm.addRelation($event)">
26 37 <md-icon>add</md-icon>
... ... @@ -66,7 +77,7 @@
66 77 <md-toolbar class="md-table-toolbar alternate" ng-show="vm.selectedRelations.length">
67 78 <div class="md-toolbar-tools">
68 79 <span translate
69   - translate-values="{count: selectedRelations.length}"
  80 + translate-values="{count: vm.selectedRelations.length}"
70 81 translate-interpolation="messageformat">relation.selected-relations</span>
71 82 <span flex></span>
72 83 <md-button class="md-icon-button" ng-click="vm.deleteRelations($event)">
... ... @@ -81,25 +92,26 @@
81 92 <table md-table md-row-select multiple="" ng-model="vm.selectedRelations" md-progress="vm.relationsDeferred.promise">
82 93 <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
83 94 <tr md-row>
84   - <th md-column md-order-by="typeName"><span translate>relation.type</span></th>
85   - <th md-column md-order-by="toEntityTypeName"><span translate>relation.to-entity-type</span></th>
86   - <th md-column md-order-by="toName"><span translate>relation.to-entity-name</span></th>
  95 + <th md-column md-order-by="type"><span translate>relation.type</span></th>
  96 + <th md-column ng-if="vm.direction == vm.types.entitySearchDirection.from"
  97 + md-order-by="toEntityTypeName"><span translate>relation.to-entity-type</span></th>
  98 + <th md-column ng-if="vm.direction == vm.types.entitySearchDirection.to"
  99 + md-order-by="fromEntityTypeName"><span translate>relation.from-entity-type</span></th>
  100 + <th md-column ng-if="vm.direction == vm.types.entitySearchDirection.from"
  101 + md-order-by="toName"><span translate>relation.to-entity-name</span></th>
  102 + <th md-column ng-if="vm.direction == vm.types.entitySearchDirection.to"
  103 + md-order-by="fromName"><span translate>relation.from-entity-name</span></th>
87 104 <th md-column><span>&nbsp</span></th>
88 105 </tr>
89 106 </thead>
90 107 <tbody md-body>
91 108 <tr md-row md-select="relation" md-select-id="relation" md-auto-select ng-repeat="relation in vm.relations">
92   - <td md-cell>{{ relation.typeName }}</td>
93   - <td md-cell>{{ relation.toEntityTypeName }}</td>
94   - <td md-cell>{{ relation.toName }}</td>
  109 + <td md-cell>{{ relation.type }}</td>
  110 + <td md-cell ng-if="vm.direction == vm.types.entitySearchDirection.from">{{ relation.toEntityTypeName }}</td>
  111 + <td md-cell ng-if="vm.direction == vm.types.entitySearchDirection.to">{{ relation.fromEntityTypeName }}</td>
  112 + <td md-cell ng-if="vm.direction == vm.types.entitySearchDirection.from">{{ relation.toName }}</td>
  113 + <td md-cell ng-if="vm.direction == vm.types.entitySearchDirection.to">{{ relation.fromName }}</td>
95 114 <td md-cell class="tb-action-cell">
96   - <md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}"
97   - ng-click="vm.editRelation($event, relation)">
98   - <md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">edit</md-icon>
99   - <md-tooltip md-direction="top">
100   - {{ 'relation.edit' | translate }}
101   - </md-tooltip>
102   - </md-button>
103 115 <md-button class="md-icon-button" aria-label="{{ 'action.delete' | translate }}" ng-click="vm.deleteRelation($event, relation)">
104 116 <md-icon aria-label="{{ 'action.delete' | translate }}" class="material-icons">delete</md-icon>
105 117 <md-tooltip md-direction="top">
... ...
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +import './relation-type-autocomplete.scss';
  17 +
  18 +/* eslint-disable import/no-unresolved, import/default */
  19 +
  20 +import relationTypeAutocompleteTemplate from './relation-type-autocomplete.tpl.html';
  21 +
  22 +/* eslint-enable import/no-unresolved, import/default */
  23 +
  24 +/*@ngInject*/
  25 +export default function RelationTypeAutocomplete($compile, $templateCache, $q, $filter, assetService, deviceService, types) {
  26 +
  27 + var linker = function (scope, element, attrs, ngModelCtrl) {
  28 + var template = $templateCache.get(relationTypeAutocompleteTemplate);
  29 + element.html(template);
  30 +
  31 + scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
  32 + scope.relationType = null;
  33 + scope.relationTypeSearchText = '';
  34 + scope.relationTypes = [];
  35 + for (var type in types.entityRelationType) {
  36 + scope.relationTypes.push(types.entityRelationType[type]);
  37 + }
  38 +
  39 + scope.fetchRelationTypes = function(searchText) {
  40 + var deferred = $q.defer();
  41 + var result = $filter('filter')(scope.relationTypes, {'$': searchText});
  42 + if (result && result.length) {
  43 + deferred.resolve(result);
  44 + } else {
  45 + deferred.resolve([searchText]);
  46 + }
  47 + return deferred.promise;
  48 + }
  49 +
  50 + scope.relationTypeSearchTextChanged = function() {
  51 + }
  52 +
  53 + scope.updateView = function () {
  54 + if (!scope.disabled) {
  55 + ngModelCtrl.$setViewValue(scope.relationType);
  56 + }
  57 + }
  58 +
  59 + ngModelCtrl.$render = function () {
  60 + scope.relationType = ngModelCtrl.$viewValue;
  61 + }
  62 +
  63 + scope.$watch('relationType', function (newValue, prevValue) {
  64 + if (!angular.equals(newValue, prevValue)) {
  65 + scope.updateView();
  66 + }
  67 + });
  68 +
  69 + scope.$watch('disabled', function () {
  70 + scope.updateView();
  71 + });
  72 +
  73 + $compile(element.contents())(scope);
  74 + }
  75 +
  76 + return {
  77 + restrict: "E",
  78 + require: "^ngModel",
  79 + link: linker,
  80 + scope: {
  81 + theForm: '=?',
  82 + tbRequired: '=?',
  83 + disabled:'=ngDisabled'
  84 + }
  85 + };
  86 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +.tb-relation-type-autocomplete {
  17 + .tb-relation-type-item {
  18 + display: block;
  19 + height: 48px;
  20 + }
  21 + li {
  22 + height: auto !important;
  23 + white-space: normal !important;
  24 + }
  25 +}
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2017 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<md-autocomplete ng-required="tbRequired"
  19 + ng-disabled="disabled"
  20 + md-no-cache="true"
  21 + md-input-name="relationType"
  22 + ng-model="relationType"
  23 + md-selected-item="relationType"
  24 + md-search-text="relationTypeSearchText"
  25 + md-search-text-change="relationTypeSearchTextChanged()"
  26 + md-items="item in fetchRelationTypes(relationTypeSearchText)"
  27 + md-item-text="item"
  28 + md-min-length="0"
  29 + md-floating-label="{{ 'relation.relation-type' | translate }}"
  30 + md-select-on-match="true"
  31 + md-menu-class="tb-relation-type-autocomplete">
  32 + <md-item-template>
  33 + <div class="tb-relation-type-item">
  34 + <span md-highlight-text="relationTypeSearchText" md-highlight-flags="^i">{{item}}</span>
  35 + </div>
  36 + </md-item-template>
  37 + <div ng-messages="theForm.relationType.$error">
  38 + <div translate ng-message="required">relation.relation-type-required</div>
  39 + </div>
  40 +</md-autocomplete>
... ...
... ... @@ -544,6 +544,7 @@ export default angular.module('thingsboard.locale', [])
544 544 "entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.",
545 545 "all-subtypes": "All",
546 546 "type": "Type",
  547 + "type-required": "Entity type is required.",
547 548 "type-device": "Device",
548 549 "type-asset": "Asset",
549 550 "type-rule": "Rule",
... ... @@ -718,19 +719,33 @@ export default angular.module('thingsboard.locale', [])
718 719 },
719 720 "relation": {
720 721 "relations": "Relations",
721   - "entity-relations": "Entity relations",
  722 + "direction": "Direction",
  723 + "search-direction": {
  724 + "FROM": "From",
  725 + "TO": "To"
  726 + },
  727 + "from-relations": "Outbound relations",
  728 + "to-relations": "Inbound relations",
722 729 "selected-relations": "{ count, select, 1 {1 relation} other {# relations} } selected",
723 730 "type": "Type",
724   - "to-entity-type": "Entity type",
725   - "to-entity-name": "Entity name",
726   - "edit": "Edit relation",
  731 + "to-entity-type": "To entity type",
  732 + "to-entity-name": "To entity name",
  733 + "from-entity-type": "From entity type",
  734 + "from-entity-name": "From entity name",
  735 + "to-entity": "To entity",
  736 + "from-entity": "From entity",
727 737 "delete": "Delete relation",
728 738 "relation-type": "Relation type",
729   - "relation-types": {
730   - "Contains": "Contains",
731   - "Manages": "Manages"
732   - },
733   - "add": "Add relation"
  739 + "relation-type-required": "Relation type is required.",
  740 + "add": "Add relation",
  741 + "delete-to-relation-title": "Are you sure you want to delete relation to the entity '{{entityName}}'?",
  742 + "delete-to-relation-text": "Be careful, after the confirmation the entity '{{entityName}}' will be unrelated from the current entity.",
  743 + "delete-to-relations-title": "Are you sure you want to delete { count, select, 1 {1 relation} other {# relations} }?",
  744 + "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.",
  745 + "delete-from-relation-title": "Are you sure you want to delete relation from the entity '{{entityName}}'?",
  746 + "delete-from-relation-text": "Be careful, after the confirmation current entity will be unrelated from the entity '{{entityName}}'.",
  747 + "delete-from-relations-title": "Are you sure you want to delete { count, select, 1 {1 relation} other {# relations} }?",
  748 + "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."
734 749 },
735 750 "rule": {
736 751 "rule": "Rule",
... ...
... ... @@ -56,5 +56,11 @@
56 56 disabled-event-types="{{vm.types.eventType.alarm.value}}">
57 57 </tb-event-table>
58 58 </md-tab>
  59 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
  60 + <tb-relation-table flex
  61 + entity-id="vm.grid.operatingItem().id.id"
  62 + entity-type="{{vm.types.entityType.plugin}}">
  63 + </tb-relation-table>
  64 + </md-tab>
59 65 </md-tabs>
60 66 </tb-grid>
... ...
... ... @@ -56,5 +56,11 @@
56 56 disabled-event-types="{{vm.types.eventType.alarm.value}}">
57 57 </tb-event-table>
58 58 </md-tab>
  59 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
  60 + <tb-relation-table flex
  61 + entity-id="vm.grid.operatingItem().id.id"
  62 + entity-type="{{vm.types.entityType.rule}}">
  63 + </tb-relation-table>
  64 + </md-tab>
59 65 </md-tabs>
60 66 </tb-grid>
... ...
... ... @@ -53,5 +53,11 @@
53 53 default-event-type="{{vm.types.eventType.alarm.value}}">
54 54 </tb-event-table>
55 55 </md-tab>
  56 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
  57 + <tb-relation-table flex
  58 + entity-id="vm.grid.operatingItem().id.id"
  59 + entity-type="{{vm.types.entityType.tenant}}">
  60 + </tb-relation-table>
  61 + </md-tab>
56 62 </md-tabs>
57 63 </tb-grid>
... ...
... ... @@ -436,6 +436,7 @@ md-tabs.tb-headless {
436 436 ***********************/
437 437
438 438 section.tb-header-buttons {
  439 + pointer-events: none;
439 440 position: absolute;
440 441 right: 0px;
441 442 top: 86px;
... ...