Commit 9ef646765e4bf4e07396b55f70534ca37671cd36

Authored by Igor Kulikov
1 parent 0a83091a

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

... ... @@ -95,8 +95,7 @@ export default class AliasController {
95 95 this.entityService.resolveAlias(entityAlias, this.stateController.getStateParams()).then(
96 96 function success(aliasInfo) {
97 97 aliasCtrl.resolvedAliases[aliasId] = aliasInfo;
98   - if (entityAlias.filter.stateEntity) {
99   - aliasInfo.stateEntity = true;
  98 + if (aliasInfo.stateEntity) {
100 99 aliasCtrl.resolvedAliasesToStateEntities[aliasId] =
101 100 aliasCtrl.stateController.getStateParams().entityId;
102 101 }
... ...
... ... @@ -31,6 +31,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
31 31 resolveAliasFilter: resolveAliasFilter,
32 32 checkEntityAlias: checkEntityAlias,
33 33 filterAliasByEntityTypes: filterAliasByEntityTypes,
  34 + getAliasFilterTypesByEntityTypes: getAliasFilterTypesByEntityTypes,
34 35 getEntityKeys: getEntityKeys,
35 36 createDatasourcesFromSubscriptionsInfo: createDatasourcesFromSubscriptionsInfo,
36 37 getRelatedEntities: getRelatedEntities,
... ... @@ -223,17 +224,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
223 224 return promise;
224 225 }
225 226
226   - function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config, subType) {
227   - var deferred = $q.defer();
228   - var pageLink = {limit: limit, textSearch: entityNameFilter};
  227 + function getEntitiesByPageLink(entityType, pageLink, config, subType, data, deferred) {
229 228 var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config, subType);
230 229 if (promise) {
231 230 promise.then(
232 231 function success(result) {
233   - if (result.data && result.data.length > 0) {
234   - deferred.resolve(result.data);
  232 + data = data.concat(result.data);
  233 + if (result.hasNext) {
  234 + pageLink = result.nextPageLink;
  235 + getEntitiesByPageLink(entityType, pageLink, config, subType, data, deferred);
235 236 } else {
236   - deferred.resolve(null);
  237 + if (data && data.length > 0) {
  238 + deferred.resolve(data);
  239 + } else {
  240 + deferred.resolve(null);
  241 + }
237 242 }
238 243 },
239 244 function fail() {
... ... @@ -243,6 +248,34 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
243 248 } else {
244 249 deferred.resolve(null);
245 250 }
  251 + }
  252 +
  253 + function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config, subType) {
  254 + var deferred = $q.defer();
  255 + var pageLink = {limit: limit, textSearch: entityNameFilter};
  256 + if (limit == -1) { // all
  257 + var data = [];
  258 + pageLink.limit = 100;
  259 + getEntitiesByPageLink(entityType, pageLink, config, subType, data, deferred);
  260 + } else {
  261 + var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config, subType);
  262 + if (promise) {
  263 + promise.then(
  264 + function success(result) {
  265 + if (result.data && result.data.length > 0) {
  266 + deferred.resolve(result.data);
  267 + } else {
  268 + deferred.resolve(null);
  269 + }
  270 + },
  271 + function fail() {
  272 + deferred.resolve(null);
  273 + }
  274 + );
  275 + } else {
  276 + deferred.resolve(null);
  277 + }
  278 + }
246 279 return deferred.promise;
247 280 }
248 281
... ... @@ -261,11 +294,12 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
261 294 function resolveAlias(entityAlias, stateParams) {
262 295 var deferred = $q.defer();
263 296 var filter = entityAlias.filter;
264   - resolveAliasFilter(filter, stateParams, 100).then(
  297 + resolveAliasFilter(filter, stateParams, -1).then(
265 298 function (result) {
266 299 var entities = result.entities;
267 300 var aliasInfo = {
268 301 alias: entityAlias.alias,
  302 + stateEntity: result.stateEntity,
269 303 resolveMultiple: filter.resolveMultiple
270 304 };
271 305 var resolvedEntities = entitiesToEntitiesInfo(entities);
... ... @@ -291,39 +325,68 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
291 325 };
292 326 switch (filter.type) {
293 327 case types.aliasFilterType.entityList.value:
294   - if (filter.stateEntity) {
295   - result.stateEntity = true;
296   - if (stateParams && stateParams.entityId) {
297   - getEntity(stateParams.entityId.entityType, stateParams.entityId.id).then(
298   - function success(entity) {
299   - result.entities = [entity];
300   - deferred.resolve(result);
301   - },
302   - function fail() {
303   - deferred.reject();
304   - }
305   - );
306   - } else {
307   - deferred.resolve(result);
  328 + getEntities(filter.entityType, filter.entityList).then(
  329 + function success(entities) {
  330 + if (entities && entities.length) {
  331 + result.entities = entities;
  332 + deferred.resolve(result);
  333 + } else {
  334 + deferred.reject();
  335 + }
  336 + },
  337 + function fail() {
  338 + deferred.reject();
308 339 }
309   - } else {
310   - getEntities(filter.entityType, filter.entityList).then(
311   - function success(entities) {
312   - if (entities && entities.length) {
313   - result.entities = entities;
314   - deferred.resolve(result);
315   - } else {
316   - deferred.reject();
317   - }
  340 + );
  341 + break;
  342 + case types.aliasFilterType.entityName.value:
  343 + getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems).then(
  344 + function success(entities) {
  345 + if (entities && entities.length) {
  346 + result.entities = entities;
  347 + deferred.resolve(result);
  348 + } else {
  349 + deferred.reject();
  350 + }
  351 + },
  352 + function fail() {
  353 + deferred.reject();
  354 + }
  355 + );
  356 + break;
  357 + case types.aliasFilterType.stateEntity.value:
  358 + result.stateEntity = true;
  359 + if (stateParams && stateParams.entityId) {
  360 + getEntity(stateParams.entityId.entityType, stateParams.entityId.id).then(
  361 + function success(entity) {
  362 + result.entities = [entity];
  363 + deferred.resolve(result);
318 364 },
319 365 function fail() {
320 366 deferred.reject();
321 367 }
322 368 );
  369 + } else {
  370 + deferred.resolve(result);
323 371 }
324 372 break;
325   - case types.aliasFilterType.entityName.value:
326   - getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems).then(
  373 + case types.aliasFilterType.assetType.value:
  374 + getEntitiesByNameFilter(types.entityType.asset, filter.assetNameFilter, maxItems, null, filter.assetType).then(
  375 + function success(entities) {
  376 + if (entities && entities.length) {
  377 + result.entities = entities;
  378 + deferred.resolve(result);
  379 + } else {
  380 + deferred.reject();
  381 + }
  382 + },
  383 + function fail() {
  384 + deferred.reject();
  385 + }
  386 + );
  387 + break;
  388 + case types.aliasFilterType.deviceType.value:
  389 + getEntitiesByNameFilter(types.entityType.device, filter.deviceNameFilter, maxItems, null, filter.deviceType).then(
327 390 function success(entities) {
328 391 if (entities && entities.length) {
329 392 result.entities = entities;
... ... @@ -337,27 +400,81 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
337 400 }
338 401 );
339 402 break;
340   - //TODO:
  403 +
  404 + //TODO: Alias filter
341 405 }
342 406 return deferred.promise;
343 407 }
344 408
345 409 function filterAliasByEntityTypes(entityAlias, entityTypes) {
346 410 var filter = entityAlias.filter;
347   - switch (filter.type) {
348   - case types.aliasFilterType.entityList.value:
349   - if (filter.stateEntity) {
350   - return true;
351   - } else {
  411 + if (filterAliasFilterTypeByEntityTypes(filter.type, entityTypes)) {
  412 + switch (filter.type) {
  413 + case types.aliasFilterType.entityList.value:
352 414 return entityTypes.indexOf(filter.entityType) > -1 ? true : false;
353   - }
  415 + case types.aliasFilterType.entityName.value:
  416 + return entityTypes.indexOf(filter.entityType) > -1 ? true : false;
  417 + case types.aliasFilterType.stateEntity.value:
  418 + return true;
  419 + case types.aliasFilterType.assetType.value:
  420 + return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false;
  421 + case types.aliasFilterType.deviceType.value:
  422 + return entityTypes.indexOf(types.entityType.device) > -1 ? true : false;
  423 + }
  424 + }
  425 + //TODO: Alias filter
  426 + return false;
  427 + }
  428 +
  429 + function filterAliasFilterTypeByEntityType(aliasFilterType, entityType) {
  430 + switch (aliasFilterType) {
  431 + case types.aliasFilterType.entityList.value:
  432 + return true;
354 433 case types.aliasFilterType.entityName.value:
355   - return entityTypes.indexOf(filter.entityType) > -1 ? true : false;
  434 + return true;
  435 + case types.aliasFilterType.stateEntity.value:
  436 + return true;
  437 + case types.aliasFilterType.assetType.value:
  438 + return entityType === types.entityType.asset;
  439 + case types.aliasFilterType.deviceType.value:
  440 + return entityType === types.entityType.device;
  441 + case types.aliasFilterType.relationsQuery.value:
  442 + return true;
  443 + case types.aliasFilterType.assetSearchQuery.value:
  444 + return entityType === types.entityType.asset;
  445 + case types.aliasFilterType.deviceSearchQuery.value:
  446 + return entityType === types.entityType.device;
356 447 }
357   - //TODO:
358 448 return false;
359 449 }
360 450
  451 + function filterAliasFilterTypeByEntityTypes(aliasFilterType, entityTypes) {
  452 + if (!entityTypes || !entityTypes.length) {
  453 + return true;
  454 + }
  455 + var valid = false;
  456 + entityTypes.forEach(function(entityType) {
  457 + valid = valid || filterAliasFilterTypeByEntityType(aliasFilterType, entityType);
  458 + });
  459 + return valid;
  460 + }
  461 +
  462 + function getAliasFilterTypesByEntityTypes(entityTypes) {
  463 + var allAliasFilterTypes = types.aliasFilterType;
  464 + if (!entityTypes || !entityTypes.length) {
  465 + return allAliasFilterTypes;
  466 + }
  467 + var result = {};
  468 + for (var type in allAliasFilterTypes) {
  469 + var aliasFilterType = allAliasFilterTypes[type];
  470 + if (filterAliasFilterTypeByEntityTypes(aliasFilterType.value, entityTypes)) {
  471 + result[type] = aliasFilterType;
  472 + }
  473 + }
  474 + return result;
  475 + }
  476 +
  477 +
361 478 function checkEntityAlias(entityAlias) {
362 479 var deferred = $q.defer();
363 480 resolveAliasFilter(entityAlias.filter, null, 1).then(
... ... @@ -380,50 +497,6 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
380 497 return deferred.promise;
381 498 }
382 499
383   - /*function processEntityAlias(index, aliasIds, entityAliases, stateParams, resolution, deferred) {
384   - if (index < aliasIds.length) {
385   - var aliasId = aliasIds[index];
386   - var entityAlias = entityAliases[aliasId];
387   - var alias = entityAlias.alias;
388   - var filter = entityAlias.filter;
389   - resolveAliasFilter(filter, stateParams).then(
390   - function (entities) {
391   - if (entities && entities.length) {
392   - var entity = entities[0];
393   - var resolvedAlias = {alias: alias, entityType: entity.id.entityType, entityId: entity.id.id};
394   - resolution.aliasesInfo.entityAliases[aliasId] = resolvedAlias;
395   - resolution.aliasesInfo.entityAliasesInfo[aliasId] = entitiesToEntitiesInfo(entities);
396   - index++;
397   - processEntityAlias(index, aliasIds, entityAliases, stateParams, resolution, deferred);
398   - } else {
399   - if (!resolution.error) {
400   - resolution.error = 'dashboard.invalid-aliases-config';
401   - }
402   - index++;
403   - processEntityAlias(index, aliasIds, entityAliases, stateParams, resolution, deferred);
404   - }
405   - }
406   - );
407   - } else {
408   - deferred.resolve(resolution);
409   - }
410   - }*/
411   -
412   - /*function processEntityAliases(entityAliases, stateParams) {
413   - var deferred = $q.defer();
414   - var resolution = {
415   - aliasesInfo: {}
416   - };
417   - var aliasIds = [];
418   - if (entityAliases) {
419   - for (var aliasId in entityAliases) {
420   - aliasIds.push(aliasId);
421   - }
422   - }
423   - processEntityAlias(0, aliasIds, entityAliases, stateParams, resolution, deferred);
424   - return deferred.promise;
425   - }*/
426   -
427 500 function getEntityKeys(entityType, entityId, query, type) {
428 501 var deferred = $q.defer();
429 502 var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/keys/';
... ... @@ -889,4 +962,4 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
889 962 }
890 963 }
891 964
892   -}
\ No newline at end of file
  965 +}
... ...
... ... @@ -258,17 +258,17 @@ export default class Subscription {
258 258 } else {
259 259 subscription.rpcEnabled = subscription.ctx.$scope.widgetEditMode ? true : false;
260 260 }
261   - subscription.callbacks.rpcStateChanged(this);
  261 + subscription.callbacks.rpcStateChanged(subscription);
262 262 deferred.resolve();
263 263 } else {
264 264 subscription.rpcEnabled = false;
265   - subscription.callbacks.rpcStateChanged(this);
  265 + subscription.callbacks.rpcStateChanged(subscription);
266 266 deferred.resolve();
267 267 }
268 268 },
269 269 function fail () {
270 270 subscription.rpcEnabled = false;
271   - subscription.callbacks.rpcStateChanged(this);
  271 + subscription.callbacks.rpcStateChanged(subscription);
272 272 deferred.resolve();
273 273 }
274 274 );
... ...
... ... @@ -110,14 +110,12 @@ function DashboardUtils(types, utils, timeService) {
110 110 deviceAlias.deviceFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value;
111 111 if (entityAlias.filter.type == types.aliasFilterType.entityList.value) {
112 112 entityAlias.filter.entityList = deviceAlias.deviceFilter.deviceList;
113   - entityAlias.filter.stateEntity = false;
114 113 } else {
115 114 entityAlias.filter.entityNameFilter = deviceAlias.deviceFilter.deviceNameFilter;
116 115 }
117 116 } else {
118 117 entityAlias.filter.type = types.aliasFilterType.entityList.value;
119 118 entityAlias.filter.entityList = [deviceAlias.deviceId];
120   - entityAlias.filter.stateEntity = false;
121 119 }
122 120 return entityAlias;
123 121 }
... ... @@ -132,7 +130,6 @@ function DashboardUtils(types, utils, timeService) {
132 130 }
133 131 if (entityAlias.filter.type == types.aliasFilterType.entityList.value) {
134 132 entityAlias.filter.entityList = entityAlias.entityFilter.entityList;
135   - entityAlias.filter.stateEntity = false;
136 133 } else {
137 134 entityAlias.filter.entityNameFilter = entityAlias.entityFilter.entityNameFilter;
138 135 }
... ... @@ -342,7 +339,6 @@ function DashboardUtils(types, utils, timeService) {
342 339 function createSingleEntityFilter(entityType, entityId) {
343 340 return {
344 341 type: types.aliasFilterType.entityList.value,
345   - stateEntity: false,
346 342 entityList: [entityId],
347 343 entityType: entityType,
348 344 resolveMultiple: false
... ...
... ... @@ -74,6 +74,10 @@ export default angular.module('thingsboard.types', [])
74 74 value: 'entityName',
75 75 name: 'alias.filter-type-entity-name'
76 76 },
  77 + stateEntity: {
  78 + value: 'stateEntity',
  79 + name: 'alias.filter-type-state-entity'
  80 + },
77 81 assetType: {
78 82 value: 'assetType',
79 83 name: 'alias.filter-type-asset-type'
... ... @@ -139,6 +143,53 @@ export default angular.module('thingsboard.types', [])
139 143 dashboard: "DASHBOARD",
140 144 alarm: "ALARM"
141 145 },
  146 + entityTypeTranslations: {
  147 + "DEVICE": {
  148 + type: 'entity.type-device',
  149 + list: 'entity.list-of-devices',
  150 + nameStartsWith: 'entity.device-name-starts-with'
  151 + },
  152 + "ASSET": {
  153 + type: 'entity.type-asset',
  154 + list: 'entity.list-of-assets',
  155 + nameStartsWith: 'entity.asset-name-starts-with'
  156 + },
  157 + "RULE": {
  158 + type: 'entity.type-rule',
  159 + list: 'entity.list-of-rules',
  160 + nameStartsWith: 'entity.rule-name-starts-with'
  161 + },
  162 + "PLUGIN": {
  163 + type: 'entity.type-plugin',
  164 + list: 'entity.list-of-plugins',
  165 + nameStartsWith: 'entity.plugin-name-starts-with'
  166 + },
  167 + "TENANT": {
  168 + type: 'entity.type-tenant',
  169 + list: 'entity.list-of-tenants',
  170 + nameStartsWith: 'entity.tenant-name-starts-with'
  171 + },
  172 + "CUSTOMER": {
  173 + type: 'entity.type-customer',
  174 + list: 'entity.list-of-customers',
  175 + nameStartsWith: 'entity.customer-name-starts-with'
  176 + },
  177 + "USER": {
  178 + type: 'entity.type-user',
  179 + list: 'entity.list-of-users',
  180 + nameStartsWith: 'entity.user-name-starts-with'
  181 + },
  182 + "DASHBOARD": {
  183 + type: 'entity.type-dashboard',
  184 + list: 'entity.list-of-dashboards',
  185 + nameStartsWith: 'entity.dashboard-name-starts-with'
  186 + },
  187 + "ALARM": {
  188 + type: 'entity.type-alarm',
  189 + list: 'entity.list-of-alarms',
  190 + nameStartsWith: 'entity.alarm-name-starts-with'
  191 + }
  192 + },
142 193 entitySearchDirection: {
143 194 from: "FROM",
144 195 to: "TO"
... ...
... ... @@ -109,8 +109,7 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
109 109 cleanCopy: cleanCopy,
110 110 isLocalUrl: isLocalUrl,
111 111 validateDatasources: validateDatasources,
112   - createKey: createKey,
113   - entityTypeName: entityTypeName
  112 + createKey: createKey
114 113 }
115 114
116 115 return service;
... ... @@ -358,27 +357,4 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
358 357 return dataKey;
359 358 }
360 359
361   - function entityTypeName (type) {
362   - switch (type) {
363   - case types.entityType.device:
364   - return 'entity.type-device';
365   - case types.entityType.asset:
366   - return 'entity.type-asset';
367   - case types.entityType.rule:
368   - return 'entity.type-rule';
369   - case types.entityType.plugin:
370   - return 'entity.type-plugin';
371   - case types.entityType.tenant:
372   - return 'entity.type-tenant';
373   - case types.entityType.customer:
374   - return 'entity.type-customer';
375   - case types.entityType.user:
376   - return 'entity.type-user';
377   - case types.entityType.dashboard:
378   - return 'entity.type-dashboard';
379   - case types.entityType.alarm:
380   - return 'entity.type-alarm';
381   - }
382   - }
383   -
384 360 }
... ...
... ... @@ -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 entityAliasDialogTemplate from '../entity/entity-alias-dialog.tpl.html';
19 19
20 20 /* eslint-enable import/no-unresolved, import/default */
21 21
... ... @@ -130,17 +130,14 @@ export default function AddWidgetController($scope, widgetService, entityService
130 130 var singleEntityAlias = {id: null, alias: alias, filter: {}};
131 131
132 132 $mdDialog.show({
133   - controller: 'EntityAliasesController',
  133 + controller: 'EntityAliasDialogController',
134 134 controllerAs: 'vm',
135   - templateUrl: entityAliasesTemplate,
  135 + templateUrl: entityAliasDialogTemplate,
136 136 locals: {
137   - config: {
138   - entityAliases: angular.copy(vm.dashboard.configuration.entityAliases),
139   - widgets: null,
140   - isSingleEntityAlias: true,
141   - singleEntityAlias: singleEntityAlias,
142   - allowedEntityTypes: allowedEntityTypes
143   - }
  137 + isAdd: true,
  138 + allowedEntityTypes: allowedEntityTypes,
  139 + entityAliases: vm.dashboard.configuration.entityAliases,
  140 + alias: singleEntityAlias
144 141 },
145 142 parent: angular.element($document[0].body),
146 143 fullscreen: true,
... ...
... ... @@ -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 entityAliasDialogTemplate from '../entity/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 */
... ... @@ -98,17 +98,14 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
98 98 var singleEntityAlias = {id: null, alias: alias, filter: {}};
99 99
100 100 $mdDialog.show({
101   - controller: 'EntityAliasesController',
  101 + controller: 'EntityAliasDialogController',
102 102 controllerAs: 'vm',
103   - templateUrl: entityAliasesTemplate,
  103 + templateUrl: entityAliasDialogTemplate,
104 104 locals: {
105   - config: {
106   - entityAliases: angular.copy(scope.dashboard.configuration.entityAliases),
107   - widgets: null,
108   - isSingleEntityAlias: true,
109   - singleEntityAlias: singleEntityAlias,
110   - allowedEntityTypes: allowedEntityTypes
111   - }
  105 + isAdd: true,
  106 + allowedEntityTypes: allowedEntityTypes,
  107 + entityAliases: scope.dashboard.configuration.entityAliases,
  108 + alias: singleEntityAlias
112 109 },
113 110 parent: angular.element($document[0].body),
114 111 fullscreen: true,
... ...
ui/src/app/entity/entity-alias-dialog.controller.js renamed from ui/src/app/entity/entity-filter-dialog.controller.js
... ... @@ -14,22 +14,48 @@
14 14 * limitations under the License.
15 15 */
16 16
  17 +import './entity-alias-dialog.scss';
  18 +
17 19 /*@ngInject*/
18   -export default function EntityFilterDialogController($scope, $mdDialog, $q, entityService, types, isAdd, allowedEntityTypes, filter) {
  20 +export default function EntityAliasDialogController($scope, $mdDialog, $q, $filter, utils, entityService, types, isAdd, allowedEntityTypes, entityAliases, alias) {
19 21
20 22 var vm = this;
21 23
22 24 vm.types = types;
23 25 vm.isAdd = isAdd;
24 26 vm.allowedEntityTypes = allowedEntityTypes;
25   - vm.filter = filter;
  27 + if (angular.isArray(entityAliases)) {
  28 + vm.entityAliases = entityAliases;
  29 + } else {
  30 + vm.entityAliases = [];
  31 + for (var aliasId in entityAliases) {
  32 + vm.entityAliases.push(entityAliases[aliasId]);
  33 + }
  34 + }
  35 + if (vm.isAdd && !alias) {
  36 + vm.alias = {
  37 + alias: '',
  38 + filter: {
  39 + resolveMultiple: false
  40 + }
  41 + };
  42 + } else {
  43 + vm.alias = alias;
  44 + }
26 45
27 46 vm.cancel = cancel;
28 47 vm.save = save;
29 48
30   - $scope.$watch('vm.filter.type', function (newType, prevType) {
31   - if (newType && newType != prevType) {
32   - updateFilter();
  49 + $scope.$watch('vm.alias.alias', function (newAlias) {
  50 + if (newAlias) {
  51 + var valid = true;
  52 + var result = $filter('filter')(vm.entityAliases, {alias: newAlias}, true);
  53 + if (result && result.length) {
  54 + if (vm.isAdd || vm.alias.id != result[0].id) {
  55 + valid = false;
  56 + }
  57 + }
  58 + $scope.theForm.aliasName.$setValidity('duplicateAliasName', valid);
33 59 }
34 60 });
35 61
... ... @@ -39,32 +65,13 @@ export default function EntityFilterDialogController($scope, $mdDialog, $q, enti
39 65 }
40 66 });
41 67
42   - function updateFilter() {
43   - var filter = {};
44   - filter.type = vm.filter.type;
45   - filter.resolveMultiple = vm.filter.resolveMultiple;
46   - switch (filter.type) {
47   - case types.aliasFilterType.entityList.value:
48   - filter.entityType = null;
49   - filter.entityList = [];
50   - filter.stateEntity = false;
51   - break;
52   - case types.aliasFilterType.entityName.value:
53   - filter.entityType = null;
54   - filter.entityNameFilter = '';
55   - break;
56   - //TODO:
57   - }
58   - vm.filter = filter;
59   - }
60   -
61 68 function validate() {
62 69 var deferred = $q.defer();
63 70 var validationResult = {
64 71 entity: null,
65 72 stateEntity: false
66 73 }
67   - entityService.resolveAliasFilter(vm.filter, null, 1).then(
  74 + entityService.resolveAliasFilter(vm.alias.filter, null, 1).then(
68 75 function success(result) {
69 76 validationResult.stateEntity = result.stateEntity;
70 77 var entities = result.entities;
... ... @@ -87,12 +94,11 @@ export default function EntityFilterDialogController($scope, $mdDialog, $q, enti
87 94 function save() {
88 95 $scope.theForm.$setPristine();
89 96 validate().then(
90   - function success(validationResult) {
91   - $mdDialog.hide({
92   - filter: vm.filter,
93   - entity: validationResult.entity,
94   - stateEntity: validationResult.stateEntity
95   - });
  97 + function success() {
  98 + if (vm.isAdd) {
  99 + vm.alias.id = utils.guid();
  100 + }
  101 + $mdDialog.hide(vm.alias);
96 102 },
97 103 function fail() {
98 104 $scope.theForm.$setValidity('entityFilter', false);
... ... @@ -101,4 +107,3 @@ export default function EntityFilterDialogController($scope, $mdDialog, $q, enti
101 107 }
102 108
103 109 }
104   -
... ...
  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-alias-dialog {
  18 + .tb-resolve-multiple-switch {
  19 + padding-left: 10px;
  20 + .resolve-multiple-switch {
  21 + margin: 0;
  22 + }
  23 + .resolve-multiple-label {
  24 + margin: 5px 0;
  25 + }
  26 + }
  27 +}
\ No newline at end of file
... ...
ui/src/app/entity/entity-alias-dialog.tpl.html renamed from ui/src/app/entity/entity-filter-dialog.tpl.html
... ... @@ -15,11 +15,11 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<md-dialog class="tb-entity-filter-dialog" style="width: 600px;" aria-label="{{ 'alias.entity-filter' | translate }}">
  18 +<md-dialog class="tb-entity-alias-dialog" style="width: 600px;" aria-label="{{ (vm.isAdd ? 'alias.add' : 'alias.edit') | translate }}">
19 19 <form name="theForm" ng-submit="vm.save()">
20 20 <md-toolbar>
21 21 <div class="md-toolbar-tools">
22   - <h2>{{ (vm.isAdd ? 'alias.create-entity-filter' : 'alias.edit-entity-filter') | translate }}</h2>
  22 + <h2>{{ (vm.isAdd ? 'alias.add' : 'alias.edit') | translate }}</h2>
23 23 <span flex></span>
24 24 <md-button class="md-icon-button" ng-click="vm.cancel()">
25 25 <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
... ... @@ -32,56 +32,29 @@
32 32 <div class="md-dialog-content">
33 33 <fieldset ng-disabled="loading">
34 34 <div flex layout="column">
35   - <md-input-container>
36   - <label>{{ 'alias.filter-type' | translate }}</label>
37   - <md-select required name="filterType"
38   - ng-model="vm.filter.type" aria-label="{{ 'alias.filter-type' | translate }}">
39   - <md-option ng-repeat="type in vm.types.aliasFilterType" ng-value="type.value">
40   - {{type.name | translate}}
41   - </md-option>
42   - </md-select>
43   - <div ng-messages="theForm.filterType.$error">
44   - <div ng-message="required" translate>alias.filter-type-required</div>
45   - </div>
46   - </md-input-container>
47   - <section layout="column" ng-if="vm.filter.type == vm.types.aliasFilterType.entityList.value" id="entityListFilter">
48   - <md-checkbox flex aria-label="{{ 'alias.use-state-entity' | translate }}"
49   - ng-model="vm.filter.stateEntity">{{ 'alias.use-state-entity' | translate }}
50   - </md-checkbox>
51   - <tb-entity-type-select
52   - ng-if="!vm.filter.stateEntity"
53   - ng-model="vm.filter.entityType"
54   - the-form="theForm"
55   - ng-disabled="vm.filter.stateEntity"
56   - tb-required="!vm.filter.stateEntity"
57   - allowed-entity-types="vm.allowedEntityTypes">
58   - </tb-entity-type-select>
59   - <tb-entity-list
60   - ng-if="!vm.filter.stateEntity"
61   - ng-model="vm.filter.entityList"
62   - ng-disabled="vm.filter.stateEntity"
63   - tb-required="!vm.filter.stateEntity"
64   - entity-type="vm.filter.entityType">
65   - </tb-entity-list>
66   - </section>
67   - <section flex layout="column" ng-if="vm.filter.type == vm.types.aliasFilterType.entityName.value" id="entityNameFilter">
68   - <tb-entity-type-select
69   - ng-model="vm.filter.entityType"
70   - the-form="theForm"
71   - tb-required="true"
72   - allowed-entity-types="vm.allowedEntityTypes">
73   - </tb-entity-type-select>
74   - <md-input-container flex>
75   - <label translate>entity.name-starts-with</label>
76   - <input required name="entityNameFilter"
77   - ng-model="vm.filter.entityNameFilter"
78   - aria-label="{{ 'entity.name-starts-with' | translate }}">
79   - <div ng-messages="theForm.entityNameFilter.$error">
80   - <div ng-message="required" translate>entity.entity-name-filter-required</div>
  35 + <div layout="row">
  36 + <md-input-container flex class="md-block">
  37 + <label translate>alias.name</label>
  38 + <input required name="aliasName"
  39 + ng-model="vm.alias.alias"
  40 + aria-label="{{ 'alias.name' | translate }}">
  41 + <div ng-messages="theForm.aliasName.$error">
  42 + <div ng-message="required" translate>alias.name-required</div>
  43 + <div ng-message="duplicateAliasName" translate>alias.duplicate-alias</div>
81 44 </div>
82   -
83 45 </md-input-container>
84   - </section>
  46 + <section class="tb-resolve-multiple-switch" layout="column" layout-align="start center">
  47 + <label class="tb-small resolve-multiple-label" translate>alias.resolve-multiple</label>
  48 + <md-switch class="resolve-multiple-switch" ng-model="vm.alias.filter.resolveMultiple"
  49 + aria-label="{{ 'alias.resolve-multiple' | translate }}">
  50 + </md-switch>
  51 + </section>
  52 + </div>
  53 + <tb-entity-filter
  54 + ng-model="vm.alias.filter"
  55 + allowed-entity-types="vm.allowedEntityTypes"
  56 + the-form="theForm">
  57 + </tb-entity-filter>
85 58 <div class="tb-error-messages" ng-messages="theForm.$error" role="alert">
86 59 <div translate ng-message="entityFilter" class="tb-error-message">alias.entity-filter-no-entity-matched</div>
87 60 </div>
... ... @@ -92,9 +65,9 @@
92 65 <md-dialog-actions layout="row">
93 66 <span flex></span>
94 67 <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary">
95   - {{ 'action.save' | translate }}
  68 + {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }}
96 69 </md-button>
97 70 <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button>
98 71 </md-dialog-actions>
99 72 </form>
100   -</md-dialog>
\ No newline at end of file
  73 +</md-dialog>
... ...
... ... @@ -15,22 +15,27 @@
15 15 */
16 16 import './entity-aliases.scss';
17 17
  18 +/* eslint-disable import/no-unresolved, import/default */
  19 +
  20 +import entityAliasDialogTemplate from './entity-alias-dialog.tpl.html';
  21 +
  22 +/* eslint-enable import/no-unresolved, import/default */
  23 +
18 24 /*@ngInject*/
19 25 export default function EntityAliasesController(utils, entityService, toast, $scope, $mdDialog, $document, $q, $translate,
20 26 types, config) {
21 27
22 28 var vm = this;
23 29
24   - vm.isSingleEntityAlias = config.isSingleEntityAlias;
25   - vm.singleEntityAlias = config.singleEntityAlias;
  30 + vm.types = types;
26 31 vm.entityAliases = [];
27 32 vm.title = config.customTitle ? config.customTitle : 'entity.aliases';
28 33 vm.disableAdd = config.disableAdd;
29 34 vm.aliasToWidgetsMap = {};
30 35 vm.allowedEntityTypes = config.allowedEntityTypes;
31 36
32   - vm.onFilterEntityChanged = onFilterEntityChanged;
33 37 vm.addAlias = addAlias;
  38 + vm.editAlias = editAlias;
34 39 vm.removeAlias = removeAlias;
35 40
36 41 vm.cancel = cancel;
... ... @@ -79,42 +84,61 @@ export default function EntityAliasesController(utils, entityService, toast, $sc
79 84 }
80 85 }
81 86
82   - if (vm.isSingleEntityAlias) {
83   - checkEntityAlias(vm.singleEntityAlias);
84   - }
85   -
86 87 for (aliasId in config.entityAliases) {
87 88 var entityAlias = config.entityAliases[aliasId];
88   - var result = {id: aliasId, alias: entityAlias.alias, filter: entityAlias.filter, changed: true};
89   - checkEntityAlias(result);
  89 + var filter = entityAlias.filter;
  90 + if (!filter) {
  91 + filter = {
  92 + resolveMultiple: false
  93 + };
  94 + }
  95 + if (!filter.resolveMultiple) {
  96 + filter.resolveMultiple = false;
  97 + }
  98 + var result = {id: aliasId, alias: entityAlias.alias, filter: filter};
90 99 vm.entityAliases.push(result);
91 100 }
92 101 }
93 102
94   - function checkEntityAlias(entityAlias) {
95   - if (!entityAlias.filter || entityAlias.filter == null) {
96   - entityAlias.filter = {};
97   - }
  103 + function addAlias($event) {
  104 + openAliasDialog($event);
98 105 }
99 106
100   - function onFilterEntityChanged(entity, stateEntity, entityAlias) {
101   - if (entityAlias) {
102   - if (!entityAlias.alias || entityAlias.alias.length == 0) {
103   - entityAlias.changed = false;
104   - }
105   - if (!entityAlias.changed && entityAlias.filter && entityAlias.filter.type) {
106   - if (stateEntity) {
107   - entityAlias.alias = $translate.instant('alias.state-entity');
108   - } else {
109   - entityAlias.alias = entity.name;
110   - }
111   - }
112   - }
  107 + function editAlias($event, entityAlias) {
  108 + openAliasDialog($event, entityAlias);
113 109 }
114 110
115   - function addAlias() {
116   - var entityAlias = {id: utils.guid(), alias: '', filter: {}, changed: false};
117   - vm.entityAliases.push(entityAlias);
  111 + function openAliasDialog($event, entityAlias) {
  112 + var isAdd = entityAlias ? false : true;
  113 + var aliasIndex;
  114 + if (!isAdd) {
  115 + aliasIndex = vm.entityAliases.indexOf(entityAlias);
  116 + }
  117 + $mdDialog.show({
  118 + controller: 'EntityAliasDialogController',
  119 + controllerAs: 'vm',
  120 + templateUrl: entityAliasDialogTemplate,
  121 + locals: {
  122 + isAdd: isAdd,
  123 + allowedEntityTypes: vm.allowedEntityTypes,
  124 + entityAliases: vm.entityAliases,
  125 + alias: isAdd ? null : angular.copy(entityAlias)
  126 + },
  127 + parent: angular.element($document[0].body),
  128 + fullscreen: true,
  129 + skipHide: true,
  130 + targetEvent: $event
  131 + }).then(function (alias) {
  132 + if (isAdd) {
  133 + vm.entityAliases.push(alias);
  134 + } else {
  135 + vm.entityAliases[aliasIndex] = alias;
  136 + }
  137 + if ($scope.theForm) {
  138 + $scope.theForm.$setDirty();
  139 + }
  140 + }, function () {
  141 + });
118 142 }
119 143
120 144 function removeAlias($event, entityAlias) {
... ... @@ -157,43 +181,30 @@ export default function EntityAliasesController(utils, entityService, toast, $sc
157 181 var uniqueAliasList = {};
158 182
159 183 var valid = true;
160   - var aliasId;
161   - var alias;
162   - var i;
163   -
164   - if (vm.isSingleEntityAlias) {
165   - if (!vm.singleEntityAlias.id) {
166   - vm.singleEntityAlias.id = utils.guid();
167   - }
168   - for (i = 0; i < vm.entityAliases.length; i ++) {
169   - alias = vm.entityAliases[i].alias;
170   - if (alias === vm.singleEntityAlias.alias) {
171   - valid = false;
172   - break;
173   - }
174   - }
175   - } else {
176   - for (i = 0; i < vm.entityAliases.length; i++) {
177   - aliasId = vm.entityAliases[i].id;
178   - alias = vm.entityAliases[i].alias;
179   - if (!uniqueAliasList[alias]) {
180   - uniqueAliasList[alias] = alias;
181   - entityAliases[aliasId] = {id: aliasId, alias: alias, filter: vm.entityAliases[i].filter};
182   - } else {
183   - valid = false;
184   - break;
185   - }
  184 + var message, aliasId, alias, filter;
  185 +
  186 + for (var i = 0; i < vm.entityAliases.length; i++) {
  187 + aliasId = vm.entityAliases[i].id;
  188 + alias = vm.entityAliases[i].alias;
  189 + filter = vm.entityAliases[i].filter;
  190 + if (uniqueAliasList[alias]) {
  191 + valid = false;
  192 + message = $translate.instant('entity.duplicate-alias-error', {alias: alias});
  193 + break;
  194 + } else if (!filter || !filter.type) {
  195 + valid = false;
  196 + message = $translate.instant('entity.missing-entity-filter-error', {alias: alias});
  197 + break;
  198 + } else {
  199 + uniqueAliasList[alias] = alias;
  200 + entityAliases[aliasId] = {id: aliasId, alias: alias, filter: filter};
186 201 }
187 202 }
188 203 if (valid) {
189 204 $scope.theForm.$setPristine();
190   - if (vm.isSingleEntityAlias) {
191   - $mdDialog.hide(vm.singleEntityAlias);
192   - } else {
193   - $mdDialog.hide(entityAliases);
194   - }
  205 + $mdDialog.hide(entityAliases);
195 206 } else {
196   - toast.showError($translate.instant('entity.duplicate-alias-error', {alias: alias}));
  207 + toast.showError(message);
197 208 }
198 209 }
199 210
... ...
... ... @@ -17,6 +17,7 @@
17 17 .tb-aliases-dialog {
18 18 .md-dialog-content {
19 19 padding-bottom: 0px;
  20 + padding-top: 0px;
20 21 }
21 22 .tb-aliases-header {
22 23 min-height: 40px;
... ... @@ -33,5 +34,16 @@
33 34 md-input-container {
34 35 margin: 0px;
35 36 }
  37 + .tb-resolve-multiple-switch {
  38 + padding-left: 10px;
  39 + .resolve-multiple-switch {
  40 + margin: 0;
  41 + }
  42 + }
  43 + .md-button {
  44 + &.md-icon-button {
  45 + margin: 0px;
  46 + }
  47 + }
36 48 }
37 49 }
... ...
... ... @@ -19,7 +19,7 @@
19 19 <form name="theForm" ng-submit="vm.save()">
20 20 <md-toolbar>
21 21 <div class="md-toolbar-tools">
22   - <h2>{{ vm.isSingleEntityAlias ? ('entity.configure-alias' | translate:vm.singleEntityAlias ) : (vm.title | translate) }}</h2>
  22 + <h2>{{ vm.title | translate }}</h2>
23 23 <span flex></span>
24 24 <md-button class="md-icon-button" ng-click="vm.cancel()">
25 25 <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
... ... @@ -28,66 +28,72 @@
28 28 </md-toolbar>
29 29 <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
30 30 <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
31   - <div class="tb-aliases-header" ng-show="!vm.isSingleEntityAlias" flex layout="row" layout-align="start center">
  31 + <div class="tb-aliases-header" flex layout="row" layout-align="start center">
32 32 <span flex="5"></span>
33 33 <div flex layout="row" layout-align="start center">
34   - <span class="tb-header-label" translate flex="20" style="min-width: 150px;">entity.alias</span>
35   - <div flex="80" layout="row" layout-align="start center" style="min-width: 240px; padding-left: 10px;">
36   - <span class="tb-header-label" translate flex="70">alias.entity-filter</span>
37   - <span class="tb-header-label" translate flex="30" style="padding-left: 10px;" >alias.resolve-multiple</span>
38   - </div>
39   - <span style="min-width: 40px; margin: 0 6px;"></span>
  34 + <span class="tb-header-label" translate flex="20" style="min-width: 150px;">alias.name</span>
  35 + <span class="tb-header-label" translate flex="70" style="padding-left: 10px;">alias.entity-filter</span>
  36 + <span class="tb-header-label" translate flex="10" style="padding-left: 10px; min-width: 120px;">alias.resolve-multiple</span>
  37 + <span style="min-width: 80px;"></span>
40 38 </div>
41 39 </div>
42   - <md-divider ng-show="!vm.isSingleEntityAlias"></md-divider>
  40 + <md-divider></md-divider>
43 41 <md-dialog-content>
44 42 <div class="md-dialog-content">
45 43 <fieldset ng-disabled="loading">
46   - <div ng-show="vm.isSingleEntityAlias" layout="row">
47   - <tb-entity-filter flex
48   - allowed-entity-types="vm.allowedEntityTypes"
49   - ng-model="vm.singleEntityAlias.filter">
50   - </tb-entity-filter>
51   - </div>
52   - <div ng-show="!vm.isSingleEntityAlias" style="height: 100%; overflow: auto; padding-bottom: 20px;">
53   - <div ng-form name="aliasForm" flex layout="row" layout-align="start center" ng-repeat="entityAlias in vm.entityAliases track by $index">
54   - <span flex="5">{{$index + 1}}.</span>
55   - <div class="md-whiteframe-4dp tb-alias" flex layout="row" layout-align="start center">
56   - <md-input-container flex="20" style="min-width: 150px;" md-no-float class="md-block">
57   - <input required ng-change="entityAlias.changed=true" name="alias" placeholder="{{ 'entity.alias' | translate }}" ng-model="entityAlias.alias">
58   - <div ng-messages="aliasForm.alias.$error">
59   - <div translate ng-message="required">entity.alias-required</div>
60   - </div>
61   - </md-input-container>
62   - <tb-entity-filter flex="80" style="min-width: 240px; padding-left: 10px;"
63   - hide-labels
64   - allowed-entity-types="vm.allowedEntityTypes"
65   - ng-model="entityAlias.filter"
66   - on-matching-entity-change="vm.onFilterEntityChanged(entity, stateEntity, entityAlias)">
67   - </tb-entity-filter>
68   - <md-button ng-disabled="loading" class="md-icon-button md-primary" style="min-width: 40px;"
69   - ng-click="vm.removeAlias($event, entityAlias)" aria-label="{{ 'action.remove' | translate }}">
70   - <md-tooltip md-direction="top">
71   - {{ 'entity.remove-alias' | translate }}
72   - </md-tooltip>
73   - <md-icon aria-label="{{ 'action.delete' | translate }}" class="material-icons">
74   - close
75   - </md-icon>
76   - </md-button>
77   - </div>
78   - </div>
  44 + <div ng-form name="aliasForm" flex layout="row" layout-align="start center" ng-repeat="entityAlias in vm.entityAliases track by $index">
  45 + <span flex="5">{{$index + 1}}.</span>
  46 + <di class="md-whiteframe-4dp tb-alias" flex layout="row" layout-align="start center">
  47 + <md-input-container flex="20" style="min-width: 150px;" md-no-float class="md-block">
  48 + <input required name="alias" placeholder="{{ 'entity.alias' | translate }}" ng-model="entityAlias.alias">
  49 + <div ng-messages="aliasForm.alias.$error">
  50 + <div translate ng-message="required">entity.alias-required</div>
  51 + </div>
  52 + </md-input-container>
  53 + <tb-entity-filter-view
  54 + flex="70" style="padding-left: 10px;"
  55 + ng-model="entityAlias.filter">
  56 + </tb-entity-filter-view>
  57 + <section flex="10" style="padding-left: 10px; min-width: 120px;"
  58 + class="tb-resolve-multiple-switch"
  59 + layout="column"
  60 + layout-align="center center">
  61 + <md-switch class="resolve-multiple-switch"
  62 + ng-model="entityAlias.filter.resolveMultiple"
  63 + aria-label="resolve-multiple-switcher">
  64 + </md-switch>
  65 + </section>
  66 + <md-button ng-disabled="loading" class="md-icon-button md-primary" style="min-width: 40px;"
  67 + ng-click="vm.editAlias($event, entityAlias)" aria-label="{{ 'action.edit' | translate }}">
  68 + <md-tooltip md-direction="top">
  69 + {{ 'alias.edit' | translate }}
  70 + </md-tooltip>
  71 + <md-icon aria-label="{{ 'alias.edit' | translate }}" class="material-icons">
  72 + edit
  73 + </md-icon>
  74 + </md-button>
  75 + <md-button ng-disabled="loading" class="md-icon-button md-primary" style="min-width: 40px;"
  76 + ng-click="vm.removeAlias($event, entityAlias)" aria-label="{{ 'action.remove' | translate }}">
  77 + <md-tooltip md-direction="top">
  78 + {{ 'entity.remove-alias' | translate }}
  79 + </md-tooltip>
  80 + <md-icon aria-label="{{ 'action.delete' | translate }}" class="material-icons">
  81 + close
  82 + </md-icon>
  83 + </md-button>
  84 + </di>
79 85 </div>
80 86 </fieldset>
81 87 </div>
82 88 </md-dialog-content>
83 89 <md-dialog-actions layout="row">
84   - <md-button ng-show="!vm.isSingleEntityAlias && !vm.disableAdd" ng-disabled="loading" class="md-primary md-raised"
  90 + <md-button ng-show="!vm.disableAdd" ng-disabled="loading" class="md-primary md-raised"
85 91 ng-click="vm.addAlias($event)"
86   - aria-label="{{ 'action.add' | translate }}">
  92 + aria-label="{{ 'alias.add' | translate }}">
87 93 <md-tooltip md-direction="top">
88   - {{ 'entity.add-alias' | translate }}
  94 + {{ 'alias.add' | translate }}
89 95 </md-tooltip>
90   - <span translate>action.add</span>
  96 + <span translate>alias.add</span>
91 97 </md-button>
92 98 <span flex></span>
93 99 <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary">
... ...
  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 entityFilterViewTemplate from './entity-filter-view.tpl.html';
  20 +
  21 +/* eslint-enable import/no-unresolved, import/default */
  22 +
  23 +import './entity-filter-view.scss';
  24 +
  25 +/*@ngInject*/
  26 +export default function EntityFilterViewDirective($compile, $templateCache, $q, $document, $mdDialog, $translate, types/*, entityService*/) {
  27 +
  28 + var linker = function (scope, element, attrs, ngModelCtrl) {
  29 +
  30 + var template = $templateCache.get(entityFilterViewTemplate);
  31 + element.html(template);
  32 +
  33 + scope.ngModelCtrl = ngModelCtrl;
  34 + scope.types = types;
  35 + scope.filterDisplayValue = '';
  36 +
  37 + scope.$watch('filter', function () {
  38 + scope.updateDisplayValue();
  39 + });
  40 +
  41 + scope.updateDisplayValue = function() {
  42 + if (scope.filter && scope.filter.type) {
  43 + var entityType;
  44 + var prefix;
  45 + switch (scope.filter.type) {
  46 + case types.aliasFilterType.entityList.value:
  47 + entityType = scope.filter.entityType;
  48 + var count = scope.filter.entityList.length;
  49 + scope.filterDisplayValue = $translate.instant(types.entityTypeTranslations[entityType].list, {count: count}, 'messageformat');
  50 + break;
  51 + case types.aliasFilterType.entityName.value:
  52 + entityType = scope.filter.entityType;
  53 + prefix = scope.filter.entityNameFilter;
  54 + scope.filterDisplayValue = $translate.instant(types.entityTypeTranslations[entityType].nameStartsWith, {prefix: prefix});
  55 + break;
  56 + case types.aliasFilterType.stateEntity.value:
  57 + scope.filterDisplayValue = $translate.instant('alias.filter-type-state-entity-description');
  58 + break;
  59 + case types.aliasFilterType.assetType.value:
  60 + var assetType = scope.filter.assetType;
  61 + prefix = scope.filter.assetNameFilter;
  62 + if (prefix && prefix.length) {
  63 + scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-type-and-name-description', {assetType: assetType, prefix: prefix});
  64 + } else {
  65 + scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-type-description', {assetType: assetType});
  66 + }
  67 + break;
  68 + case types.aliasFilterType.deviceType.value:
  69 + var deviceType = scope.filter.deviceType;
  70 + prefix = scope.filter.deviceNameFilter;
  71 + if (prefix && prefix.length) {
  72 + scope.filterDisplayValue = $translate.instant('alias.filter-type-device-type-and-name-description', {deviceType: deviceType, prefix: prefix});
  73 + } else {
  74 + scope.filterDisplayValue = $translate.instant('alias.filter-type-device-type-description', {deviceType: deviceType});
  75 + }
  76 + break;
  77 + //TODO: Alias filter
  78 + default:
  79 + scope.filterDisplayValue = scope.filter.type;
  80 + break;
  81 + }
  82 + } else {
  83 + scope.filterDisplayValue = '';
  84 + }
  85 + }
  86 +
  87 + ngModelCtrl.$render = function () {
  88 + if (ngModelCtrl.$viewValue) {
  89 + scope.filter = ngModelCtrl.$viewValue;
  90 + } else {
  91 + scope.filter = null;
  92 + }
  93 + }
  94 +
  95 + $compile(element.contents())(scope);
  96 +
  97 + }
  98 +
  99 + return {
  100 + restrict: "E",
  101 + require: "^ngModel",
  102 + link: linker,
  103 + scope: true
  104 + };
  105 +
  106 +}
... ...
  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-filter-view {
  18 + .entity-filter-empty {
  19 + color: rgba(221, 44, 0, 0.87);
  20 + font-size: 14px;
  21 + line-height: 16px;
  22 + }
  23 + .entity-filter-type {
  24 + font-size: 14px;
  25 + line-height: 16px;
  26 + color: rgba(0, 0, 0, 0.570588);
  27 + }
  28 + .entity-filter-value {
  29 + font-size: 14px;
  30 + line-height: 16px;
  31 + color: rgba(0, 0, 0, 0.570588);
  32 + }
  33 +}
\ 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 +<div layout='column' class="tb-entity-filter-view">
  20 + <div ng-if="!filter || !filter.type" class="entity-filter-empty" translate>alias.no-entity-filter-specified</div>
  21 + <div ng-if="filter && filter.type" layout="column">
  22 + <div class="entity-filter-value">{{ filterDisplayValue }}</div>
  23 + </div>
  24 +</div>
... ...
... ... @@ -17,16 +17,13 @@
17 17 /* eslint-disable import/no-unresolved, import/default */
18 18
19 19 import entityFilterTemplate from './entity-filter.tpl.html';
20   -import entityFilterDialogTemplate from './entity-filter-dialog.tpl.html';
21 20
22 21 /* eslint-enable import/no-unresolved, import/default */
23 22
24   -import EntityFilterDialogController from './entity-filter-dialog.controller';
25   -
26 23 import './entity-filter.scss';
27 24
28 25 /*@ngInject*/
29   -export default function EntityFilterDirective($compile, $templateCache, $q, $document, $mdDialog, types) {
  26 +export default function EntityFilterDirective($compile, $templateCache, $q, $document, $mdDialog, types, entityService) {
30 27
31 28 var linker = function (scope, element, attrs, ngModelCtrl) {
32 29
... ... @@ -35,66 +32,59 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
35 32
36 33 scope.ngModelCtrl = ngModelCtrl;
37 34 scope.types = types;
38   - scope.hideLabels = angular.isDefined(attrs.hideLabels);
  35 + scope.aliasFilterTypes = entityService.getAliasFilterTypesByEntityTypes(scope.allowedEntityTypes);
39 36
40   - scope.updateValidity = function() {
41   - if (ngModelCtrl.$viewValue) {
42   - var value = ngModelCtrl.$viewValue;
43   - ngModelCtrl.$setValidity('filter', value.type ? true : false);
  37 + scope.$watch('filter.type', function (newType, prevType) {
  38 + if (newType && newType != prevType) {
  39 + updateFilter();
44 40 }
45   - }
  41 + });
46 42
47   - ngModelCtrl.$render = function () {
48   - if (ngModelCtrl.$viewValue) {
49   - scope.model = angular.copy(ngModelCtrl.$viewValue);
50   - } else {
51   - scope.model = {
52   - type: null,
53   - resolveMultiple: false
54   - }
  43 + function updateFilter() {
  44 + var filter = {};
  45 + filter.type = scope.filter.type;
  46 + filter.resolveMultiple = scope.filter.resolveMultiple;
  47 + switch (filter.type) {
  48 + case types.aliasFilterType.entityList.value:
  49 + filter.entityType = null;
  50 + filter.entityList = [];
  51 + break;
  52 + case types.aliasFilterType.entityName.value:
  53 + filter.entityType = null;
  54 + filter.entityNameFilter = '';
  55 + break;
  56 + case types.aliasFilterType.stateEntity.value:
  57 + break;
  58 + case types.aliasFilterType.assetType.value:
  59 + filter.assetType = null;
  60 + filter.assetNameFilter = '';
  61 + break;
  62 + case types.aliasFilterType.deviceType.value:
  63 + filter.deviceType = null;
  64 + filter.deviceNameFilter = '';
  65 + break;
  66 + //TODO: Alias filter
55 67 }
  68 + scope.filter = filter;
56 69 }
57 70
58   - scope.$watch('model.resolveMultiple', function () {
59   - if (ngModelCtrl.$viewValue) {
60   - var value = ngModelCtrl.$viewValue;
61   - value.resolveMultiple = scope.model.resolveMultiple;
62   - ngModelCtrl.$setViewValue(value);
63   - scope.updateValidity();
64   - }
  71 + scope.$watch('filter', function () {
  72 + scope.updateView();
65 73 });
66 74
67   - scope.editFilter = function($event) {
68   - openEntityFilterDialog($event, false);
  75 + scope.updateView = function() {
  76 + ngModelCtrl.$setViewValue(scope.filter);
69 77 }
70 78
71   - scope.createFilter = function($event) {
72   - openEntityFilterDialog($event, true);
73   - }
74   -
75   - function openEntityFilterDialog($event, isAdd) {
76   - $mdDialog.show({
77   - controller: EntityFilterDialogController,
78   - controllerAs: 'vm',
79   - templateUrl: entityFilterDialogTemplate,
80   - locals: {
81   - isAdd: isAdd,
82   - allowedEntityTypes: scope.allowedEntityTypes,
83   - filter: angular.copy(scope.model)
84   - },
85   - parent: angular.element($document[0].body),
86   - fullscreen: true,
87   - skipHide: true,
88   - targetEvent: $event
89   - }).then(function (result) {
90   - scope.model = result.filter;
91   - ngModelCtrl.$setViewValue(result.filter);
92   - scope.updateValidity();
93   - if (scope.onMatchingEntityChange) {
94   - scope.onMatchingEntityChange({entity: result.entity, stateEntity: result.stateEntity});
  79 + ngModelCtrl.$render = function () {
  80 + if (ngModelCtrl.$viewValue) {
  81 + scope.filter = ngModelCtrl.$viewValue;
  82 + } else {
  83 + scope.filter = {
  84 + type: null,
  85 + resolveMultiple: false
95 86 }
96   - }, function () {
97   - });
  87 + }
98 88 }
99 89
100 90 $compile(element.contents())(scope);
... ... @@ -106,8 +96,8 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
106 96 require: "^ngModel",
107 97 link: linker,
108 98 scope: {
109   - allowedEntityTypes: '=?',
110   - onMatchingEntityChange: '&'
  99 + theForm: '=',
  100 + allowedEntityTypes: '=?'
111 101 }
112 102 };
113 103
... ...
... ... @@ -13,22 +13,7 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 .tb-entity-filter {
17   - .tb-filter-switch {
18   - padding-left: 10px;
19   - .filter-switch {
20   - margin: 0;
21   - }
22   - .filter-label {
23   - margin: 5px 0;
24   - }
25   - }
26   - .tb-error-messages {
27   - margin-top: -11px;
28   - height: 35px;
29   - .tb-error-message {
30   - padding-left: 8px;
31   - padding-top: 14px;
32   - }
33   - }
  18 +
34 19 }
\ No newline at end of file
... ...
... ... @@ -15,37 +15,77 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<section layout='row' class="tb-entity-filter">
19   - <section layout="row" flex="70">
20   - <section flex layout="column" layout-align="center start">
21   - <div ng-if="model.type">{{ types.aliasFilterType[model.type].name | translate }}</div>
22   - <md-button ng-if="!model.type"
23   - ng-disabled="loading" class="md-primary"
24   - ng-click="createFilter($event)"
25   - aria-label="{{ 'alias.create-entity-filter' | translate }}">
26   - <md-icon aria-label="{{ 'alias.create-entity-filter' | translate }}"
27   - class="material-icons">
28   - add
29   - </md-icon>
30   - {{ 'alias.create-entity-filter' | translate }}
31   - </md-button>
32   - </section>
33   - <md-button ng-if="model.type" ng-disabled="loading" class="md-icon-button md-primary"
34   - style="min-width: 40px;"
35   - ng-click="editFilter($event)"
36   - aria-label="{{ 'alias.edit-entity-filter' | translate }}">
37   - <md-tooltip md-direction="top">
38   - {{ 'alias.edit-entity-filter' | translate }}
39   - </md-tooltip>
40   - <md-icon aria-label="{{ 'alias.edit-entity-filter' | translate }}"
41   - class="material-icons">
42   - edit
43   - </md-icon>
44   - </md-button>
  18 +<div layout='column' class="tb-entity-filter">
  19 + <md-input-container class="md-block">
  20 + <label>{{ 'alias.filter-type' | translate }}</label>
  21 + <md-select required name="filterType"
  22 + ng-model="filter.type" aria-label="{{ 'alias.filter-type' | translate }}">
  23 + <md-option ng-repeat="type in aliasFilterTypes" ng-value="type.value">
  24 + {{type.name | translate}}
  25 + </md-option>
  26 + </md-select>
  27 + <div ng-messages="theForm.filterType.$error">
  28 + <div ng-message="required" translate>alias.filter-type-required</div>
  29 + </div>
  30 + </md-input-container>
  31 + <section layout="column" ng-if="filter.type == types.aliasFilterType.entityList.value" id="entityListFilter">
  32 + <tb-entity-type-select
  33 + ng-model="filter.entityType"
  34 + the-form="theForm"
  35 + tb-required="true"
  36 + allowed-entity-types="allowedEntityTypes">
  37 + </tb-entity-type-select>
  38 + <tb-entity-list
  39 + ng-model="filter.entityList"
  40 + tb-required="true"
  41 + entity-type="filter.entityType">
  42 + </tb-entity-list>
45 43 </section>
46   - <section class="tb-filter-switch" layout="column" flex="30" layout-align="center center">
47   - <label ng-if="!hideLabels" class="tb-small filter-label" translate>alias.resolve-multiple</label>
48   - <md-switch class="filter-switch" ng-model="model.resolveMultiple" aria-label="resolve-multiple-switcher">
49   - </md-switch>
  44 + <section flex layout="column" ng-if="filter.type == types.aliasFilterType.entityName.value" id="entityNameFilter">
  45 + <tb-entity-type-select
  46 + ng-model="filter.entityType"
  47 + the-form="theForm"
  48 + tb-required="true"
  49 + allowed-entity-types="allowedEntityTypes">
  50 + </tb-entity-type-select>
  51 + <md-input-container class="md-block">
  52 + <label translate>entity.name-starts-with</label>
  53 + <input required name="entityNameFilter"
  54 + ng-model="filter.entityNameFilter"
  55 + aria-label="{{ 'entity.name-starts-with' | translate }}">
  56 + <div ng-messages="theForm.entityNameFilter.$error">
  57 + <div ng-message="required" translate>entity.entity-name-filter-required</div>
  58 + </div>
  59 + </md-input-container>
50 60 </section>
51   -</section>
  61 + <section layout="column" ng-if="filter.type == types.aliasFilterType.stateEntity.value" id="stateEntityFilter">
  62 + </section>
  63 + <section layout="column" ng-if="filter.type == types.aliasFilterType.assetType.value" id="assetTypeFilter">
  64 + <tb-entity-subtype-autocomplete
  65 + tb-required="true"
  66 + the-form="theForm"
  67 + ng-model="filter.assetType"
  68 + entity-type="types.entityType.asset">
  69 + </tb-entity-subtype-autocomplete>
  70 + <md-input-container class="md-block">
  71 + <label translate>asset.name-starts-with</label>
  72 + <input name="assetNameFilter"
  73 + ng-model="filter.assetNameFilter"
  74 + aria-label="{{ 'asset.name-starts-with' | translate }}">
  75 + </md-input-container>
  76 + </section>
  77 + <section layout="column" ng-if="filter.type == types.aliasFilterType.deviceType.value" id="deviceTypeFilter">
  78 + <tb-entity-subtype-autocomplete
  79 + tb-required="true"
  80 + the-form="theForm"
  81 + ng-model="filter.deviceType"
  82 + entity-type="types.entityType.device">
  83 + </tb-entity-subtype-autocomplete>
  84 + <md-input-container class="md-block">
  85 + <label translate>device.name-starts-with</label>
  86 + <input name="deviceNameFilter"
  87 + ng-model="filter.deviceNameFilter"
  88 + aria-label="{{ 'device.name-starts-with' | translate }}">
  89 + </md-input-container>
  90 + </section>
  91 +</div>
... ...
... ... @@ -71,7 +71,7 @@ export default function EntityTypeSelect($compile, $templateCache, utils, userSe
71 71 }
72 72
73 73 scope.typeName = function(type) {
74   - return utils.entityTypeName(type);
  74 + return type ? types.entityTypeTranslations[type].type : '';
75 75 }
76 76
77 77 scope.updateValidity = function () {
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16
17 17 import EntityAliasesController from './entity-aliases.controller';
  18 +import EntityAliasDialogController from './entity-alias-dialog.controller';
18 19 import EntityTypeSelectDirective from './entity-type-select.directive';
19 20 import EntitySubtypeSelectDirective from './entity-subtype-select.directive';
20 21 import EntitySubtypeAutocompleteDirective from './entity-subtype-autocomplete.directive';
... ... @@ -22,6 +23,7 @@ import EntityAutocompleteDirective from './entity-autocomplete.directive';
22 23 import EntityListDirective from './entity-list.directive';
23 24 import EntitySelectDirective from './entity-select.directive';
24 25 import EntityFilterDirective from './entity-filter.directive';
  26 +import EntityFilterViewDirective from './entity-filter-view.directive';
25 27 import AliasesEntitySelectPanelController from './aliases-entity-select-panel.controller';
26 28 import AliasesEntitySelectDirective from './aliases-entity-select.directive';
27 29 import AddAttributeDialogController from './attribute/add-attribute-dialog.controller';
... ... @@ -32,6 +34,7 @@ import RelationTypeAutocompleteDirective from './relation/relation-type-autocomp
32 34
33 35 export default angular.module('thingsboard.entity', [])
34 36 .controller('EntityAliasesController', EntityAliasesController)
  37 + .controller('EntityAliasDialogController', EntityAliasDialogController)
35 38 .controller('AliasesEntitySelectPanelController', AliasesEntitySelectPanelController)
36 39 .controller('AddAttributeDialogController', AddAttributeDialogController)
37 40 .controller('AddWidgetToDashboardDialogController', AddWidgetToDashboardDialogController)
... ... @@ -42,6 +45,7 @@ export default angular.module('thingsboard.entity', [])
42 45 .directive('tbEntityList', EntityListDirective)
43 46 .directive('tbEntitySelect', EntitySelectDirective)
44 47 .directive('tbEntityFilter', EntityFilterDirective)
  48 + .directive('tbEntityFilterView', EntityFilterViewDirective)
45 49 .directive('tbAliasesEntitySelect', AliasesEntitySelectDirective)
46 50 .directive('tbAttributeTable', AttributeTableDirective)
47 51 .directive('tbRelationTable', RelationTableDirective)
... ...
... ... @@ -218,9 +218,9 @@ function RelationTableController($scope, $q, $mdDialog, $document, $translate, $
218 218 function success(allRelations) {
219 219 allRelations.forEach(function(relation) {
220 220 if (vm.direction == vm.types.entitySearchDirection.from) {
221   - relation.toEntityTypeName = $translate.instant(utils.entityTypeName(relation.to.entityType));
  221 + relation.toEntityTypeName = $translate.instant(types.entityTypeTranslations[relation.to.entityType].type);
222 222 } else {
223   - relation.fromEntityTypeName = $translate.instant(utils.entityTypeName(relation.from.entityType));
  223 + relation.fromEntityTypeName = $translate.instant(types.entityTypeTranslations[relation.from.entityType].type);
224 224 }
225 225 });
226 226 vm.allRelations = allRelations;
... ...
... ... @@ -365,7 +365,6 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
365 365 alias = aliasInfo.aliasName;
366 366 filter = {
367 367 type: types.aliasFilterType.entityList.value,
368   - stateEntity: false,
369 368 entityType: types.entityType.device,
370 369 entityList: [aliasInfo.deviceId],
371 370 resolveMultiple: false
... ... @@ -378,7 +377,6 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
378 377 resolveMultiple: false
379 378 }
380 379 if (filter.type == types.aliasFilterType.entityList.value) {
381   - filter.stateEntity = false;
382 380 filter.entityList = aliasInfo.deviceFilter.deviceList
383 381 } else {
384 382 filter.entityNameFilter = aliasInfo.deviceFilter.deviceNameFilter;
... ... @@ -391,7 +389,6 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
391 389 resolveMultiple: false
392 390 }
393 391 if (filter.type == types.aliasFilterType.entityList.value) {
394   - filter.stateEntity = false;
395 392 filter.entityList = aliasInfo.entityFilter.entityList;
396 393 } else {
397 394 filter.entityNameFilter = aliasInfo.entityFilter.entityNameFilter;
... ... @@ -662,8 +659,6 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
662 659 entityAliases: missingEntityAliases,
663 660 widgets: widgets,
664 661 isSingleWidget: isSingleWidget,
665   - isSingleEntityAlias: false,
666   - singleEntityAlias: null,
667 662 customTitle: customTitle,
668 663 disableAdd: true
669 664 }
... ...
... ... @@ -113,23 +113,30 @@ export default angular.module('thingsboard.locale', [])
113 113 "alarm-required": "Alarm is required"
114 114 },
115 115 "alias": {
  116 + "add": "Add alias",
  117 + "edit": "Edit alias",
  118 + "name": "Alias name",
  119 + "name-required": "Alias name is required",
  120 + "duplicate-alias": "Alias with same name is already exists.",
116 121 "filter-type-entity-list": "Entity list",
117 122 "filter-type-entity-name": "Entity name",
  123 + "filter-type-state-entity": "Entity from dashboard state",
  124 + "filter-type-state-entity-description": "Entity taken from dashboard state parameters",
118 125 "filter-type-asset-type": "Asset type",
  126 + "filter-type-asset-type-description": "Assets of type '{{assetType}}'",
  127 + "filter-type-asset-type-and-name-description": "Assets of type '{{assetType}}' and with name starting with '{{prefix}}'",
119 128 "filter-type-device-type": "Device type",
  129 + "filter-type-device-type-description": "Devices of type '{{deviceType}}'",
  130 + "filter-type-device-type-and-name-description": "Devices of type '{{deviceType}}' and with name starting with '{{prefix}}'",
120 131 "filter-type-relations-query": "Relations query",
121 132 "filter-type-asset-search-query": "Asset search query",
122 133 "filter-type-device-search-query": "Device search query",
123 134 "entity-filter": "Entity filter",
124   - "create-entity-filter": "Create entity filter",
125   - "edit-entity-filter": "Edit entity filter",
126   - "entity-filter-required": "Entity filter is required.",
127 135 "resolve-multiple": "Resolve as multiple entities",
128 136 "filter-type": "Filter type",
129 137 "filter-type-required": "Filter type is required.",
130   - "use-state-entity": "Use state entity",
131   - "state-entity": "State entity",
132 138 "entity-filter-no-entity-matched": "No entities matching specified filter were found.",
  139 + "no-entity-filter-specified": "No entity filter specified"
133 140 },
134 141 "asset": {
135 142 "asset": "Asset",
... ... @@ -185,7 +192,8 @@ export default angular.module('thingsboard.locale', [])
185 192 "idCopiedMessage": "Asset Id has been copied to clipboard",
186 193 "select-asset": "Select asset",
187 194 "no-assets-matching": "No assets matching '{{entity}}' were found.",
188   - "asset-required": "Asset is required"
  195 + "asset-required": "Asset is required",
  196 + "name-starts-with": "Asset name starts with"
189 197 },
190 198 "attribute": {
191 199 "attributes": "Attributes",
... ... @@ -463,7 +471,7 @@ export default angular.module('thingsboard.locale', [])
463 471 "alias-required": "Device alias is required.",
464 472 "remove-alias": "Remove device alias",
465 473 "add-alias": "Add device alias",
466   - "name-starts-with": "Name starts with",
  474 + "name-starts-with": "Device name starts with",
467 475 "device-list": "Device list",
468 476 "use-device-name-filter": "Use filter",
469 477 "device-list-empty": "No devices selected.",
... ... @@ -549,6 +557,7 @@ export default angular.module('thingsboard.locale', [])
549 557 "unable-delete-entity-alias-title": "Unable to delete entity alias",
550 558 "unable-delete-entity-alias-text": "Entity alias '{{entityAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",
551 559 "duplicate-alias-error": "Duplicate alias found '{{alias}}'.<br>Entity aliases must be unique whithin the dashboard.",
  560 + "missing-entity-filter-error": "Filter is missing for alias '{{alias}}'.",
552 561 "configure-alias": "Configure '{{alias}}' alias",
553 562 "alias": "Alias",
554 563 "alias-required": "Entity alias is required.",
... ... @@ -562,24 +571,42 @@ export default angular.module('thingsboard.locale', [])
562 571 "entity-name-filter-required": "Entity name filter is required.",
563 572 "entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.",
564 573 "all-subtypes": "All",
  574 + "select-entities": "Select entities",
  575 + "no-aliases-found": "No aliases found.",
  576 + "no-alias-matching": "'{{alias}}' not found.",
  577 + "create-new-alias": "Create a new one!",
  578 + "no-keys-found": "No keys found.",
  579 + "no-key-matching": "'{{key}}' not found.",
  580 + "create-new-key": "Create a new one!",
565 581 "type": "Type",
566 582 "type-required": "Entity type is required.",
567 583 "type-device": "Device",
  584 + "list-of-devices": "{ count, select, 1 {One device} other {List of # devices} }",
  585 + "device-name-starts-with": "Devices whose names start with '{{prefix}}'",
568 586 "type-asset": "Asset",
  587 + "list-of-assets": "{ count, select, 1 {One asset} other {List of # assets} }",
  588 + "asset-name-starts-with": "Assets whose names start with '{{prefix}}'",
569 589 "type-rule": "Rule",
  590 + "list-of-rules": "{ count, select, 1 {One rule} other {List of # rules} }",
  591 + "rule-name-starts-with": "Rules whose names start with '{{prefix}}'",
570 592 "type-plugin": "Plugin",
  593 + "list-of-plugins": "{ count, select, 1 {One plugin} other {List of # plugins} }",
  594 + "plugin-name-starts-with": "Plugins whose names start with '{{prefix}}'",
571 595 "type-tenant": "Tenant",
  596 + "list-of-tenants": "{ count, select, 1 {One tenant} other {List of # tenants} }",
  597 + "tenant-name-starts-with": "Tenants whose names start with '{{prefix}}'",
572 598 "type-customer": "Customer",
  599 + "list-of-customers": "{ count, select, 1 {One customer} other {List of # customers} }",
  600 + "customer-name-starts-with": "Customers whose names start with '{{prefix}}'",
573 601 "type-user": "User",
  602 + "list-of-users": "{ count, select, 1 {One user} other {List of # users} }",
  603 + "user-name-starts-with": "Users whose names start with '{{prefix}}'",
574 604 "type-dashboard": "Dashboard",
  605 + "list-of-dashboards": "{ count, select, 1 {One dashboard} other {List of # dashboards} }",
  606 + "dashboard-name-starts-with": "Dashboards whose names start with '{{prefix}}'",
575 607 "type-alarm": "Alarm",
576   - "select-entities": "Select entities",
577   - "no-aliases-found": "No aliases found.",
578   - "no-alias-matching": "'{{alias}}' not found.",
579   - "create-new-alias": "Create a new one!",
580   - "no-keys-found": "No keys found.",
581   - "no-key-matching": "'{{key}}' not found.",
582   - "create-new-key": "Create a new one!"
  608 + "list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }",
  609 + "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'"
583 610 },
584 611 "event": {
585 612 "event-type": "Event type",
... ...