Commit 9cdfe4ef54e38e4a0b962665abae1bef431a548d

Authored by Igor Kulikov
1 parent a90f16f4

TB-61: Implemented new alias filters.

Showing 44 changed files with 1399 additions and 126 deletions
... ... @@ -250,6 +250,21 @@ public class EntityRelationController extends BaseController {
250 250 }
251 251 }
252 252
  253 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
  254 + @RequestMapping(value = "/relations/info", method = RequestMethod.POST)
  255 + @ResponseBody
  256 + public List<EntityRelationInfo> findInfoByQuery(@RequestBody EntityRelationsQuery query) throws ThingsboardException {
  257 + checkNotNull(query);
  258 + checkNotNull(query.getParameters());
  259 + checkNotNull(query.getFilters());
  260 + checkEntityId(query.getParameters().getEntityId());
  261 + try {
  262 + return checkNotNull(relationService.findInfoByQuery(query).get());
  263 + } catch (Exception e) {
  264 + throw handleException(e);
  265 + }
  266 + }
  267 +
253 268 private RelationTypeGroup parseRelationTypeGroup(String strRelationTypeGroup, RelationTypeGroup defaultValue) {
254 269 RelationTypeGroup result = defaultValue;
255 270 if (strRelationTypeGroup != null && strRelationTypeGroup.trim().length()>0) {
... ...
... ... @@ -191,7 +191,7 @@ public class BaseRelationService implements RelationService {
191 191
192 192 @Override
193 193 public ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query) {
194   - log.trace("Executing findByQuery [{}][{}]", query);
  194 + log.trace("Executing findByQuery [{}]", query);
195 195 RelationsSearchParameters params = query.getParameters();
196 196 final List<EntityTypeFilter> filters = query.getFilters();
197 197 if (filters == null || filters.isEmpty()) {
... ... @@ -224,6 +224,30 @@ public class BaseRelationService implements RelationService {
224 224 }
225 225 }
226 226
  227 + @Override
  228 + public ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(EntityRelationsQuery query) {
  229 + log.trace("Executing findInfoByQuery [{}]", query);
  230 + ListenableFuture<List<EntityRelation>> relations = findByQuery(query);
  231 + EntitySearchDirection direction = query.getParameters().getDirection();
  232 + ListenableFuture<List<EntityRelationInfo>> relationsInfo = Futures.transform(relations,
  233 + (AsyncFunction<List<EntityRelation>, List<EntityRelationInfo>>) relations1 -> {
  234 + List<ListenableFuture<EntityRelationInfo>> futures = new ArrayList<>();
  235 + relations1.stream().forEach(relation ->
  236 + futures.add(fetchRelationInfoAsync(relation,
  237 + relation2 -> direction == EntitySearchDirection.FROM ? relation2.getTo() : relation2.getFrom(),
  238 + (EntityRelationInfo relationInfo, String entityName) -> {
  239 + if (direction == EntitySearchDirection.FROM) {
  240 + relationInfo.setToName(entityName);
  241 + } else {
  242 + relationInfo.setFromName(entityName);
  243 + }
  244 + }))
  245 + );
  246 + return Futures.successfulAsList(futures);
  247 + });
  248 + return relationsInfo;
  249 + }
  250 +
227 251 protected void validate(EntityRelation relation) {
228 252 if (relation == null) {
229 253 throw new DataValidationException("Relation type should be specified!");
... ...
... ... @@ -52,6 +52,8 @@ public interface RelationService {
52 52
53 53 ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query);
54 54
  55 + ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(EntityRelationsQuery query);
  56 +
55 57 // TODO: This method may be useful for some validations in the future
56 58 // ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to);
57 59
... ...
... ... @@ -30,7 +30,8 @@ function EntityRelationService($http, $q) {
30 30 findByTo: findByTo,
31 31 findInfoByTo: findInfoByTo,
32 32 findByToAndType: findByToAndType,
33   - findByQuery: findByQuery
  33 + findByQuery: findByQuery,
  34 + findInfoByQuery: findInfoByQuery
34 35 }
35 36
36 37 return service;
... ... @@ -159,4 +160,15 @@ function EntityRelationService($http, $q) {
159 160 return deferred.promise;
160 161 }
161 162
  163 + function findInfoByQuery(query) {
  164 + var deferred = $q.defer();
  165 + var url = '/api/relations/info';
  166 + $http.post(url, query).then(function success(response) {
  167 + deferred.resolve(response.data);
  168 + }, function fail() {
  169 + deferred.reject();
  170 + });
  171 + return deferred.promise;
  172 + }
  173 +
162 174 }
... ...
... ... @@ -32,6 +32,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
32 32 checkEntityAlias: checkEntityAlias,
33 33 filterAliasByEntityTypes: filterAliasByEntityTypes,
34 34 getAliasFilterTypesByEntityTypes: getAliasFilterTypesByEntityTypes,
  35 + prepareAllowedEntityTypesList: prepareAllowedEntityTypesList,
35 36 getEntityKeys: getEntityKeys,
36 37 createDatasourcesFromSubscriptionsInfo: createDatasourcesFromSubscriptionsInfo,
37 38 getRelatedEntities: getRelatedEntities,
... ... @@ -176,6 +177,54 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
176 177 return deferred.promise;
177 178 }
178 179
  180 + function getSingleTenantByPageLinkPromise(pageLink) {
  181 + var user = userService.getCurrentUser();
  182 + var tenantId = user.tenantId;
  183 + var deferred = $q.defer();
  184 + tenantService.getTenant(tenantId).then(
  185 + function success(tenant) {
  186 + var tenantName = tenant.name;
  187 + var result = {
  188 + data: [],
  189 + nextPageLink: pageLink,
  190 + hasNext: false
  191 + };
  192 + if (tenantName.toLowerCase().startsWith(pageLink.textSearch)) {
  193 + result.data.push(tenant);
  194 + }
  195 + deferred.resolve(result);
  196 + },
  197 + function fail() {
  198 + deferred.reject();
  199 + }
  200 + );
  201 + return deferred.promise;
  202 + }
  203 +
  204 + function getSingleCustomerByPageLinkPromise(pageLink) {
  205 + var user = userService.getCurrentUser();
  206 + var customerId = user.customerId;
  207 + var deferred = $q.defer();
  208 + customerService.getCustomer(customerId).then(
  209 + function success(customer) {
  210 + var customerName = customer.name;
  211 + var result = {
  212 + data: [],
  213 + nextPageLink: pageLink,
  214 + hasNext: false
  215 + };
  216 + if (customerName.toLowerCase().startsWith(pageLink.textSearch)) {
  217 + result.data.push(customer);
  218 + }
  219 + deferred.resolve(result);
  220 + },
  221 + function fail() {
  222 + deferred.reject();
  223 + }
  224 + );
  225 + return deferred.promise;
  226 + }
  227 +
179 228 function getEntitiesByPageLinkPromise(entityType, pageLink, config, subType) {
180 229 var promise;
181 230 var user = userService.getCurrentUser();
... ... @@ -196,10 +245,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
196 245 }
197 246 break;
198 247 case types.entityType.tenant:
199   - promise = tenantService.getTenants(pageLink);
  248 + if (user.authority === 'TENANT_ADMIN') {
  249 + promise = getSingleTenantByPageLinkPromise(pageLink);
  250 + } else {
  251 + promise = tenantService.getTenants(pageLink);
  252 + }
200 253 break;
201 254 case types.entityType.customer:
202   - promise = customerService.getCustomers(pageLink);
  255 + if (user.authority === 'CUSTOMER_USER') {
  256 + promise = getSingleCustomerByPageLinkPromise(pageLink);
  257 + } else {
  258 + promise = customerService.getCustomers(pageLink);
  259 + }
203 260 break;
204 261 case types.entityType.rule:
205 262 promise = ruleService.getAllRules(pageLink);
... ... @@ -283,6 +340,16 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
283 340 return { name: entity.name, entityType: entity.id.entityType, id: entity.id.id };
284 341 }
285 342
  343 + function entityRelationInfoToEntityInfo(entityRelationInfo, direction) {
  344 + var entityId = direction == types.entitySearchDirection.from ? entityRelationInfo.to : entityRelationInfo.from;
  345 + var name = direction == types.entitySearchDirection.from ? entityRelationInfo.toName : entityRelationInfo.fromName;
  346 + return {
  347 + name: name,
  348 + entityType: entityId.entityType,
  349 + id: entityId.id
  350 + };
  351 + }
  352 +
286 353 function entitiesToEntitiesInfo(entities) {
287 354 var entitiesInfo = [];
288 355 for (var d = 0; d < entities.length; d++) {
... ... @@ -291,19 +358,26 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
291 358 return entitiesInfo;
292 359 }
293 360
  361 + function entityRelationInfosToEntitiesInfo(entityRelations, direction) {
  362 + var entitiesInfo = [];
  363 + for (var d = 0; d < entityRelations.length; d++) {
  364 + entitiesInfo.push(entityRelationInfoToEntityInfo(entityRelations[d], direction));
  365 + }
  366 + return entitiesInfo;
  367 + }
  368 +
  369 +
294 370 function resolveAlias(entityAlias, stateParams) {
295 371 var deferred = $q.defer();
296 372 var filter = entityAlias.filter;
297 373 resolveAliasFilter(filter, stateParams, -1).then(
298 374 function (result) {
299   - var entities = result.entities;
300 375 var aliasInfo = {
301 376 alias: entityAlias.alias,
302 377 stateEntity: result.stateEntity,
303 378 resolveMultiple: filter.resolveMultiple
304 379 };
305   - var resolvedEntities = entitiesToEntitiesInfo(entities);
306   - aliasInfo.resolvedEntities = resolvedEntities;
  380 + aliasInfo.resolvedEntities = result.entities;
307 381 aliasInfo.currentEntity = null;
308 382 if (aliasInfo.resolvedEntities.length) {
309 383 aliasInfo.currentEntity = aliasInfo.resolvedEntities[0];
... ... @@ -328,7 +402,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
328 402 getEntities(filter.entityType, filter.entityList).then(
329 403 function success(entities) {
330 404 if (entities && entities.length) {
331   - result.entities = entities;
  405 + result.entities = entitiesToEntitiesInfo(entities);
332 406 deferred.resolve(result);
333 407 } else {
334 408 deferred.reject();
... ... @@ -343,7 +417,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
343 417 getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems).then(
344 418 function success(entities) {
345 419 if (entities && entities.length) {
346   - result.entities = entities;
  420 + result.entities = entitiesToEntitiesInfo(entities);
347 421 deferred.resolve(result);
348 422 } else {
349 423 deferred.reject();
... ... @@ -359,7 +433,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
359 433 if (stateParams && stateParams.entityId) {
360 434 getEntity(stateParams.entityId.entityType, stateParams.entityId.id).then(
361 435 function success(entity) {
362   - result.entities = [entity];
  436 + result.entities = entitiesToEntitiesInfo([entity]);
363 437 deferred.resolve(result);
364 438 },
365 439 function fail() {
... ... @@ -374,7 +448,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
374 448 getEntitiesByNameFilter(types.entityType.asset, filter.assetNameFilter, maxItems, null, filter.assetType).then(
375 449 function success(entities) {
376 450 if (entities && entities.length) {
377   - result.entities = entities;
  451 + result.entities = entitiesToEntitiesInfo(entities);
378 452 deferred.resolve(result);
379 453 } else {
380 454 deferred.reject();
... ... @@ -389,7 +463,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
389 463 getEntitiesByNameFilter(types.entityType.device, filter.deviceNameFilter, maxItems, null, filter.deviceType).then(
390 464 function success(entities) {
391 465 if (entities && entities.length) {
392   - result.entities = entities;
  466 + result.entities = entitiesToEntitiesInfo(entities);
393 467 deferred.resolve(result);
394 468 } else {
395 469 deferred.reject();
... ... @@ -400,8 +474,97 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
400 474 }
401 475 );
402 476 break;
403   -
404   - //TODO: Alias filter
  477 + case types.aliasFilterType.relationsQuery.value:
  478 + result.stateEntity = filter.rootStateEntity;
  479 + var rootEntityType;
  480 + var rootEntityId;
  481 + if (result.stateEntity && stateParams && stateParams.entityId) {
  482 + rootEntityType = stateParams.entityId.entityType;
  483 + rootEntityId = stateParams.entityId.id;
  484 + } else if (!result.stateEntity) {
  485 + rootEntityType = filter.rootEntity.entityType;
  486 + rootEntityId = filter.rootEntity.id;
  487 + }
  488 + if (rootEntityType && rootEntityId) {
  489 + var searchQuery = {
  490 + parameters: {
  491 + rootId: rootEntityId,
  492 + rootType: rootEntityType,
  493 + direction: filter.direction
  494 + },
  495 + filters: filter.filters
  496 + };
  497 + searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1;
  498 + entityRelationService.findInfoByQuery(searchQuery).then(
  499 + function success(allRelations) {
  500 + if (allRelations && allRelations.length) {
  501 + if (angular.isDefined(maxItems) && maxItems > 0) {
  502 + var limit = Math.min(allRelations.length, maxItems);
  503 + allRelations.length = limit;
  504 + }
  505 + result.entities = entityRelationInfosToEntitiesInfo(allRelations, filter.direction);
  506 + deferred.resolve(result);
  507 + } else {
  508 + deferred.reject();
  509 + }
  510 + },
  511 + function fail() {
  512 + deferred.reject();
  513 + }
  514 + );
  515 + } else {
  516 + deferred.resolve(result);
  517 + }
  518 + break;
  519 + case types.aliasFilterType.assetSearchQuery.value:
  520 + case types.aliasFilterType.deviceSearchQuery.value:
  521 + result.stateEntity = filter.rootStateEntity;
  522 + if (result.stateEntity && stateParams && stateParams.entityId) {
  523 + rootEntityType = stateParams.entityId.entityType;
  524 + rootEntityId = stateParams.entityId.id;
  525 + } else if (!result.stateEntity) {
  526 + rootEntityType = filter.rootEntity.entityType;
  527 + rootEntityId = filter.rootEntity.id;
  528 + }
  529 + if (rootEntityType && rootEntityId) {
  530 + searchQuery = {
  531 + parameters: {
  532 + rootId: rootEntityId,
  533 + rootType: rootEntityType,
  534 + direction: filter.direction
  535 + },
  536 + relationType: filter.relationType
  537 + };
  538 + searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1;
  539 + var findByQueryPromise;
  540 + if (filter.type == types.aliasFilterType.assetSearchQuery.value) {
  541 + searchQuery.assetTypes = filter.assetTypes;
  542 + findByQueryPromise = assetService.findByQuery(searchQuery, false);
  543 + } else if (filter.type == types.aliasFilterType.deviceSearchQuery.value) {
  544 + searchQuery.deviceTypes = filter.deviceTypes;
  545 + findByQueryPromise = deviceService.findByQuery(searchQuery, false);
  546 + }
  547 + findByQueryPromise.then(
  548 + function success(entities) {
  549 + if (entities && entities.length) {
  550 + if (angular.isDefined(maxItems) && maxItems > 0) {
  551 + var limit = Math.min(entities.length, maxItems);
  552 + entities.length = limit;
  553 + }
  554 + result.entities = entitiesToEntitiesInfo(entities);
  555 + deferred.resolve(result);
  556 + } else {
  557 + deferred.reject();
  558 + }
  559 + },
  560 + function fail() {
  561 + deferred.reject();
  562 + }
  563 + );
  564 + } else {
  565 + deferred.resolve(result);
  566 + }
  567 + break;
405 568 }
406 569 return deferred.promise;
407 570 }
... ... @@ -420,9 +583,33 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
420 583 return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false;
421 584 case types.aliasFilterType.deviceType.value:
422 585 return entityTypes.indexOf(types.entityType.device) > -1 ? true : false;
  586 + case types.aliasFilterType.relationsQuery.value:
  587 + if (filter.filters && filter.filters.length) {
  588 + var match = false;
  589 + for (var f=0;f<filter.filters.length;f++) {
  590 + var relationFilter = filter.filters[f];
  591 + if (relationFilter.entityTypes && relationFilter.entityTypes.length) {
  592 + for (var et=0;et<relationFilter.entityTypes.length;et++) {
  593 + if (entityTypes.indexOf(relationFilter.entityTypes[et]) > -1) {
  594 + match = true;
  595 + break;
  596 + }
  597 + }
  598 + } else {
  599 + match = true;
  600 + break;
  601 + }
  602 + }
  603 + return match;
  604 + } else {
  605 + return true;
  606 + }
  607 + case types.aliasFilterType.assetSearchQuery.value:
  608 + return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false;
  609 + case types.aliasFilterType.deviceSearchQuery.value:
  610 + return entityTypes.indexOf(types.entityType.device) > -1 ? true : false;
423 611 }
424 612 }
425   - //TODO: Alias filter
426 613 return false;
427 614 }
428 615
... ... @@ -474,6 +661,42 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
474 661 return result;
475 662 }
476 663
  664 + function prepareAllowedEntityTypesList(allowedEntityTypes) {
  665 + var authority = userService.getAuthority();
  666 + var entityTypes = {};
  667 + switch(authority) {
  668 + case 'SYS_ADMIN':
  669 + entityTypes.tenant = types.entityType.tenant;
  670 + entityTypes.rule = types.entityType.rule;
  671 + entityTypes.plugin = types.entityType.plugin;
  672 + break;
  673 + case 'TENANT_ADMIN':
  674 + entityTypes.device = types.entityType.device;
  675 + entityTypes.asset = types.entityType.asset;
  676 + entityTypes.tenant = types.entityType.tenant;
  677 + entityTypes.customer = types.entityType.customer;
  678 + entityTypes.rule = types.entityType.rule;
  679 + entityTypes.plugin = types.entityType.plugin;
  680 + entityTypes.dashboard = types.entityType.dashboard;
  681 + break;
  682 + case 'CUSTOMER_USER':
  683 + entityTypes.device = types.entityType.device;
  684 + entityTypes.asset = types.entityType.asset;
  685 + entityTypes.customer = types.entityType.customer;
  686 + entityTypes.dashboard = types.entityType.dashboard;
  687 + break;
  688 + }
  689 +
  690 + if (allowedEntityTypes) {
  691 + for (var entityType in entityTypes) {
  692 + if (allowedEntityTypes.indexOf(entityTypes[entityType]) === -1) {
  693 + delete entityTypes[entityType];
  694 + }
  695 + }
  696 + }
  697 + return entityTypes;
  698 + }
  699 +
477 700
478 701 function checkEntityAlias(entityAlias) {
479 702 var deferred = $q.defer();
... ...
... ... @@ -146,46 +146,55 @@ export default angular.module('thingsboard.types', [])
146 146 entityTypeTranslations: {
147 147 "DEVICE": {
148 148 type: 'entity.type-device',
  149 + typePlural: 'entity.type-devices',
149 150 list: 'entity.list-of-devices',
150 151 nameStartsWith: 'entity.device-name-starts-with'
151 152 },
152 153 "ASSET": {
153 154 type: 'entity.type-asset',
  155 + typePlural: 'entity.type-assets',
154 156 list: 'entity.list-of-assets',
155 157 nameStartsWith: 'entity.asset-name-starts-with'
156 158 },
157 159 "RULE": {
158 160 type: 'entity.type-rule',
  161 + typePlural: 'entity.type-rules',
159 162 list: 'entity.list-of-rules',
160 163 nameStartsWith: 'entity.rule-name-starts-with'
161 164 },
162 165 "PLUGIN": {
163 166 type: 'entity.type-plugin',
  167 + typePlural: 'entity.type-plugins',
164 168 list: 'entity.list-of-plugins',
165 169 nameStartsWith: 'entity.plugin-name-starts-with'
166 170 },
167 171 "TENANT": {
168 172 type: 'entity.type-tenant',
  173 + typePlural: 'entity.type-tenants',
169 174 list: 'entity.list-of-tenants',
170 175 nameStartsWith: 'entity.tenant-name-starts-with'
171 176 },
172 177 "CUSTOMER": {
173 178 type: 'entity.type-customer',
  179 + typePlural: 'entity.type-customers',
174 180 list: 'entity.list-of-customers',
175 181 nameStartsWith: 'entity.customer-name-starts-with'
176 182 },
177 183 "USER": {
178 184 type: 'entity.type-user',
  185 + typePlural: 'entity.type-users',
179 186 list: 'entity.list-of-users',
180 187 nameStartsWith: 'entity.user-name-starts-with'
181 188 },
182 189 "DASHBOARD": {
183 190 type: 'entity.type-dashboard',
  191 + typePlural: 'entity.type-dashboards',
184 192 list: 'entity.list-of-dashboards',
185 193 nameStartsWith: 'entity.dashboard-name-starts-with'
186 194 },
187 195 "ALARM": {
188 196 type: 'entity.type-alarm',
  197 + typePlural: 'entity.type-alarms',
189 198 list: 'entity.list-of-alarms',
190 199 nameStartsWith: 'entity.alarm-name-starts-with'
191 200 }
... ...
... ... @@ -71,6 +71,13 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
71 71 }
72 72 }, true);
73 73
  74 + scope.$watch('datasourceName', function () {
  75 + if (ngModelCtrl.$viewValue) {
  76 + ngModelCtrl.$viewValue.name = scope.datasourceName;
  77 + scope.updateValidity();
  78 + }
  79 + });
  80 +
74 81 ngModelCtrl.$render = function () {
75 82 if (ngModelCtrl.$viewValue) {
76 83 var funcDataKeys = [];
... ... @@ -78,6 +85,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
78 85 funcDataKeys = funcDataKeys.concat(ngModelCtrl.$viewValue.dataKeys);
79 86 }
80 87 scope.funcDataKeys = funcDataKeys;
  88 + scope.datasourceName = ngModelCtrl.$viewValue.name;
81 89 }
82 90 };
83 91
... ...
... ... @@ -15,23 +15,29 @@
15 15 */
16 16 @import '../../scss/constants';
17 17
18   -.tb-func-datakey-autocomplete {
19   - .tb-not-found {
20   - display: block;
21   - line-height: 1.5;
22   - height: 48px;
23   - .tb-no-entries {
24   - line-height: 48px;
25   - }
  18 +.tb-datasource-func {
  19 + @media (min-width: $layout-breakpoint-gt-sm) {
  20 + padding-left: 8px;
26 21 }
27   - li {
28   - height: auto !important;
29   - white-space: normal !important;
  22 +
  23 + md-input-container.tb-datasource-name {
  24 + .md-errors-spacer {
  25 + display: none;
  26 + }
30 27 }
31   -}
32 28
33   -tb-datasource-func {
34   - @media (min-width: $layout-breakpoint-gt-sm) {
35   - padding-left: 8px;
  29 + .tb-func-datakey-autocomplete {
  30 + .tb-not-found {
  31 + display: block;
  32 + line-height: 1.5;
  33 + height: 48px;
  34 + .tb-no-entries {
  35 + line-height: 48px;
  36 + }
  37 + }
  38 + li {
  39 + height: auto !important;
  40 + white-space: normal !important;
  41 + }
36 42 }
37   -}
\ No newline at end of file
  43 +}
... ...
... ... @@ -15,59 +15,68 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<section flex layout='column' style="padding-left: 4px;">
19   - <md-chips flex
20   - id="function_datakey_chips"
21   - ng-required="true"
22   - ng-model="funcDataKeys" md-autocomplete-snap
23   - md-transform-chip="transformDataKeyChip($chip)"
24   - md-require-match="false">
25   - <md-autocomplete
26   - md-no-cache="false"
27   - id="dataKey"
28   - md-selected-item="selectedDataKey"
29   - md-search-text="dataKeySearchText"
30   - md-items="item in dataKeysSearch(dataKeySearchText)"
31   - md-item-text="item.name"
32   - md-min-length="0"
33   - placeholder="{{ 'datakey.function-types' | translate }}"
34   - md-menu-class="tb-func-datakey-autocomplete">
35   - <span md-highlight-text="dataKeySearchText" md-highlight-flags="^i">{{item}}</span>
36   - <md-not-found>
37   - <div class="tb-not-found">
38   - <div class="tb-no-entries" ng-if="!textIsNotEmpty(dataKeySearchText)">
39   - <span translate>device.no-keys-found</span>
  18 +<section class="tb-datasource-func" flex layout='column'
  19 + layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
  20 + <md-input-container class="tb-datasource-name" md-no-float style="min-width: 200px;">
  21 + <input name="datasourceName"
  22 + placeholder="{{ 'datasource.name' | translate }}"
  23 + ng-model="datasourceName"
  24 + aria-label="{{ 'datasource.name' | translate }}">
  25 + </md-input-container>
  26 + <section flex layout='column' style="padding-left: 4px;">
  27 + <md-chips flex
  28 + id="function_datakey_chips"
  29 + ng-required="true"
  30 + ng-model="funcDataKeys" md-autocomplete-snap
  31 + md-transform-chip="transformDataKeyChip($chip)"
  32 + md-require-match="false">
  33 + <md-autocomplete
  34 + md-no-cache="false"
  35 + id="dataKey"
  36 + md-selected-item="selectedDataKey"
  37 + md-search-text="dataKeySearchText"
  38 + md-items="item in dataKeysSearch(dataKeySearchText)"
  39 + md-item-text="item.name"
  40 + md-min-length="0"
  41 + placeholder="{{ 'datakey.function-types' | translate }}"
  42 + md-menu-class="tb-func-datakey-autocomplete">
  43 + <span md-highlight-text="dataKeySearchText" md-highlight-flags="^i">{{item}}</span>
  44 + <md-not-found>
  45 + <div class="tb-not-found">
  46 + <div class="tb-no-entries" ng-if="!textIsNotEmpty(dataKeySearchText)">
  47 + <span translate>device.no-keys-found</span>
  48 + </div>
  49 + <div ng-if="textIsNotEmpty(dataKeySearchText)">
  50 + <span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>device.no-key-matching</span>
  51 + <span>
  52 + <a translate ng-click="createKey($event, '#function_datakey_chips')">device.create-new-key</a>
  53 + </span>
  54 + </div>
40 55 </div>
41   - <div ng-if="textIsNotEmpty(dataKeySearchText)">
42   - <span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>device.no-key-matching</span>
43   - <span>
44   - <a translate ng-click="createKey($event, '#function_datakey_chips')">device.create-new-key</a>
45   - </span>
46   - </div>
47   - </div>
48   - </md-not-found>
49   - </md-autocomplete>
50   - <md-chip-template>
51   - <div layout="row" layout-align="start center" class="tb-attribute-chip">
52   - <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
53   - <div class="tb-color-result" ng-style="{background: $chip.color}"></div>
54   - </div>
55   - <div layout="row" flex>
56   - <div class="tb-chip-label">
57   - {{$chip.label}}
  56 + </md-not-found>
  57 + </md-autocomplete>
  58 + <md-chip-template>
  59 + <div layout="row" layout-align="start center" class="tb-attribute-chip">
  60 + <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
  61 + <div class="tb-color-result" ng-style="{background: $chip.color}"></div>
58 62 </div>
59   - <div class="tb-chip-separator">: </div>
60   - <div class="tb-chip-label">
61   - <strong>{{$chip.name}}</strong>
  63 + <div layout="row" flex>
  64 + <div class="tb-chip-label">
  65 + {{$chip.label}}
  66 + </div>
  67 + <div class="tb-chip-separator">: </div>
  68 + <div class="tb-chip-label">
  69 + <strong>{{$chip.name}}</strong>
  70 + </div>
62 71 </div>
  72 + <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
  73 + <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
  74 + </md-button>
63 75 </div>
64   - <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
65   - <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
66   - </md-button>
67   - </div>
68   - </md-chip-template>
69   - </md-chips>
70   - <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
71   - <div translate ng-message="funcTypes" class="tb-error-message">datakey.function-types-required</div>
72   - </div>
  76 + </md-chip-template>
  77 + </md-chips>
  78 + <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
  79 + <div translate ng-message="funcTypes" class="tb-error-message">datakey.function-types-required</div>
  80 + </div>
  81 + </section>
73 82 </section>
... ...
... ... @@ -15,7 +15,7 @@
15 15 */
16 16 /* eslint-disable import/no-unresolved, import/default */
17 17
18   -import entityAliasDialogTemplate from '../entity/entity-alias-dialog.tpl.html';
  18 +import entityAliasDialogTemplate from '../entity/alias/entity-alias-dialog.tpl.html';
19 19
20 20 /* eslint-enable import/no-unresolved, import/default */
21 21
... ...
... ... @@ -15,7 +15,7 @@
15 15 */
16 16 /* eslint-disable import/no-unresolved, import/default */
17 17
18   -import entityAliasesTemplate from '../entity/entity-aliases.tpl.html';
  18 +import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html';
19 19 import dashboardSettingsTemplate from './dashboard-settings.tpl.html';
20 20 import manageDashboardLayoutsTemplate from './layouts/manage-dashboard-layouts.tpl.html';
21 21 import manageDashboardStatesTemplate from './states/manage-dashboard-states.tpl.html';
... ...
... ... @@ -15,7 +15,7 @@
15 15 */
16 16 /* eslint-disable import/no-unresolved, import/default */
17 17
18   -import entityAliasDialogTemplate from '../entity/entity-alias-dialog.tpl.html';
  18 +import entityAliasDialogTemplate from '../entity/alias/entity-alias-dialog.tpl.html';
19 19 import editWidgetTemplate from './edit-widget.tpl.html';
20 20
21 21 /* eslint-enable import/no-unresolved, import/default */
... ...
ui/src/app/entity/alias/aliases-entity-select-button.tpl.html renamed from ui/src/app/entity/aliases-entity-select-button.tpl.html
ui/src/app/entity/alias/aliases-entity-select-panel.controller.js renamed from ui/src/app/entity/aliases-entity-select-panel.controller.js
ui/src/app/entity/alias/aliases-entity-select-panel.tpl.html renamed from ui/src/app/entity/aliases-entity-select-panel.tpl.html
ui/src/app/entity/alias/aliases-entity-select.directive.js renamed from ui/src/app/entity/aliases-entity-select.directive.js
ui/src/app/entity/alias/aliases-entity-select.scss renamed from ui/src/app/entity/aliases-entity-select.scss
ui/src/app/entity/alias/entity-alias-dialog.controller.js renamed from ui/src/app/entity/entity-alias-dialog.controller.js
ui/src/app/entity/alias/entity-alias-dialog.scss renamed from ui/src/app/entity/entity-alias-dialog.scss
ui/src/app/entity/alias/entity-alias-dialog.tpl.html renamed from ui/src/app/entity/entity-alias-dialog.tpl.html
ui/src/app/entity/alias/entity-aliases.controller.js renamed from ui/src/app/entity/entity-aliases.controller.js
ui/src/app/entity/alias/entity-aliases.scss renamed from ui/src/app/entity/entity-aliases.scss
ui/src/app/entity/alias/entity-aliases.tpl.html renamed from ui/src/app/entity/entity-aliases.tpl.html
... ... @@ -74,7 +74,106 @@ export default function EntityFilterViewDirective($compile, $templateCache, $q,
74 74 scope.filterDisplayValue = $translate.instant('alias.filter-type-device-type-description', {deviceType: deviceType});
75 75 }
76 76 break;
77   - //TODO: Alias filter
  77 + case types.aliasFilterType.relationsQuery.value:
  78 + var rootEntityText;
  79 + var directionText;
  80 + var allEntitiesText = $translate.instant('alias.all-entities');
  81 + var anyRelationText = $translate.instant('alias.any-relation');
  82 + if (scope.filter.rootStateEntity) {
  83 + rootEntityText = $translate.instant('alias.state-entity');
  84 + } else {
  85 + rootEntityText = $translate.instant(types.entityTypeTranslations[scope.filter.rootEntity.entityType].type);
  86 + }
  87 + directionText = $translate.instant('relation.direction-type.' + scope.filter.direction);
  88 + var relationFilters = scope.filter.filters;
  89 + if (relationFilters && relationFilters.length) {
  90 + var relationFiltersDisplayValues = [];
  91 + relationFilters.forEach(function(relationFilter) {
  92 + var entitiesText;
  93 + if (relationFilter.entityTypes && relationFilter.entityTypes.length) {
  94 + var entitiesNamesList = [];
  95 + relationFilter.entityTypes.forEach(function(entityType) {
  96 + entitiesNamesList.push(
  97 + $translate.instant(types.entityTypeTranslations[entityType].typePlural)
  98 + );
  99 + });
  100 + entitiesText = entitiesNamesList.join(', ');
  101 + } else {
  102 + entitiesText = allEntitiesText;
  103 + }
  104 + var relationTypeText;
  105 + if (relationFilter.relationType && relationFilter.relationType.length) {
  106 + relationTypeText = "'" + relationFilter.relationType + "'";
  107 + } else {
  108 + relationTypeText = anyRelationText;
  109 + }
  110 + var relationFilterDisplayValue = $translate.instant('alias.filter-type-relations-query-description',
  111 + {
  112 + entities: entitiesText,
  113 + relationType: relationTypeText,
  114 + direction: directionText,
  115 + rootEntity: rootEntityText
  116 + }
  117 + );
  118 + relationFiltersDisplayValues.push(relationFilterDisplayValue);
  119 + });
  120 + scope.filterDisplayValue = relationFiltersDisplayValues.join(', ');
  121 + } else {
  122 + scope.filterDisplayValue = $translate.instant('alias.filter-type-relations-query-description',
  123 + {
  124 + entities: allEntitiesText,
  125 + relationType: anyRelationText,
  126 + direction: directionText,
  127 + rootEntity: rootEntityText
  128 + }
  129 + );
  130 + }
  131 + break;
  132 + case types.aliasFilterType.assetSearchQuery.value:
  133 + case types.aliasFilterType.deviceSearchQuery.value:
  134 + allEntitiesText = $translate.instant('alias.all-entities');
  135 + anyRelationText = $translate.instant('alias.any-relation');
  136 + if (scope.filter.rootStateEntity) {
  137 + rootEntityText = $translate.instant('alias.state-entity');
  138 + } else {
  139 + rootEntityText = $translate.instant(types.entityTypeTranslations[scope.filter.rootEntity.entityType].type);
  140 + }
  141 + directionText = $translate.instant('relation.direction-type.' + scope.filter.direction);
  142 + var relationTypeText;
  143 + if (scope.filter.relationType && scope.filter.relationType.length) {
  144 + relationTypeText = "'" + scope.filter.relationType + "'";
  145 + } else {
  146 + relationTypeText = anyRelationText;
  147 + }
  148 +
  149 + var translationValues = {
  150 + relationType: relationTypeText,
  151 + direction: directionText,
  152 + rootEntity: rootEntityText
  153 + }
  154 +
  155 + if (scope.filter.type == types.aliasFilterType.assetSearchQuery.value) {
  156 + var assetTypesQuoted = [];
  157 + scope.filter.assetTypes.forEach(function(assetType) {
  158 + assetTypesQuoted.push("'"+assetType+"'");
  159 + });
  160 + var assetTypesText = assetTypesQuoted.join(', ');
  161 + translationValues.assetTypes = assetTypesText;
  162 + scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-search-query-description',
  163 + translationValues
  164 + );
  165 + } else {
  166 + var deviceTypesQuoted = [];
  167 + scope.filter.deviceTypes.forEach(function(deviceType) {
  168 + deviceTypesQuoted.push("'"+deviceType+"'");
  169 + });
  170 + var deviceTypesText = deviceTypesQuoted.join(', ');
  171 + translationValues.deviceTypes = deviceTypesText;
  172 + scope.filterDisplayValue = $translate.instant('alias.filter-type-device-search-query-description',
  173 + translationValues
  174 + );
  175 + }
  176 + break;
78 177 default:
79 178 scope.filterDisplayValue = scope.filter.type;
80 179 break;
... ...
... ... @@ -63,7 +63,23 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
63 63 filter.deviceType = null;
64 64 filter.deviceNameFilter = '';
65 65 break;
66   - //TODO: Alias filter
  66 + case types.aliasFilterType.relationsQuery.value:
  67 + case types.aliasFilterType.assetSearchQuery.value:
  68 + case types.aliasFilterType.deviceSearchQuery.value:
  69 + filter.rootStateEntity = false;
  70 + filter.rootEntity = null;
  71 + filter.direction = types.entitySearchDirection.from;
  72 + filter.maxLevel = 1;
  73 + if (filter.type === types.aliasFilterType.relationsQuery.value) {
  74 + filter.filters = [];
  75 + } else if (filter.type === types.aliasFilterType.assetSearchQuery.value) {
  76 + filter.relationType = null;
  77 + filter.assetTypes = [];
  78 + } else if (filter.type === types.aliasFilterType.deviceSearchQuery.value) {
  79 + filter.relationType = null;
  80 + filter.deviceTypes = [];
  81 + }
  82 + break;
67 83 }
68 84 scope.filter = filter;
69 85 }
... ...
... ... @@ -16,4 +16,21 @@
16 16
17 17 .tb-entity-filter {
18 18
  19 + #relationsQueryFilter {
  20 + padding-top: 20px;
  21 + tb-entity-select {
  22 + min-height: 92px;
  23 + }
  24 + }
  25 +
  26 + .tb-root-state-entity-switch {
  27 + padding-left: 10px;
  28 + .root-state-entity-switch {
  29 + margin: 0;
  30 + }
  31 + .root-state-entity-label {
  32 + margin: 5px 0;
  33 + }
  34 + }
  35 +
19 36 }
\ No newline at end of file
... ...
... ... @@ -88,4 +88,146 @@
88 88 aria-label="{{ 'device.name-starts-with' | translate }}">
89 89 </md-input-container>
90 90 </section>
  91 + <section layout="column" ng-if="filter.type == types.aliasFilterType.relationsQuery.value" id="relationsQueryFilter">
  92 + <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
  93 + <div flex layout="row">
  94 + <tb-entity-select flex
  95 + the-form="theForm"
  96 + tb-required="!filter.rootStateEntity"
  97 + ng-disabled="filter.rootStateEntity"
  98 + ng-model="filter.rootEntity">
  99 + </tb-entity-select>
  100 + <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
  101 + <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
  102 + <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
  103 + aria-label="{{ 'alias.root-state-entity' | translate }}">
  104 + </md-switch>
  105 + </section>
  106 + </div>
  107 + <div flex layout="row">
  108 + <md-input-container class="md-block" style="min-width: 100px;">
  109 + <label translate>relation.direction</label>
  110 + <md-select required ng-model="filter.direction">
  111 + <md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction">
  112 + {{ ('relation.search-direction.' + direction) | translate}}
  113 + </md-option>
  114 + </md-select>
  115 + </md-input-container>
  116 + <md-input-container flex class="md-block">
  117 + <label translate>alias.max-relation-level</label>
  118 + <input name="maxRelationLevel"
  119 + type="number"
  120 + min="1"
  121 + step="1"
  122 + placeholder="{{ 'alias.unlimited-level' | translate }}"
  123 + ng-model="filter.maxLevel"
  124 + aria-label="{{ 'alias.max-relation-level' | translate }}">
  125 + </md-input-container>
  126 + </div>
  127 + <div class="md-caption" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>relation.relation-filters</div>
  128 + <tb-relation-filters
  129 + ng-model="filter.filters"
  130 + allowed-entity-types="allowedEntityTypes">
  131 + </tb-relation-filters>
  132 + </section>
  133 + <section layout="column" ng-if="filter.type == types.aliasFilterType.assetSearchQuery.value" id="assetSearchQueryFilter">
  134 + <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
  135 + <div flex layout="row">
  136 + <tb-entity-select flex
  137 + the-form="theForm"
  138 + tb-required="!filter.rootStateEntity"
  139 + ng-disabled="filter.rootStateEntity"
  140 + ng-model="filter.rootEntity">
  141 + </tb-entity-select>
  142 + <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
  143 + <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
  144 + <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
  145 + aria-label="{{ 'alias.root-state-entity' | translate }}">
  146 + </md-switch>
  147 + </section>
  148 + </div>
  149 + <div flex layout="row">
  150 + <md-input-container class="md-block" style="min-width: 100px;">
  151 + <label translate>relation.direction</label>
  152 + <md-select required ng-model="filter.direction">
  153 + <md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction">
  154 + {{ ('relation.search-direction.' + direction) | translate}}
  155 + </md-option>
  156 + </md-select>
  157 + </md-input-container>
  158 + <md-input-container flex class="md-block">
  159 + <label translate>alias.max-relation-level</label>
  160 + <input name="maxRelationLevel"
  161 + type="number"
  162 + min="1"
  163 + step="1"
  164 + placeholder="{{ 'alias.unlimited-level' | translate }}"
  165 + ng-model="filter.maxLevel"
  166 + aria-label="{{ 'alias.max-relation-level' | translate }}">
  167 + </md-input-container>
  168 + </div>
  169 + <div class="md-caption" style="color: rgba(0,0,0,0.57);" translate>relation.relation-type</div>
  170 + <tb-relation-type-autocomplete flex
  171 + hide-label
  172 + the-form="theForm"
  173 + ng-model="filter.relationType"
  174 + tb-required="false">
  175 + </tb-relation-type-autocomplete>
  176 + <div class="md-caption tb-required" style="color: rgba(0,0,0,0.57);" translate>asset.asset-types</div>
  177 + <tb-entity-subtype-list
  178 + tb-required="true"
  179 + entity-type="types.entityType.asset"
  180 + ng-model="filter.assetTypes">
  181 + </tb-entity-subtype-list>
  182 + </section>
  183 + <section layout="column" ng-if="filter.type == types.aliasFilterType.deviceSearchQuery.value" id="deviceSearchQueryFilter">
  184 + <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
  185 + <div flex layout="row">
  186 + <tb-entity-select flex
  187 + the-form="theForm"
  188 + tb-required="!filter.rootStateEntity"
  189 + ng-disabled="filter.rootStateEntity"
  190 + ng-model="filter.rootEntity">
  191 + </tb-entity-select>
  192 + <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
  193 + <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
  194 + <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
  195 + aria-label="{{ 'alias.root-state-entity' | translate }}">
  196 + </md-switch>
  197 + </section>
  198 + </div>
  199 + <div flex layout="row">
  200 + <md-input-container class="md-block" style="min-width: 100px;">
  201 + <label translate>relation.direction</label>
  202 + <md-select required ng-model="filter.direction">
  203 + <md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction">
  204 + {{ ('relation.search-direction.' + direction) | translate}}
  205 + </md-option>
  206 + </md-select>
  207 + </md-input-container>
  208 + <md-input-container flex class="md-block">
  209 + <label translate>alias.max-relation-level</label>
  210 + <input name="maxRelationLevel"
  211 + type="number"
  212 + min="1"
  213 + step="1"
  214 + placeholder="{{ 'alias.unlimited-level' | translate }}"
  215 + ng-model="filter.maxLevel"
  216 + aria-label="{{ 'alias.max-relation-level' | translate }}">
  217 + </md-input-container>
  218 + </div>
  219 + <div class="md-caption" style="color: rgba(0,0,0,0.57);" translate>relation.relation-type</div>
  220 + <tb-relation-type-autocomplete flex
  221 + hide-label
  222 + the-form="theForm"
  223 + ng-model="filter.relationType"
  224 + tb-required="false">
  225 + </tb-relation-type-autocomplete>
  226 + <div class="md-caption tb-required" style="color: rgba(0,0,0,0.57);" translate>device.device-types</div>
  227 + <tb-entity-subtype-list
  228 + tb-required="true"
  229 + entity-type="types.entityType.device"
  230 + ng-model="filter.deviceTypes">
  231 + </tb-entity-subtype-list>
  232 + </section>
91 233 </div>
... ...
... ... @@ -114,6 +114,9 @@ export default function EntitySubtypeAutocomplete($compile, $templateCache, $q,
114 114 scope.selectEntitySubtypeText = 'asset.select-asset-type';
115 115 scope.entitySubtypeText = 'asset.asset-type';
116 116 scope.entitySubtypeRequiredText = 'asset.asset-type-required';
  117 + scope.$on('assetSaved', function() {
  118 + scope.entitySubtypes = null;
  119 + });
117 120 } else if (scope.entityType == types.entityType.device) {
118 121 scope.selectEntitySubtypeText = 'device.select-device-type';
119 122 scope.entitySubtypeText = 'device.device-type';
... ...
  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 +
  17 +/* eslint-disable import/no-unresolved, import/default */
  18 +
  19 +import entitySubtypeListTemplate from './entity-subtype-list.tpl.html';
  20 +
  21 +/* eslint-enable import/no-unresolved, import/default */
  22 +
  23 +import './entity-subtype-list.scss';
  24 +
  25 +/*@ngInject*/
  26 +export default function EntitySubtypeListDirective($compile, $templateCache, $q, $mdUtil, $translate, $filter, types, assetService, deviceService) {
  27 +
  28 + var linker = function (scope, element, attrs, ngModelCtrl) {
  29 +
  30 + var template = $templateCache.get(entitySubtypeListTemplate);
  31 + element.html(template);
  32 +
  33 + scope.ngModelCtrl = ngModelCtrl;
  34 +
  35 +
  36 + scope.entitySubtypesList = [];
  37 + scope.entitySubtypes = null;
  38 +
  39 + if (scope.entityType == types.entityType.asset) {
  40 + scope.placeholder = scope.tbRequired ? $translate.instant('asset.enter-asset-type')
  41 + : $translate.instant('asset.any-asset');
  42 + scope.secondaryPlaceholder = '+' + $translate.instant('asset.asset-type');
  43 + scope.noSubtypesMathingText = 'asset.no-asset-types-matching';
  44 + scope.subtypeListEmptyText = 'asset.asset-type-list-empty';
  45 + } else if (scope.entityType == types.entityType.device) {
  46 + scope.placeholder = scope.tbRequired ? $translate.instant('device.enter-device-type')
  47 + : $translate.instant('device.any-device');
  48 + scope.secondaryPlaceholder = '+' + $translate.instant('device.device-type');
  49 + scope.noSubtypesMathingText = 'device.no-device-types-matching';
  50 + scope.subtypeListEmptyText = 'device.device-type-list-empty';
  51 + }
  52 +
  53 + scope.$watch('tbRequired', function () {
  54 + scope.updateValidity();
  55 + });
  56 +
  57 + scope.fetchEntitySubtypes = function(searchText) {
  58 + var deferred = $q.defer();
  59 + loadSubTypes().then(
  60 + function success(subTypes) {
  61 + var result = $filter('filter')(subTypes, {'$': searchText});
  62 + if (result && result.length) {
  63 + deferred.resolve(result);
  64 + } else {
  65 + deferred.resolve([searchText]);
  66 + }
  67 + },
  68 + function fail() {
  69 + deferred.reject();
  70 + }
  71 + );
  72 + return deferred.promise;
  73 + }
  74 +
  75 + scope.updateValidity = function() {
  76 + var value = ngModelCtrl.$viewValue;
  77 + var valid = !scope.tbRequired || value && value.length > 0;
  78 + ngModelCtrl.$setValidity('entitySubtypesList', valid);
  79 + }
  80 +
  81 + ngModelCtrl.$render = function () {
  82 + scope.entitySubtypesList = ngModelCtrl.$viewValue;
  83 + if (!scope.entitySubtypesList) {
  84 + scope.entitySubtypesList = [];
  85 + }
  86 + }
  87 +
  88 + scope.$watch('entitySubtypesList', function () {
  89 + ngModelCtrl.$setViewValue(scope.entitySubtypesList);
  90 + scope.updateValidity();
  91 + }, true);
  92 +
  93 + function loadSubTypes() {
  94 + var deferred = $q.defer();
  95 + if (!scope.entitySubtypes) {
  96 + var entitySubtypesPromise;
  97 + if (scope.entityType == types.entityType.asset) {
  98 + entitySubtypesPromise = assetService.getAssetTypes();
  99 + } else if (scope.entityType == types.entityType.device) {
  100 + entitySubtypesPromise = deviceService.getDeviceTypes();
  101 + }
  102 + if (entitySubtypesPromise) {
  103 + entitySubtypesPromise.then(
  104 + function success(types) {
  105 + scope.entitySubtypes = [];
  106 + types.forEach(function (type) {
  107 + scope.entitySubtypes.push(type.type);
  108 + });
  109 + deferred.resolve(scope.entitySubtypes);
  110 + },
  111 + function fail() {
  112 + deferred.reject();
  113 + }
  114 + );
  115 + } else {
  116 + deferred.reject();
  117 + }
  118 + } else {
  119 + deferred.resolve(scope.entitySubtypes);
  120 + }
  121 + return deferred.promise;
  122 + }
  123 +
  124 + $compile(element.contents())(scope);
  125 +
  126 + $mdUtil.nextTick(function(){
  127 + var inputElement = angular.element('input', element);
  128 + inputElement.on('blur', function() {
  129 + scope.inputTouched = true;
  130 + } );
  131 + });
  132 +
  133 + }
  134 +
  135 + return {
  136 + restrict: "E",
  137 + require: "^ngModel",
  138 + link: linker,
  139 + scope: {
  140 + disabled:'=ngDisabled',
  141 + tbRequired: '=?',
  142 + entityType: "="
  143 + }
  144 + };
  145 +
  146 +}
... ...
  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 +
  17 +/*.tb-entity-subtype-list {
  18 + #entity_subtype_list_chips {
  19 + .md-chips {
  20 + padding-bottom: 1px;
  21 + }
  22 + }
  23 + .tb-error-messages {
  24 + margin-top: -11px;
  25 + height: 35px;
  26 + .tb-error-message {
  27 + padding-left: 1px;
  28 + }
  29 + }
  30 +}*/
... ...
  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 +
  19 +<section flex layout='column' class="tb-entity-subtype-list">
  20 + <md-chips flex
  21 + readonly="disabled"
  22 + id="entity_subtype_list_chips"
  23 + ng-required="tbRequired"
  24 + ng-model="entitySubtypesList"
  25 + placeholder="{{placeholder}}"
  26 + secondary-placeholder="{{secondaryPlaceholder}}"
  27 + md-autocomplete-snap
  28 + md-require-match="false">
  29 + <md-autocomplete
  30 + md-no-cache="true"
  31 + id="entitySubtype"
  32 + md-selected-item="selectedEntitySubtype"
  33 + md-search-text="entitySubtypeSearchText"
  34 + md-items="item in fetchEntitySubtypes(entitySubtypeSearchText)"
  35 + md-item-text="item"
  36 + md-min-length="0"
  37 + placeholder="{{ (!entitySubtypesList || !entitySubtypesList.length) ? placeholder : secondaryPlaceholder }}">
  38 + <md-item-template>
  39 + <span md-highlight-text="entitySubtypeSearchText" md-highlight-flags="^i">{{item}}</span>
  40 + </md-item-template>
  41 + <md-not-found>
  42 + <span translate translate-values='{ entitySubtype: entitySubtypeSearchText }'>{{noSubtypesMathingText}}</span>
  43 + </md-not-found>
  44 + </md-autocomplete>
  45 + <md-chip-template>
  46 + <span>
  47 + <strong>{{$chip}}</strong>
  48 + </span>
  49 + </md-chip-template>
  50 + </md-chips>
  51 + <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" ng-if="inputTouched && tbRequired" role="alert">
  52 + <div translate ng-message="entitySubtypesList" class="tb-error-message">{{subtypeListEmptyText}}</div>
  53 + </div>
  54 +</section>
... ...
  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 +
  17 +/* eslint-disable import/no-unresolved, import/default */
  18 +
  19 +import entityTypeListTemplate from './entity-type-list.tpl.html';
  20 +
  21 +/* eslint-enable import/no-unresolved, import/default */
  22 +
  23 +import './entity-type-list.scss';
  24 +
  25 +/*@ngInject*/
  26 +export default function EntityTypeListDirective($compile, $templateCache, $q, $mdUtil, $translate, $filter, types, entityService) {
  27 +
  28 + var linker = function (scope, element, attrs, ngModelCtrl) {
  29 +
  30 + var template = $templateCache.get(entityTypeListTemplate);
  31 + element.html(template);
  32 +
  33 + scope.ngModelCtrl = ngModelCtrl;
  34 +
  35 + scope.placeholder = scope.tbRequired ? $translate.instant('entity.enter-entity-type')
  36 + : $translate.instant('entity.any-entity');
  37 + scope.secondaryPlaceholder = '+' + $translate.instant('entity.entity-type');
  38 +
  39 + var entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes);
  40 + scope.entityTypesList = [];
  41 + for (var type in entityTypes) {
  42 + var entityTypeInfo = {};
  43 + entityTypeInfo.value = entityTypes[type];
  44 + entityTypeInfo.name = $translate.instant(types.entityTypeTranslations[entityTypeInfo.value].type) + '';
  45 + scope.entityTypesList.push(entityTypeInfo);
  46 + }
  47 +
  48 + scope.$watch('tbRequired', function () {
  49 + scope.updateValidity();
  50 + });
  51 +
  52 + scope.fetchEntityTypes = function(searchText) {
  53 + var deferred = $q.defer();
  54 + var entityTypes = $filter('filter')(scope.entityTypesList, {name: searchText});
  55 + deferred.resolve(entityTypes);
  56 + return deferred.promise;
  57 + }
  58 +
  59 + scope.updateValidity = function() {
  60 + var value = ngModelCtrl.$viewValue;
  61 + var valid = !scope.tbRequired || value && value.length > 0;
  62 + ngModelCtrl.$setValidity('entityTypeList', valid);
  63 + }
  64 +
  65 + ngModelCtrl.$render = function () {
  66 + scope.entityTypeList = [];
  67 + var value = ngModelCtrl.$viewValue;
  68 + if (value && value.length) {
  69 + value.forEach(function(type) {
  70 + var entityTypeInfo = {};
  71 + entityTypeInfo.value = type;
  72 + entityTypeInfo.name = $translate.instant(types.entityTypeTranslations[entityTypeInfo.value].type) + '';
  73 + scope.entityTypeList.push(entityTypeInfo);
  74 + });
  75 + }
  76 + }
  77 +
  78 + scope.$watch('entityTypeList', function () {
  79 + var values = [];
  80 + if (scope.entityTypeList && scope.entityTypeList.length) {
  81 + scope.entityTypeList.forEach(function(entityType) {
  82 + values.push(entityType.value);
  83 + });
  84 + }
  85 + ngModelCtrl.$setViewValue(values);
  86 + scope.updateValidity();
  87 + }, true);
  88 +
  89 + $compile(element.contents())(scope);
  90 +
  91 + $mdUtil.nextTick(function(){
  92 + var inputElement = angular.element('input', element);
  93 + inputElement.on('blur', function() {
  94 + scope.inputTouched = true;
  95 + } );
  96 + });
  97 +
  98 + }
  99 +
  100 + return {
  101 + restrict: "E",
  102 + require: "^ngModel",
  103 + link: linker,
  104 + scope: {
  105 + disabled:'=ngDisabled',
  106 + tbRequired: '=?',
  107 + allowedEntityTypes: '=?'
  108 + }
  109 + };
  110 +
  111 +}
... ...
  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 +
  17 +/*.tb-entity-type-list {
  18 + #entity_type_list_chips {
  19 + .md-chips {
  20 + padding-bottom: 1px;
  21 + }
  22 + }
  23 + .tb-error-messages {
  24 + margin-top: -11px;
  25 + height: 35px;
  26 + .tb-error-message {
  27 + padding-left: 1px;
  28 + }
  29 + }
  30 +}*/
\ No newline at end of file
... ...
  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 +
  19 +<section flex layout='column' class="tb-entity-type-list">
  20 + <md-chips flex
  21 + readonly="disabled"
  22 + id="entity_type_list_chips"
  23 + ng-required="tbRequired"
  24 + ng-model="entityTypeList"
  25 + placeholder="{{placeholder}}"
  26 + secondary-placeholder="{{secondaryPlaceholder}}"
  27 + md-autocomplete-snap
  28 + md-require-match="true">
  29 + <md-autocomplete
  30 + md-no-cache="true"
  31 + id="entityType"
  32 + md-selected-item="selectedEntityType"
  33 + md-search-text="entityTypeSearchText"
  34 + md-items="item in fetchEntityTypes(entityTypeSearchText)"
  35 + md-item-text="item.name"
  36 + md-min-length="0"
  37 + placeholder="{{ (!entityTypeList || !entityTypeList.length) ? placeholder : secondaryPlaceholder }}">
  38 + <md-item-template>
  39 + <span md-highlight-text="entityTypeSearchText" md-highlight-flags="^i">{{item.name}}</span>
  40 + </md-item-template>
  41 + <md-not-found>
  42 + <span translate translate-values='{ entityType: entityTypeSearchText }'>entity.no-entity-types-matching</span>
  43 + </md-not-found>
  44 + </md-autocomplete>
  45 + <md-chip-template>
  46 + <span>
  47 + <strong>{{$chip.name}}</strong>
  48 + </span>
  49 + </md-chip-template>
  50 + </md-chips>
  51 + <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" ng-if="inputTouched && tbRequired" role="alert">
  52 + <div translate ng-message="entityTypeList" class="tb-error-message">entity.entity-type-list-empty</div>
  53 + </div>
  54 +</section>
... ...
... ... @@ -23,7 +23,7 @@ import entityTypeSelectTemplate from './entity-type-select.tpl.html';
23 23 /* eslint-enable import/no-unresolved, import/default */
24 24
25 25 /*@ngInject*/
26   -export default function EntityTypeSelect($compile, $templateCache, utils, userService, types) {
  26 +export default function EntityTypeSelect($compile, $templateCache, utils, entityService, userService, types) {
27 27
28 28 var linker = function (scope, element, attrs, ngModelCtrl) {
29 29 var template = $templateCache.get(entityTypeSelectTemplate);
... ... @@ -39,36 +39,7 @@ export default function EntityTypeSelect($compile, $templateCache, utils, userSe
39 39
40 40 scope.ngModelCtrl = ngModelCtrl;
41 41
42   - var authority = userService.getAuthority();
43   - scope.entityTypes = {};
44   - switch(authority) {
45   - case 'SYS_ADMIN':
46   - scope.entityTypes.tenant = types.entityType.tenant;
47   - scope.entityTypes.rule = types.entityType.rule;
48   - scope.entityTypes.plugin = types.entityType.plugin;
49   - break;
50   - case 'TENANT_ADMIN':
51   - scope.entityTypes.device = types.entityType.device;
52   - scope.entityTypes.asset = types.entityType.asset;
53   - scope.entityTypes.customer = types.entityType.customer;
54   - scope.entityTypes.rule = types.entityType.rule;
55   - scope.entityTypes.plugin = types.entityType.plugin;
56   - scope.entityTypes.dashboard = types.entityType.dashboard;
57   - break;
58   - case 'CUSTOMER_USER':
59   - scope.entityTypes.device = types.entityType.device;
60   - scope.entityTypes.asset = types.entityType.asset;
61   - scope.entityTypes.dashboard = types.entityType.dashboard;
62   - break;
63   - }
64   -
65   - if (scope.allowedEntityTypes) {
66   - for (var entityType in scope.entityTypes) {
67   - if (scope.allowedEntityTypes.indexOf(scope.entityTypes[entityType]) === -1) {
68   - delete scope.entityTypes[entityType];
69   - }
70   - }
71   - }
  42 + scope.entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes);
72 43
73 44 scope.typeName = function(type) {
74 45 return type ? types.entityTypeTranslations[type].type : '';
... ...
... ... @@ -14,9 +14,11 @@
14 14 * limitations under the License.
15 15 */
16 16
17   -import EntityAliasesController from './entity-aliases.controller';
18   -import EntityAliasDialogController from './entity-alias-dialog.controller';
  17 +import EntityAliasesController from './alias/entity-aliases.controller';
  18 +import EntityAliasDialogController from './alias/entity-alias-dialog.controller';
19 19 import EntityTypeSelectDirective from './entity-type-select.directive';
  20 +import EntityTypeListDirective from './entity-type-list.directive';
  21 +import EntitySubtypeListDirective from './entity-subtype-list.directive';
20 22 import EntitySubtypeSelectDirective from './entity-subtype-select.directive';
21 23 import EntitySubtypeAutocompleteDirective from './entity-subtype-autocomplete.directive';
22 24 import EntityAutocompleteDirective from './entity-autocomplete.directive';
... ... @@ -24,11 +26,12 @@ import EntityListDirective from './entity-list.directive';
24 26 import EntitySelectDirective from './entity-select.directive';
25 27 import EntityFilterDirective from './entity-filter.directive';
26 28 import EntityFilterViewDirective from './entity-filter-view.directive';
27   -import AliasesEntitySelectPanelController from './aliases-entity-select-panel.controller';
28   -import AliasesEntitySelectDirective from './aliases-entity-select.directive';
  29 +import AliasesEntitySelectPanelController from './alias/aliases-entity-select-panel.controller';
  30 +import AliasesEntitySelectDirective from './alias/aliases-entity-select.directive';
29 31 import AddAttributeDialogController from './attribute/add-attribute-dialog.controller';
30 32 import AddWidgetToDashboardDialogController from './attribute/add-widget-to-dashboard-dialog.controller';
31 33 import AttributeTableDirective from './attribute/attribute-table.directive';
  34 +import RelationFiltersDirective from './relation/relation-filters.directive';
32 35 import RelationTableDirective from './relation/relation-table.directive';
33 36 import RelationTypeAutocompleteDirective from './relation/relation-type-autocomplete.directive';
34 37
... ... @@ -39,6 +42,8 @@ export default angular.module('thingsboard.entity', [])
39 42 .controller('AddAttributeDialogController', AddAttributeDialogController)
40 43 .controller('AddWidgetToDashboardDialogController', AddWidgetToDashboardDialogController)
41 44 .directive('tbEntityTypeSelect', EntityTypeSelectDirective)
  45 + .directive('tbEntityTypeList', EntityTypeListDirective)
  46 + .directive('tbEntitySubtypeList', EntitySubtypeListDirective)
42 47 .directive('tbEntitySubtypeSelect', EntitySubtypeSelectDirective)
43 48 .directive('tbEntitySubtypeAutocomplete', EntitySubtypeAutocompleteDirective)
44 49 .directive('tbEntityAutocomplete', EntityAutocompleteDirective)
... ... @@ -48,6 +53,7 @@ export default angular.module('thingsboard.entity', [])
48 53 .directive('tbEntityFilterView', EntityFilterViewDirective)
49 54 .directive('tbAliasesEntitySelect', AliasesEntitySelectDirective)
50 55 .directive('tbAttributeTable', AttributeTableDirective)
  56 + .directive('tbRelationFilters', RelationFiltersDirective)
51 57 .directive('tbRelationTable', RelationTableDirective)
52 58 .directive('tbRelationTypeAutocomplete', RelationTypeAutocompleteDirective)
53 59 .name;
... ...
  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 +
  17 +import './relation-filters.scss';
  18 +
  19 +/* eslint-disable import/no-unresolved, import/default */
  20 +
  21 +import relationFiltersTemplate from './relation-filters.tpl.html';
  22 +
  23 +/* eslint-enable import/no-unresolved, import/default */
  24 +
  25 +/*@ngInject*/
  26 +export default function RelationFilters($compile, $templateCache) {
  27 +
  28 + return {
  29 + restrict: "E",
  30 + require: "^ngModel",
  31 + scope: {
  32 + allowedEntityTypes: '=?'
  33 + },
  34 + link: linker
  35 + };
  36 +
  37 + function linker( scope, element, attrs, ngModelCtrl ) {
  38 +
  39 + var template = $templateCache.get(relationFiltersTemplate);
  40 + element.html(template);
  41 +
  42 + scope.relationFilters = [];
  43 +
  44 + scope.addFilter = addFilter;
  45 + scope.removeFilter = removeFilter;
  46 +
  47 + ngModelCtrl.$render = function () {
  48 + if (ngModelCtrl.$viewValue) {
  49 + var value = ngModelCtrl.$viewValue;
  50 + value.forEach(function (filter) {
  51 + scope.relationFilters.push(filter);
  52 + });
  53 + }
  54 + scope.$watch('relationFilters', function (newVal, prevVal) {
  55 + if (!angular.equals(newVal, prevVal)) {
  56 + updateValue();
  57 + }
  58 + }, true);
  59 + }
  60 +
  61 + function addFilter() {
  62 + var filter = {
  63 + relationType: null,
  64 + entityTypes: []
  65 + };
  66 + scope.relationFilters.push(filter);
  67 + }
  68 +
  69 + function removeFilter($event, filter) {
  70 + var index = scope.relationFilters.indexOf(filter);
  71 + if (index > -1) {
  72 + scope.relationFilters.splice(index, 1);
  73 + }
  74 + }
  75 +
  76 + function updateValue() {
  77 + var value = [];
  78 + scope.relationFilters.forEach(function (filter) {
  79 + value.push(filter);
  80 + });
  81 + ngModelCtrl.$setViewValue(value);
  82 + }
  83 + $compile(element.contents())(scope);
  84 + }
  85 +}
... ...
  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 +
  17 +.tb-relation-filters {
  18 + .header {
  19 + padding-left: 5px;
  20 + padding-right: 5px;
  21 + padding-bottom: 5px;
  22 + .cell {
  23 + padding-left: 5px;
  24 + padding-right: 5px;
  25 + color: rgba(0,0,0,.54);
  26 + font-size: 12px;
  27 + font-weight: 700;
  28 + white-space: nowrap;
  29 + }
  30 + }
  31 + .body {
  32 + padding-left: 5px;
  33 + padding-right: 5px;
  34 + max-height: 300px;
  35 + overflow: auto;
  36 + padding-bottom: 20px;
  37 + .row {
  38 + padding-top: 5px;
  39 + }
  40 + .cell {
  41 + padding-left: 5px;
  42 + padding-right: 5px;
  43 +
  44 + md-select {
  45 + margin: 0 0 24px;
  46 + }
  47 +
  48 + md-input-container {
  49 + margin: 0;
  50 + }
  51 +
  52 + md-chips-wrap {
  53 + padding: 0px;
  54 + margin: 0 0 24px;
  55 + .md-chip-input-container {
  56 + margin: 0;
  57 + }
  58 + md-autocomplete {
  59 + height: 30px;
  60 + md-autocomplete-wrap {
  61 + height: 30px;
  62 + }
  63 + }
  64 + }
  65 + .md-chips .md-chip-input-container input {
  66 + padding: 2px 2px 2px;
  67 + height: 26px;
  68 + line-height: 26px;
  69 + }
  70 +
  71 + }
  72 +
  73 + .md-button {
  74 + margin: 0;
  75 + }
  76 + }
  77 +}
\ No newline at end of file
... ...
  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 +<div class="tb-relation-filters">
  19 + <div class="header" ng-show="relationFilters.length">
  20 + <div layout="row" layout-align="start center">
  21 + <span class="cell" style="width: 200px; min-width: 200px;" translate>relation.type</span>
  22 + <span class="cell" flex translate>entity.entity-types</span>
  23 + <span class="cell" style="width: 40px; min-width: 40px;">&nbsp</span>
  24 + </div>
  25 + </div>
  26 + <div class="body" ng-show="relationFilters.length">
  27 + <div class="row" ng-form name="relationFilterForm" flex layout="row" layout-align="start center" ng-repeat="filter in relationFilters track by $index">
  28 + <div class="md-whiteframe-1dp" flex layout="row" layout-align="start center">
  29 + <tb-relation-type-autocomplete class="cell" style="width: 200px; min-width: 200px;"
  30 + hide-label
  31 + the-form="relationFilterForm"
  32 + ng-model="filter.relationType"
  33 + tb-required="false">
  34 + </tb-relation-type-autocomplete>
  35 + <tb-entity-type-list class="cell" flex
  36 + ng-model="filter.entityTypes"
  37 + allowed-entity-types="allowedEntityTypes"
  38 + tb-required="false">
  39 + </tb-entity-type-list>
  40 + <md-button ng-disabled="loading" class="md-icon-button md-primary" style="width: 40px; min-width: 40px;"
  41 + ng-click="removeFilter($event, filter)" aria-label="{{ 'action.remove' | translate }}">
  42 + <md-tooltip md-direction="top">
  43 + {{ 'relation.remove-relation-filter' | translate }}
  44 + </md-tooltip>
  45 + <md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">
  46 + close
  47 + </md-icon>
  48 + </md-button>
  49 + </div>
  50 + </div>
  51 + </div>
  52 + <div class="any-filter" ng-show="!relationFilters.length">
  53 + <span layout-align="center center"
  54 + class="tb-prompt" translate>relation.any-relation</span>
  55 + </div>
  56 + <div>
  57 + <md-button ng-disabled="loading" class="md-primary md-raised" ng-click="addFilter($event)" aria-label="{{ 'action.add' | translate }}">
  58 + <md-tooltip md-direction="top">
  59 + {{ 'relation.add-relation-filter' | translate }}
  60 + </md-tooltip>
  61 + <md-icon aria-label="{{ 'action.add' | translate }}" class="material-icons">
  62 + add
  63 + </md-icon>
  64 + {{ 'action.add' | translate }}
  65 + </md-button>
  66 + </div>
  67 +</div>
\ No newline at end of file
... ...
... ... @@ -29,6 +29,8 @@ export default function RelationTypeAutocomplete($compile, $templateCache, $q, $
29 29 element.html(template);
30 30
31 31 scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
  32 + scope.hideLabel = angular.isDefined(attrs.hideLabel) ? true : false;
  33 +
32 34 scope.relationType = null;
33 35 scope.relationTypeSearchText = '';
34 36 scope.relationTypes = [];
... ...
... ... @@ -26,7 +26,7 @@
26 26 md-items="item in fetchRelationTypes(relationTypeSearchText)"
27 27 md-item-text="item"
28 28 md-min-length="0"
29   - md-floating-label="{{ 'relation.relation-type' | translate }}"
  29 + md-floating-label="{{ tbRequired ? ('relation.relation-type' | translate) : ( !relationType ? ('relation.any-relation-type' | translate) : ' ') }}"
30 30 md-select-on-match="true"
31 31 md-menu-class="tb-relation-type-autocomplete">
32 32 <md-item-template>
... ...
... ... @@ -16,7 +16,7 @@
16 16 /* eslint-disable import/no-unresolved, import/default */
17 17
18 18 import importDialogTemplate from './import-dialog.tpl.html';
19   -import entityAliasesTemplate from '../entity/entity-aliases.tpl.html';
  19 +import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html';
20 20
21 21 /* eslint-enable import/no-unresolved, import/default */
22 22
... ...
... ... @@ -129,14 +129,24 @@ export default angular.module('thingsboard.locale', [])
129 129 "filter-type-device-type-description": "Devices of type '{{deviceType}}'",
130 130 "filter-type-device-type-and-name-description": "Devices of type '{{deviceType}}' and with name starting with '{{prefix}}'",
131 131 "filter-type-relations-query": "Relations query",
  132 + "filter-type-relations-query-description": "{{entities}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
132 133 "filter-type-asset-search-query": "Asset search query",
  134 + "filter-type-asset-search-query-description": "Assets with types {{assetTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
133 135 "filter-type-device-search-query": "Device search query",
  136 + "filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
134 137 "entity-filter": "Entity filter",
135 138 "resolve-multiple": "Resolve as multiple entities",
136 139 "filter-type": "Filter type",
137 140 "filter-type-required": "Filter type is required.",
138 141 "entity-filter-no-entity-matched": "No entities matching specified filter were found.",
139   - "no-entity-filter-specified": "No entity filter specified"
  142 + "no-entity-filter-specified": "No entity filter specified",
  143 + "root-state-entity": "Use dashboard state entity as root",
  144 + "root-entity": "Root entity",
  145 + "max-relation-level": "Max relation level",
  146 + "unlimited-level": "Unlimited level",
  147 + "state-entity": "Dashboard state entity",
  148 + "all-entities": "All entities",
  149 + "any-relation": "any"
140 150 },
141 151 "asset": {
142 152 "asset": "Asset",
... ... @@ -159,6 +169,11 @@ export default angular.module('thingsboard.locale', [])
159 169 "asset-type": "Asset type",
160 170 "asset-type-required": "Asset type is required.",
161 171 "select-asset-type": "Select asset type",
  172 + "enter-asset-type": "Enter asset type",
  173 + "any-asset": "Any asset",
  174 + "no-asset-types-matching": "No asset types matching '{{entitySubtype}}' were found.",
  175 + "asset-type-list-empty": "No asset types selected.",
  176 + "asset-types": "Asset types",
162 177 "name": "Name",
163 178 "name-required": "Name is required.",
164 179 "description": "Description",
... ... @@ -444,6 +459,7 @@ export default angular.module('thingsboard.locale', [])
444 459 },
445 460 "datasource": {
446 461 "type": "Datasource type",
  462 + "name": "Name",
447 463 "add-datasource-prompt": "Please add datasource"
448 464 },
449 465 "details": {
... ... @@ -524,6 +540,11 @@ export default angular.module('thingsboard.locale', [])
524 540 "device-type": "Device type",
525 541 "device-type-required": "Device type is required.",
526 542 "select-device-type": "Select device type",
  543 + "enter-device-type": "Enter device type",
  544 + "any-device": "Any device",
  545 + "no-device-types-matching": "No device types matching '{{entitySubtype}}' were found.",
  546 + "device-type-list-empty": "No device types selected.",
  547 + "device-types": "Device types",
527 548 "name": "Name",
528 549 "name-required": "Name is required.",
529 550 "description": "Description",
... ... @@ -564,10 +585,17 @@ export default angular.module('thingsboard.locale', [])
564 585 "remove-alias": "Remove entity alias",
565 586 "add-alias": "Add entity alias",
566 587 "entity-list": "Entity list",
  588 + "entity-type": "Entity type",
  589 + "entity-types": "Entity types",
  590 + "entity-type-list": "Entity type list",
  591 + "any-entity": "Any entity",
  592 + "enter-entity-type": "Enter entity type",
567 593 "no-entities-matching": "No entities matching '{{entity}}' were found.",
  594 + "no-entity-types-matching": "No entity types matching '{{entityType}}' were found.",
568 595 "name-starts-with": "Name starts with",
569 596 "use-entity-name-filter": "Use filter",
570 597 "entity-list-empty": "No entities selected.",
  598 + "entity-type-list-empty": "No entity types selected.",
571 599 "entity-name-filter-required": "Entity name filter is required.",
572 600 "entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.",
573 601 "all-subtypes": "All",
... ... @@ -581,30 +609,39 @@ export default angular.module('thingsboard.locale', [])
581 609 "type": "Type",
582 610 "type-required": "Entity type is required.",
583 611 "type-device": "Device",
  612 + "type-devices": "Devices",
584 613 "list-of-devices": "{ count, select, 1 {One device} other {List of # devices} }",
585 614 "device-name-starts-with": "Devices whose names start with '{{prefix}}'",
586 615 "type-asset": "Asset",
  616 + "type-assets": "Assets",
587 617 "list-of-assets": "{ count, select, 1 {One asset} other {List of # assets} }",
588 618 "asset-name-starts-with": "Assets whose names start with '{{prefix}}'",
589 619 "type-rule": "Rule",
  620 + "type-rules": "Rules",
590 621 "list-of-rules": "{ count, select, 1 {One rule} other {List of # rules} }",
591 622 "rule-name-starts-with": "Rules whose names start with '{{prefix}}'",
592 623 "type-plugin": "Plugin",
  624 + "type-plugins": "Plugins",
593 625 "list-of-plugins": "{ count, select, 1 {One plugin} other {List of # plugins} }",
594 626 "plugin-name-starts-with": "Plugins whose names start with '{{prefix}}'",
595 627 "type-tenant": "Tenant",
  628 + "type-tenants": "Tenants",
596 629 "list-of-tenants": "{ count, select, 1 {One tenant} other {List of # tenants} }",
597 630 "tenant-name-starts-with": "Tenants whose names start with '{{prefix}}'",
598 631 "type-customer": "Customer",
  632 + "type-customers": "Customers",
599 633 "list-of-customers": "{ count, select, 1 {One customer} other {List of # customers} }",
600 634 "customer-name-starts-with": "Customers whose names start with '{{prefix}}'",
601 635 "type-user": "User",
  636 + "type-users": "Users",
602 637 "list-of-users": "{ count, select, 1 {One user} other {List of # users} }",
603 638 "user-name-starts-with": "Users whose names start with '{{prefix}}'",
604 639 "type-dashboard": "Dashboard",
  640 + "type-dashboards": "Dashboards",
605 641 "list-of-dashboards": "{ count, select, 1 {One dashboard} other {List of # dashboards} }",
606 642 "dashboard-name-starts-with": "Dashboards whose names start with '{{prefix}}'",
607 643 "type-alarm": "Alarm",
  644 + "type-alarms": "Alarms",
608 645 "list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }",
609 646 "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'"
610 647 },
... ... @@ -770,6 +807,10 @@ export default angular.module('thingsboard.locale', [])
770 807 "FROM": "From",
771 808 "TO": "To"
772 809 },
  810 + "direction-type": {
  811 + "FROM": "from",
  812 + "TO": "to"
  813 + },
773 814 "from-relations": "Outbound relations",
774 815 "to-relations": "Inbound relations",
775 816 "selected-relations": "{ count, select, 1 {1 relation} other {# relations} } selected",
... ... @@ -783,6 +824,7 @@ export default angular.module('thingsboard.locale', [])
783 824 "delete": "Delete relation",
784 825 "relation-type": "Relation type",
785 826 "relation-type-required": "Relation type is required.",
  827 + "any-relation-type": "Any type",
786 828 "add": "Add relation",
787 829 "delete-to-relation-title": "Are you sure you want to delete relation to the entity '{{entityName}}'?",
788 830 "delete-to-relation-text": "Be careful, after the confirmation the entity '{{entityName}}' will be unrelated from the current entity.",
... ... @@ -791,7 +833,11 @@ export default angular.module('thingsboard.locale', [])
791 833 "delete-from-relation-title": "Are you sure you want to delete relation from the entity '{{entityName}}'?",
792 834 "delete-from-relation-text": "Be careful, after the confirmation current entity will be unrelated from the entity '{{entityName}}'.",
793 835 "delete-from-relations-title": "Are you sure you want to delete { count, select, 1 {1 relation} other {# relations} }?",
794   - "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."
  836 + "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.",
  837 + "remove-relation-filter": "Remove relation filter",
  838 + "add-relation-filter": "Add relation filter",
  839 + "any-relation": "Any relation",
  840 + "relation-filters": "Relation filters"
795 841 },
796 842 "rule": {
797 843 "rule": "Rule",
... ...
... ... @@ -236,6 +236,15 @@ div {
236 236 }
237 237 }
238 238
  239 +.md-caption {
  240 + &.tb-required:after {
  241 + content: ' *';
  242 + font-size: 10px;
  243 + vertical-align: top;
  244 + color: rgba(0,0,0,0.54);
  245 + }
  246 +}
  247 +
239 248 pre.tb-highlight {
240 249 background-color: #f7f7f7;
241 250 display: block;
... ...