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

This commit is contained in:
volodymyr-babak 2017-05-30 19:45:58 +03:00
commit 2f79950178
24 changed files with 454 additions and 94 deletions

View File

@ -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<EntityRelation> 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<EntityRelationInfo> 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<EntityRelation> 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<EntityRelation> 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<EntityRelationInfo> 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<EntityRelation> 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<EntityRelation> findByQuery(@RequestBody EntityRelationsQuery query) throws ThingsboardException {

View File

@ -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;
}

View File

@ -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<List<EntityRelationInfo>> relationsInfo = Futures.transform(relations,
(AsyncFunction<List<EntityRelation>, List<EntityRelationInfo>>) relations1 -> {
List<ListenableFuture<EntityRelationInfo>> futures = new ArrayList<>();
relations1.stream().forEach(relation -> futures.add(fetchRelationInfoAsync(relation)));
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<EntityRelationInfo> fetchRelationInfoAsync(EntityRelation relation) {
ListenableFuture<String> entityName = entityService.fetchEntityNameAsync(relation.getTo());
ListenableFuture<EntityRelationInfo> entityRelationInfo =
Futures.transform(entityName, (Function<String, EntityRelationInfo>) entityName1 -> {
EntityRelationInfo entityRelationInfo1 = new EntityRelationInfo(relation);
entityRelationInfo1.setToName(entityName1);
return entityRelationInfo1;
});
return entityRelationInfo;
}
@Override
public ListenableFuture<List<EntityRelation>> 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<List<EntityRelationInfo>> findInfoByTo(EntityId to, RelationTypeGroup typeGroup) {
log.trace("Executing findInfoByTo [{}][{}]", to, typeGroup);
validate(to);
validateTypeGroup(typeGroup);
ListenableFuture<List<EntityRelation>> relations = relationDao.findAllByTo(to, typeGroup);
ListenableFuture<List<EntityRelationInfo>> relationsInfo = Futures.transform(relations,
(AsyncFunction<List<EntityRelation>, List<EntityRelationInfo>>) relations1 -> {
List<ListenableFuture<EntityRelationInfo>> futures = new ArrayList<>();
relations1.stream().forEach(relation ->
futures.add(fetchRelationInfoAsync(relation,
relation2 -> relation2.getFrom(),
(EntityRelationInfo relationInfo, String entityName) -> relationInfo.setFromName(entityName)))
);
return Futures.successfulAsList(futures);
});
return relationsInfo;
}
private ListenableFuture<EntityRelationInfo> fetchRelationInfoAsync(EntityRelation relation,
Function<EntityRelation, EntityId> entityIdGetter,
BiConsumer<EntityRelationInfo, String> entityNameSetter) {
ListenableFuture<String> entityName = entityService.fetchEntityNameAsync(entityIdGetter.apply(relation));
ListenableFuture<EntityRelationInfo> entityRelationInfo =
Futures.transform(entityName, (Function<String, EntityRelationInfo>) entityName1 -> {
EntityRelationInfo entityRelationInfo1 = new EntityRelationInfo(relation);
entityNameSetter.accept(entityRelationInfo1, entityName1);
return entityRelationInfo1;
});
return entityRelationInfo;
}
@Override
public ListenableFuture<List<EntityRelation>> findByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup) {
log.trace("Executing findByToAndType [{}][{}][{}]", to, relationType, typeGroup);

View File

@ -46,6 +46,8 @@ public interface RelationService {
ListenableFuture<List<EntityRelation>> findByTo(EntityId to, RelationTypeGroup typeGroup);
ListenableFuture<List<EntityRelationInfo>> findInfoByTo(EntityId to, RelationTypeGroup typeGroup);
ListenableFuture<List<EntityRelation>> findByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup);
ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query);

View File

@ -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;

View File

@ -55,5 +55,11 @@
default-event-type="{{vm.types.eventType.alarm.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
<tb-relation-table flex
entity-id="vm.grid.operatingItem().id.id"
entity-type="{{vm.types.entityType.customer}}">
</tb-relation-table>
</md-tab>
</md-tabs>
</tb-grid>

View File

@ -56,4 +56,10 @@
default-event-type="{{vm.types.eventType.alarm.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
<tb-relation-table flex
entity-id="vm.grid.operatingItem().id.id"
entity-type="{{vm.types.entityType.device}}">
</tb-relation-table>
</md-tab>
</tb-grid>

View File

@ -41,7 +41,7 @@
</md-autocomplete>
<md-chip-template>
<span>
<strong>{{itemName($chip)}}</strong>
<strong>{{$chip.name}}</strong>
</span>
</md-chip-template>
</md-chips>

View File

@ -17,6 +17,9 @@
-->
<div layout='row' class="tb-entity-select">
<tb-entity-type-select style="min-width: 100px;"
the-form="theForm"
ng-disabled="disabled"
tb-required="tbRequired"
ng-model="model.entityType">
</tb-entity-type-select>
<tb-entity-autocomplete flex ng-if="model.entityType"

View File

@ -29,6 +29,8 @@ export default function EntityTypeSelect($compile, $templateCache, utils, userSe
var template = $templateCache.get(entityTypeSelectTemplate);
element.html(template);
scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
if (angular.isDefined(attrs.hideLabel)) {
scope.showLabel = false;
} else {
@ -103,6 +105,9 @@ export default function EntityTypeSelect($compile, $templateCache, utils, userSe
require: "^ngModel",
link: linker,
scope: {
theForm: '=?',
tbRequired: '=?',
disabled:'=ngDisabled',
allowedEntityTypes: "=?"
}
};

View File

@ -17,9 +17,13 @@
-->
<md-input-container>
<label ng-if="showLabel">{{ 'entity.type' | translate }}</label>
<md-select ng-model="entityType" class="tb-entity-type-select" aria-label="{{ 'entity.type' | translate }}">
<md-select ng-required="tbRequired" ng-disabled="disabled" name="entityType"
ng-model="entityType" class="tb-entity-type-select" aria-label="{{ 'entity.type' | translate }}">
<md-option ng-repeat="type in entityTypes" ng-value="type">
{{typeName(type) | translate}}
</md-option>
</md-select>
</md-input-container>
<div ng-messages="theForm.entityType.$error">
<div ng-message="required" translate>entity.type-required</div>
</div>
</md-input-container>

View File

@ -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;

View File

@ -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() {

View File

@ -32,19 +32,16 @@
<div class="md-dialog-content">
<md-content class="md-padding" layout="column">
<fieldset ng-disabled="loading">
<md-input-container class="md-block">
<label translate>relation.relation-type</label>
<md-select required ng-model="vm.relation.type" ng-disabled="loading">
<md-option ng-repeat="type in vm.types.entityRelationType" ng-value="type">
<span>{{('relation.relation-types.' + type) | translate}}</span>
</md-option>
</md-select>
</md-input-container>
<span class="tb-small">{{'entity.entity' | translate }}</span>
<tb-relation-type-autocomplete ng-model="vm.relation.type"
tb-required="true"
ng-disabled="loading">
</tb-relation-type-autocomplete>
<small>{{(vm.direction == vm.types.entitySearchDirection.from ?
'relation.to-entity' : 'relation.from-entity') | translate}}</small>
<tb-entity-select flex
the-form="theForm"
tb-required="true"
ng-model="vm.relation.to">
ng-model="vm.targetEntityId">
</tb-entity-select>
</fieldset>
</md-content>

View File

@ -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<vm.selectedRelations.length;i++) {
var relation = vm.selectedRelations[i];
tasks.push( entityRelationService.deleteRelation(
relation.from.id,
relation.from.entityType,
relation.type,
relation.to.id,
relation.to.entityType));
}
$q.all(tasks).then(function () {
reloadRelations();
});
});
}
}
function reloadRelations () {
vm.allRelations.length = 0;
vm.relations.length = 0;
vm.relationsPromise = entityRelationService.findInfoByFrom(vm.entityId, vm.entityType);
vm.relationsPromise;
if (vm.direction == vm.types.entitySearchDirection.from) {
vm.relationsPromise = entityRelationService.findInfoByFrom(vm.entityId, vm.entityType);
} else {
vm.relationsPromise = entityRelationService.findInfoByTo(vm.entityId, vm.entityType);
}
vm.relationsPromise.then(
function success(allRelations) {
allRelations.forEach(function(relation) {
relation.typeName = $translate.instant('relation.relation-type.' + relation.type);
relation.toEntityTypeName = $translate.instant(utils.entityTypeName(relation.to.entityType));
if (vm.direction == vm.types.entitySearchDirection.from) {
relation.toEntityTypeName = $translate.instant(utils.entityTypeName(relation.to.entityType));
} else {
relation.fromEntityTypeName = $translate.instant(utils.entityTypeName(relation.from.entityType));
}
});
vm.allRelations = allRelations;
vm.selectedRelations = [];

View File

@ -16,11 +16,22 @@
-->
<md-content flex class="md-padding tb-absolute-fill tb-relation-table tb-data-table" layout="column">
<section layout="row">
<md-input-container class="md-block" style="width: 200px;">
<label translate>relation.direction</label>
<md-select ng-model="vm.direction" ng-disabled="loading">
<md-option ng-repeat="direction in vm.types.entitySearchDirection" ng-value="direction">
{{ ('relation.search-direction.' + direction) | translate}}
</md-option>
</md-select>
</md-input-container>
</section>
<div layout="column" class="md-whiteframe-z1">
<md-toolbar class="md-table-toolbar md-default" ng-show="!vm.selectedRelations.length
&& vm.query.search === null">
<div class="md-toolbar-tools">
<span translate>relation.entity-relations</span>
<span>{{(vm.direction == vm.types.entitySearchDirection.from ?
'relation.from-relations' : 'relation.to-relations') | translate}}</span>
<span flex></span>
<md-button class="md-icon-button" ng-click="vm.addRelation($event)">
<md-icon>add</md-icon>
@ -66,7 +77,7 @@
<md-toolbar class="md-table-toolbar alternate" ng-show="vm.selectedRelations.length">
<div class="md-toolbar-tools">
<span translate
translate-values="{count: selectedRelations.length}"
translate-values="{count: vm.selectedRelations.length}"
translate-interpolation="messageformat">relation.selected-relations</span>
<span flex></span>
<md-button class="md-icon-button" ng-click="vm.deleteRelations($event)">
@ -81,25 +92,26 @@
<table md-table md-row-select multiple="" ng-model="vm.selectedRelations" md-progress="vm.relationsDeferred.promise">
<thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
<tr md-row>
<th md-column md-order-by="typeName"><span translate>relation.type</span></th>
<th md-column md-order-by="toEntityTypeName"><span translate>relation.to-entity-type</span></th>
<th md-column md-order-by="toName"><span translate>relation.to-entity-name</span></th>
<th md-column md-order-by="type"><span translate>relation.type</span></th>
<th md-column ng-if="vm.direction == vm.types.entitySearchDirection.from"
md-order-by="toEntityTypeName"><span translate>relation.to-entity-type</span></th>
<th md-column ng-if="vm.direction == vm.types.entitySearchDirection.to"
md-order-by="fromEntityTypeName"><span translate>relation.from-entity-type</span></th>
<th md-column ng-if="vm.direction == vm.types.entitySearchDirection.from"
md-order-by="toName"><span translate>relation.to-entity-name</span></th>
<th md-column ng-if="vm.direction == vm.types.entitySearchDirection.to"
md-order-by="fromName"><span translate>relation.from-entity-name</span></th>
<th md-column><span>&nbsp</span></th>
</tr>
</thead>
<tbody md-body>
<tr md-row md-select="relation" md-select-id="relation" md-auto-select ng-repeat="relation in vm.relations">
<td md-cell>{{ relation.typeName }}</td>
<td md-cell>{{ relation.toEntityTypeName }}</td>
<td md-cell>{{ relation.toName }}</td>
<td md-cell>{{ relation.type }}</td>
<td md-cell ng-if="vm.direction == vm.types.entitySearchDirection.from">{{ relation.toEntityTypeName }}</td>
<td md-cell ng-if="vm.direction == vm.types.entitySearchDirection.to">{{ relation.fromEntityTypeName }}</td>
<td md-cell ng-if="vm.direction == vm.types.entitySearchDirection.from">{{ relation.toName }}</td>
<td md-cell ng-if="vm.direction == vm.types.entitySearchDirection.to">{{ relation.fromName }}</td>
<td md-cell class="tb-action-cell">
<md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}"
ng-click="vm.editRelation($event, relation)">
<md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">edit</md-icon>
<md-tooltip md-direction="top">
{{ 'relation.edit' | translate }}
</md-tooltip>
</md-button>
<md-button class="md-icon-button" aria-label="{{ 'action.delete' | translate }}" ng-click="vm.deleteRelation($event, relation)">
<md-icon aria-label="{{ 'action.delete' | translate }}" class="material-icons">delete</md-icon>
<md-tooltip md-direction="top">

View File

@ -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'
}
};
}

View File

@ -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;
}
}

View File

@ -0,0 +1,40 @@
<!--
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-autocomplete ng-required="tbRequired"
ng-disabled="disabled"
md-no-cache="true"
md-input-name="relationType"
ng-model="relationType"
md-selected-item="relationType"
md-search-text="relationTypeSearchText"
md-search-text-change="relationTypeSearchTextChanged()"
md-items="item in fetchRelationTypes(relationTypeSearchText)"
md-item-text="item"
md-min-length="0"
md-floating-label="{{ 'relation.relation-type' | translate }}"
md-select-on-match="true"
md-menu-class="tb-relation-type-autocomplete">
<md-item-template>
<div class="tb-relation-type-item">
<span md-highlight-text="relationTypeSearchText" md-highlight-flags="^i">{{item}}</span>
</div>
</md-item-template>
<div ng-messages="theForm.relationType.$error">
<div translate ng-message="required">relation.relation-type-required</div>
</div>
</md-autocomplete>

View File

@ -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",

View File

@ -56,5 +56,11 @@
disabled-event-types="{{vm.types.eventType.alarm.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
<tb-relation-table flex
entity-id="vm.grid.operatingItem().id.id"
entity-type="{{vm.types.entityType.plugin}}">
</tb-relation-table>
</md-tab>
</md-tabs>
</tb-grid>

View File

@ -56,5 +56,11 @@
disabled-event-types="{{vm.types.eventType.alarm.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
<tb-relation-table flex
entity-id="vm.grid.operatingItem().id.id"
entity-type="{{vm.types.entityType.rule}}">
</tb-relation-table>
</md-tab>
</md-tabs>
</tb-grid>

View File

@ -53,5 +53,11 @@
default-event-type="{{vm.types.eventType.alarm.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
<tb-relation-table flex
entity-id="vm.grid.operatingItem().id.id"
entity-type="{{vm.types.entityType.tenant}}">
</tb-relation-table>
</md-tab>
</md-tabs>
</tb-grid>

View File

@ -436,6 +436,7 @@ md-tabs.tb-headless {
***********************/
section.tb-header-buttons {
pointer-events: none;
position: absolute;
right: 0px;
top: 86px;