From 0c0b3dade41aaa80996cece0dc3ee7865dc44143 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 7 Jun 2017 17:09:04 +0300 Subject: [PATCH 1/7] TB-61: Improve Alias Filter. Miltiple datasources resolution. --- ui/src/app/api/alias-controller.js | 259 ++++++++++++++++++ ui/src/app/api/entity.service.js | 185 +++++++++---- ui/src/app/api/subscription.js | 129 +++++++-- ui/src/app/common/dashboard-utils.service.js | 76 +++-- ui/src/app/common/types.constant.js | 30 ++ ui/src/app/components/dashboard.directive.js | 6 +- ui/src/app/components/dashboard.tpl.html | 2 +- .../datakey-config-dialog.controller.js | 32 ++- .../components/datasource-entity.directive.js | 11 +- .../app/components/datasource-entity.tpl.html | 2 +- ui/src/app/components/datasource.directive.js | 2 +- ui/src/app/components/datasource.tpl.html | 2 +- .../entity-alias-select.directive.js | 17 +- .../app/components/widget-config.directive.js | 12 +- ui/src/app/components/widget-config.tpl.html | 4 +- ui/src/app/components/widget.controller.js | 156 ++++++++--- ui/src/app/dashboard/add-widget.controller.js | 52 ++-- ui/src/app/dashboard/add-widget.tpl.html | 2 +- ui/src/app/dashboard/dashboard.controller.js | 37 ++- ui/src/app/dashboard/dashboard.tpl.html | 5 +- ui/src/app/dashboard/edit-widget.directive.js | 45 +-- ui/src/app/dashboard/edit-widget.tpl.html | 2 +- .../layouts/dashboard-layout.tpl.html | 2 +- .../aliases-entity-select-panel.controller.js | 25 +- .../aliases-entity-select-panel.tpl.html | 10 +- .../entity/aliases-entity-select.directive.js | 50 ++-- .../app/entity/entity-aliases.controller.js | 38 +-- ui/src/app/entity/entity-aliases.tpl.html | 27 +- .../entity/entity-filter-dialog.controller.js | 104 +++++++ .../app/entity/entity-filter-dialog.tpl.html | 100 +++++++ ui/src/app/entity/entity-filter.directive.js | 81 ++++-- ui/src/app/entity/entity-filter.tpl.html | 37 ++- ui/src/app/entity/entity-list.directive.js | 130 +++++++++ ui/src/app/entity/entity-list.scss | 30 ++ ui/src/app/entity/entity-list.tpl.html | 52 ++++ ui/src/app/entity/index.js | 2 + ui/src/app/locale/locale.constant.js | 19 ++ ui/src/app/services/item-buffer.service.js | 9 +- 38 files changed, 1415 insertions(+), 369 deletions(-) create mode 100644 ui/src/app/api/alias-controller.js create mode 100644 ui/src/app/entity/entity-filter-dialog.controller.js create mode 100644 ui/src/app/entity/entity-filter-dialog.tpl.html create mode 100644 ui/src/app/entity/entity-list.directive.js create mode 100644 ui/src/app/entity/entity-list.scss create mode 100644 ui/src/app/entity/entity-list.tpl.html diff --git a/ui/src/app/api/alias-controller.js b/ui/src/app/api/alias-controller.js new file mode 100644 index 0000000000..779436336f --- /dev/null +++ b/ui/src/app/api/alias-controller.js @@ -0,0 +1,259 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const varsRegex = /\$\{([^\}]*)\}/g; + +export default class AliasController { + + constructor($scope, $q, $filter, utils, types, entityService, stateController, entityAliases) { + this.$scope = $scope; + this.$q = $q; + this.$filter = $filter; + this.utils = utils; + this.types = types; + this.entityService = entityService; + this.stateController = stateController; + this.entityAliases = angular.copy(entityAliases); + this.resolvedAliases = {}; + this.resolvedAliasesPromise = {}; + this.resolvedAliasesToStateEntities = {}; + } + + updateEntityAliases(newEntityAliases) { + var changedAliasIds = []; + for (var aliasId in newEntityAliases) { + var newEntityAlias = newEntityAliases[aliasId]; + var prevEntityAlias = this.entityAliases[aliasId]; + if (!angular.equals(newEntityAlias, prevEntityAlias)) { + changedAliasIds.push(aliasId); + this.setAliasUnresolved(aliasId); + } + } + for (aliasId in this.entityAliases) { + if (!newEntityAliases[aliasId]) { + changedAliasIds.push(aliasId); + this.setAliasUnresolved(aliasId); + } + } + this.entityAliases = angular.copy(newEntityAliases); + if (changedAliasIds.length) { + this.$scope.$broadcast('entityAliasesChanged', changedAliasIds); + } + } + + dashboardStateChanged() { + var newEntityId = this.stateController.getStateParams().entityId; + var changedAliasIds = []; + for (var aliasId in this.resolvedAliasesToStateEntities) { + var prevEntityId = this.resolvedAliasesToStateEntities[aliasId]; + if (!angular.equals(newEntityId, prevEntityId)) { + changedAliasIds.push(aliasId); + this.setAliasUnresolved(aliasId); + } + } + if (changedAliasIds.length) { + this.$scope.$broadcast('entityAliasesChanged', changedAliasIds); + } + } + + setAliasUnresolved(aliasId) { + delete this.resolvedAliases[aliasId]; + delete this.resolvedAliasesPromise[aliasId]; + delete this.resolvedAliasesToStateEntities[aliasId]; + } + + getEntityAliases() { + return this.entityAliases; + } + + getAliasInfo(aliasId) { + var deferred = this.$q.defer(); + var aliasInfo = this.resolvedAliases[aliasId]; + if (aliasInfo) { + deferred.resolve(aliasInfo); + return deferred.promise; + } else if (this.resolvedAliasesPromise[aliasId]) { + return this.resolvedAliasesPromise[aliasId]; + } else { + this.resolvedAliasesPromise[aliasId] = deferred.promise; + var aliasCtrl = this; + var entityAlias = this.entityAliases[aliasId]; + if (entityAlias) { + this.entityService.resolveAlias(entityAlias, this.stateController.getStateParams()).then( + function success(aliasInfo) { + aliasCtrl.resolvedAliases[aliasId] = aliasInfo; + if (entityAlias.filter.stateEntity) { + aliasCtrl.resolvedAliasesToStateEntities[aliasId] = + aliasCtrl.stateController.getStateParams().entityId; + } + deferred.resolve(aliasInfo); + }, + function fail() { + deferred.reject(); + } + ); + } else { + deferred.reject(); + } + return this.resolvedAliasesPromise[aliasId]; + } + } + + resolveDatasource(datasource) { + var deferred = this.$q.defer(); + if (datasource.type === this.types.datasourceType.entity) { + if (datasource.entityAliasId) { + this.getAliasInfo(datasource.entityAliasId).then( + function success(aliasInfo) { + datasource.aliasName = aliasInfo.alias; + if (aliasInfo.resolveMultiple) { + var resolvedEntities = aliasInfo.resolvedEntities; + if (resolvedEntities && resolvedEntities.length) { + var datasources = []; + for (var i=0;i 0 ? true : false; + datasources.push(newDatasource); + } + deferred.resolve(datasources); + } else { + deferred.reject(); + } + } else { + var entity = aliasInfo.currentEntity; + datasource.entityId = entity.id; + datasource.entityType = entity.entityType; + datasource.entityName = entity.name; + datasource.name = entity.name; + deferred.resolve([datasource]); + } + }, + function fail() { + deferred.reject(); + } + ); + } else { // entityId + datasource.aliasName = datasource.entityName; + datasource.name = datasource.entityName; + deferred.resolve([datasource]); + } + } else { // function + deferred.resolve([datasource]); + } + return deferred.promise; + } + + resolveDatasources(datasources) { + + function updateDataKeyLabel(dataKey, datasource) { + if (!dataKey.pattern) { + dataKey.pattern = angular.copy(dataKey.label); + } + var pattern = dataKey.pattern; + var label = dataKey.pattern; + var match = varsRegex.exec(pattern); + while (match !== null) { + var variable = match[0]; + var variableName = match[1]; + if (variableName === 'dsName') { + label = label.split(variable).join(datasource.name); + } else if (variableName === 'entityName') { + label = label.split(variable).join(datasource.entityName); + } else if (variableName === 'deviceName') { + label = label.split(variable).join(datasource.entityName); + } else if (variableName === 'aliasName') { + label = label.split(variable).join(datasource.aliasName); + } + match = varsRegex.exec(pattern); + } + dataKey.label = label; + } + + function updateDatasourceKeyLabels(datasource) { + for (var dk = 0; dk < datasource.dataKeys.length; dk++) { + updateDataKeyLabel(datasource.dataKeys[dk], datasource); + } + } + + var deferred = this.$q.defer(); + var newDatasources = angular.copy(datasources); + var datasorceResolveTasks = []; + var aliasCtrl = this; + newDatasources.forEach(function (datasource) { + var resolveDatasourceTask = aliasCtrl.resolveDatasource(datasource); + datasorceResolveTasks.push(resolveDatasourceTask); + }); + this.$q.all(datasorceResolveTasks).then( + function success(datasourcesArrays) { + var datasources = [].concat.apply([], datasourcesArrays); + datasources = aliasCtrl.$filter('orderBy')(datasources, '+generated'); + var index = 0; + var functionIndex = 0; + datasources.forEach(function(datasource) { + if (datasource.type === aliasCtrl.types.datasourceType.function) { + var name; + if (datasource.name && datasource.name.length) { + name = datasource.name; + } else { + functionIndex++; + name = aliasCtrl.types.datasourceType.function; + if (functionIndex > 1) { + name += ' ' + functionIndex; + } + } + datasource.name = name; + datasource.aliasName = name; + datasource.entityName = name; + } + datasource.dataKeys.forEach(function(dataKey) { + if (datasource.generated) { + dataKey._hash = Math.random(); + dataKey.color = aliasCtrl.utils.getMaterialColor(index); + } + index++; + }); + updateDatasourceKeyLabels(datasource); + }); + deferred.resolve(datasources); + }, + function fail() { + deferred.reject(); + } + ); + return deferred.promise; + } + + getInstantAliasInfo(aliasId) { + return this.resolvedAliases[aliasId]; + } + + updateCurrentAliasEntity(aliasId, currentEntity) { + var aliasInfo = this.resolvedAliases[aliasId]; + if (aliasInfo) { + var prevCurrentEntity = aliasInfo.currentEntity; + if (!angular.equals(currentEntity, prevCurrentEntity)) { + aliasInfo.currentEntity = currentEntity; + this.$scope.$broadcast('entityAliasesChanged', [aliasId]); + } + } + } + +} \ No newline at end of file diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js index 891f9d83b2..8c28a9bfde 100644 --- a/ui/src/app/api/entity.service.js +++ b/ui/src/app/api/entity.service.js @@ -27,10 +27,13 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device getEntity: getEntity, getEntities: getEntities, getEntitiesByNameFilter: getEntitiesByNameFilter, - processEntityAliases: processEntityAliases, + resolveAlias: resolveAlias, + resolveAliasFilter: resolveAliasFilter, + filterAliasByEntityTypes: filterAliasByEntityTypes, + //processEntityAliases: processEntityAliases, getEntityKeys: getEntityKeys, checkEntityAlias: checkEntityAlias, - createDatasoucesFromSubscriptionsInfo: createDatasoucesFromSubscriptionsInfo, + createDatasourcesFromSubscriptionsInfo: createDatasourcesFromSubscriptionsInfo, getRelatedEntities: getRelatedEntities, saveRelatedEntity: saveRelatedEntity, getRelatedEntity: getRelatedEntity, @@ -244,81 +247,151 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device return deferred.promise; } - function entityToEntityInfo(entityType, entity) { - return { name: entity.name, entityType: entityType, id: entity.id.id }; + function entityToEntityInfo(entity) { + return { name: entity.name, entityType: entity.id.entityType, id: entity.id.id }; } - function entitiesToEntitiesInfo(entityType, entities) { + function entitiesToEntitiesInfo(entities) { var entitiesInfo = []; for (var d = 0; d < entities.length; d++) { - entitiesInfo.push(entityToEntityInfo(entityType, entities[d])); + entitiesInfo.push(entityToEntityInfo(entities[d])); } return entitiesInfo; } - function processEntityAlias(index, aliasIds, entityAliases, resolution, deferred) { - if (index < aliasIds.length) { - var aliasId = aliasIds[index]; - var entityAlias = entityAliases[aliasId]; - var alias = entityAlias.alias; - var entityFilter = entityAlias.entityFilter; - if (entityFilter.useFilter) { - var entityNameFilter = entityFilter.entityNameFilter; - getEntitiesByNameFilter(entityAlias.entityType, entityNameFilter, 100).then( - function(entities) { - if (entities && entities != null) { - var resolvedAlias = {alias: alias, entityType: entityAlias.entityType, entityId: entities[0].id.id}; - resolution.aliasesInfo.entityAliases[aliasId] = resolvedAlias; - resolution.aliasesInfo.entityAliasesInfo[aliasId] = entitiesToEntitiesInfo(entityAlias.entityType, entities); - index++; - processEntityAlias(index, aliasIds, entityAliases, resolution, deferred); - } else { - if (!resolution.error) { - resolution.error = 'dashboard.invalid-aliases-config'; + function resolveAliasFilter(filter, stateParams) { + var deferred = $q.defer(); + var result = { + entities: [], + stateEntity: false + }; + switch (filter.type) { + case types.aliasFilterType.entityList.value: + if (filter.stateEntity) { + result.stateEntity = true; + if (stateParams && stateParams.entityId) { + getEntity(stateParams.entityId.entityType, stateParams.entityId.id).then( + function success(entity) { + result.entities = [entity]; + deferred.resolve(result); + }, + function fail() { + deferred.reject(); } - index++; - processEntityAlias(index, aliasIds, entityAliases, resolution, deferred); + ); + } else { + deferred.resolve(result); + } + } else { + getEntities(filter.entityType, filter.entityList).then( + function success(entities) { + if (entities && entities.length) { + result.entities = entities; + deferred.resolve(result); + } else { + deferred.reject(); + } + }, + function fail() { + deferred.reject(); } - }); - } else { - var entityList = entityFilter.entityList; - getEntities(entityAlias.entityType, entityList).then( + ); + } + break; + case types.aliasFilterType.entityName.value: + getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, 100).then( function success(entities) { - if (entities && entities.length > 0) { - var resolvedAlias = {alias: alias, entityType: entityAlias.entityType, entityId: entities[0].id.id}; - resolution.aliasesInfo.entityAliases[aliasId] = resolvedAlias; - resolution.aliasesInfo.entityAliasesInfo[aliasId] = entitiesToEntitiesInfo(entityAlias.entityType, entities); - index++; - processEntityAlias(index, aliasIds, entityAliases, resolution, deferred); + if (entities && entities.length) { + result.entities = entities; + deferred.resolve(result); } else { - if (!resolution.error) { - resolution.error = 'dashboard.invalid-aliases-config'; - } - index++; - processEntityAlias(index, aliasIds, entityAliases, resolution, deferred); + deferred.reject(); } }, function fail() { + deferred.reject(); + } + ); + break; + //TODO: + } + return deferred.promise; + } + + function resolveAlias(entityAlias, stateParams) { + var deferred = $q.defer(); + var filter = entityAlias.filter; + resolveAliasFilter(filter, stateParams).then( + function (result) { + var entities = result.entities; + var aliasInfo = { + alias: entityAlias.alias, + resolveMultiple: filter.resolveMultiple + }; + var resolvedEntities = entitiesToEntitiesInfo(entities); + aliasInfo.resolvedEntities = resolvedEntities; + aliasInfo.currentEntity = null; + if (aliasInfo.resolvedEntities.length) { + aliasInfo.currentEntity = aliasInfo.resolvedEntities[0]; + } + deferred.resolve(aliasInfo); + }, + function fail() { + deferred.reject(); + } + ); + return deferred.promise; + } + + function filterAliasByEntityTypes(entityAlias, entityTypes) { + var filter = entityAlias.filter; + switch (filter.type) { + case types.aliasFilterType.entityList.value: + if (filter.stateEntity) { + return true; + } else { + return entityTypes.indexOf(filter.entityType) > -1 ? true : false; + } + case types.aliasFilterType.entityName.value: + return entityTypes.indexOf(filter.entityType) > -1 ? true : false; + } + //TODO: + return false; + } + + /*function processEntityAlias(index, aliasIds, entityAliases, stateParams, resolution, deferred) { + if (index < aliasIds.length) { + var aliasId = aliasIds[index]; + var entityAlias = entityAliases[aliasId]; + var alias = entityAlias.alias; + var filter = entityAlias.filter; + resolveAliasFilter(filter, stateParams).then( + function (entities) { + if (entities && entities.length) { + var entity = entities[0]; + var resolvedAlias = {alias: alias, entityType: entity.id.entityType, entityId: entity.id.id}; + resolution.aliasesInfo.entityAliases[aliasId] = resolvedAlias; + resolution.aliasesInfo.entityAliasesInfo[aliasId] = entitiesToEntitiesInfo(entities); + index++; + processEntityAlias(index, aliasIds, entityAliases, stateParams, resolution, deferred); + } else { if (!resolution.error) { resolution.error = 'dashboard.invalid-aliases-config'; } index++; - processEntityAlias(index, aliasIds, entityAliases, resolution, deferred); + processEntityAlias(index, aliasIds, entityAliases, stateParams, resolution, deferred); } - ); - } + } + ); } else { deferred.resolve(resolution); } - } + }*/ - function processEntityAliases(entityAliases) { + /*function processEntityAliases(entityAliases, stateParams) { var deferred = $q.defer(); var resolution = { - aliasesInfo: { - entityAliases: {}, - entityAliasesInfo: {} - } + aliasesInfo: {} }; var aliasIds = []; if (entityAliases) { @@ -326,9 +399,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device aliasIds.push(aliasId); } } - processEntityAlias(0, aliasIds, entityAliases, resolution, deferred); + processEntityAlias(0, aliasIds, entityAliases, stateParams, resolution, deferred); return deferred.promise; - } + }*/ function getEntityKeys(entityType, entityId, query, type) { var deferred = $q.defer(); @@ -354,8 +427,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device } } deferred.resolve(result); - }, function fail(response) { - deferred.reject(response.data); + }, function fail() { + deferred.reject(); }); return deferred.promise; } @@ -387,7 +460,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device return deferred.promise; } - function createDatasoucesFromSubscriptionsInfo(subscriptionsInfo) { + function createDatasourcesFromSubscriptionsInfo(subscriptionsInfo) { var deferred = $q.defer(); var datasources = []; processSubscriptionsInfo(0, subscriptionsInfo, datasources, deferred); diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js index fb774a1f0a..471203c3b2 100644 --- a/ui/src/app/api/subscription.js +++ b/ui/src/app/api/subscription.js @@ -39,6 +39,9 @@ export default class Subscription { this.cafs = {}; this.registrations = []; + var subscription = this; + var deferred = this.ctx.$q.defer(); + if (this.type === this.ctx.types.widgetType.rpc.value) { this.callbacks.rpcStateChanged = this.callbacks.rpcStateChanged || function(){}; this.callbacks.onRpcSuccess = this.callbacks.onRpcSuccess || function(){}; @@ -56,7 +59,11 @@ export default class Subscription { this.rpcEnabled = false; this.executingRpcRequest = false; this.executingPromises = []; - this.initRpc(); + this.initRpc().then( + function() { + deferred.resolve(subscription); + } + ); } else { this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || function(){}; this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || function(){}; @@ -103,11 +110,36 @@ export default class Subscription { this.legendConfig.showMax === true || this.legendConfig.showAvg === true || this.legendConfig.showTotal === true); - this.initDataSubscription(); + this.initDataSubscription().then( + function success() { + deferred.resolve(subscription); + }, + function fail() { + deferred.reject(); + } + ); } + + return deferred.promise; } initDataSubscription() { + var deferred = this.ctx.$q.defer(); + var subscription = this; + this.ctx.aliasController.resolveDatasources(this.datasources).then( + function success(datasources) { + subscription.datasources = datasources; + subscription.configureData(); + deferred.resolve(); + }, + function fail() { + deferred.reject(); + } + ); + return deferred.promise; + } + + configureData() { var dataIndex = 0; for (var i = 0; i < this.datasources.length; i++) { var datasource = this.datasources[i]; @@ -199,21 +231,46 @@ export default class Subscription { } initRpc() { + var deferred = this.ctx.$q.defer(); if (this.targetDeviceAliasIds && this.targetDeviceAliasIds.length > 0) { this.targetDeviceAliasId = this.targetDeviceAliasIds[0]; - if (this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId]) { - this.targetDeviceId = this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId].entityId; + var subscription = this; + this.ctx.aliasController.getAliasInfo(this.targetDeviceAliasId).then( + function success(aliasInfo) { + if (aliasInfo.currentEntity && aliasInfo.currentEntity.entityType == subscription.ctx.types.entityType.device) { + subscription.targetDeviceId = aliasInfo.currentEntity.id; + if (subscription.targetDeviceId) { + subscription.rpcEnabled = true; + } else { + subscription.rpcEnabled = subscription.ctx.$scope.widgetEditMode ? true : false; + } + subscription.callbacks.rpcStateChanged(this); + deferred.resolve(); + } else { + subscription.rpcEnabled = false; + subscription.callbacks.rpcStateChanged(this); + deferred.resolve(); + } + }, + function fail () { + subscription.rpcEnabled = false; + subscription.callbacks.rpcStateChanged(this); + deferred.resolve(); + } + ); + } else { + if (this.targetDeviceIds && this.targetDeviceIds.length > 0) { + this.targetDeviceId = this.targetDeviceIds[0]; } - } else if (this.targetDeviceIds && this.targetDeviceIds.length > 0) { - this.targetDeviceId = this.targetDeviceIds[0]; - } - - if (this.targetDeviceId) { - this.rpcEnabled = true; - } else { - this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false; + if (this.targetDeviceId) { + this.rpcEnabled = true; + } else { + this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false; + } + this.callbacks.rpcStateChanged(this); + deferred.resolve(); } - this.callbacks.rpcStateChanged(this); + return deferred.promise; } clearRpcError() { @@ -319,11 +376,11 @@ export default class Subscription { this.onDataUpdated(); } - onAliasesChanged() { + onAliasesChanged(aliasIds) { if (this.type === this.ctx.types.widgetType.rpc.value) { - this.checkRpcTarget(); + return this.checkRpcTarget(aliasIds); } else { - this.checkSubscriptions(); + return this.checkSubscriptions(aliasIds); } } @@ -481,7 +538,7 @@ export default class Subscription { var datasource = this.datasources[i]; if (angular.isFunction(datasource)) continue; - var entityId = null; + /* var entityId = null; var entityType = null; if (datasource.type === this.ctx.types.datasourceType.entity) { var aliasName = null; @@ -513,7 +570,7 @@ export default class Subscription { } for (var dk = 0; dk < datasource.dataKeys.length; dk++) { updateDataKeyLabel(datasource.dataKeys[dk], datasource.name, entityName, aliasName); - } + }*/ var subscription = this; @@ -521,8 +578,8 @@ export default class Subscription { subscriptionType: this.type, subscriptionTimewindow: this.subscriptionTimewindow, datasource: datasource, - entityType: entityType, - entityId: entityId, + entityType: datasource.entityType, + entityId: datasource.entityId, dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) { subscription.dataUpdated(data, datasourceIndex, dataKeyIndex, apply); }, @@ -557,8 +614,13 @@ export default class Subscription { } } - checkRpcTarget() { - var deviceId = null; + checkRpcTarget(aliasIds) { + if (aliasIds.indexOf(this.targetDeviceAliasId) > -1) { + return true; + } else { + return false; + } + /*var deviceId = null; if (this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId]) { deviceId = this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId].entityId; } @@ -570,14 +632,20 @@ export default class Subscription { this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false; } this.callbacks.rpcStateChanged(this); - } + }*/ } - checkSubscriptions() { + checkSubscriptions(aliasIds) { var subscriptionsChanged = false; for (var i = 0; i < this.datasourceListeners.length; i++) { var listener = this.datasourceListeners[i]; - var entityId = null; + if (listener.datasource.entityAliasId) { + if (aliasIds.indexOf(listener.datasource.entityAliasId) > -1) { + subscriptionsChanged = true; + break; + } + } + /*var entityId = null; var entityType = null; var aliasName = null; if (listener.datasource.type === this.ctx.types.datasourceType.entity) { @@ -593,12 +661,13 @@ export default class Subscription { subscriptionsChanged = true; break; } - } + }*/ } - if (subscriptionsChanged) { + return subscriptionsChanged; + /*if (subscriptionsChanged) { this.unsubscribe(); this.subscribe(); - } + }*/ } destroy() { @@ -617,7 +686,7 @@ export default class Subscription { } -const varsRegex = /\$\{([^\}]*)\}/g; +/*const varsRegex = /\$\{([^\}]*)\}/g; function updateDataKeyLabel(dataKey, dsName, entityName, aliasName) { var pattern = dataKey.pattern; @@ -638,7 +707,7 @@ function updateDataKeyLabel(dataKey, dsName, entityName, aliasName) { match = varsRegex.exec(pattern); } dataKey.label = label; -} +}*/ function calculateMin(data) { if (data.length > 0) { diff --git a/ui/src/app/common/dashboard-utils.service.js b/ui/src/app/common/dashboard-utils.service.js index 78136dfb2e..51a0215dd2 100644 --- a/ui/src/app/common/dashboard-utils.service.js +++ b/ui/src/app/common/dashboard-utils.service.js @@ -40,39 +40,75 @@ function DashboardUtils(types, utils, timeService) { return service; function validateAndUpdateEntityAliases(configuration) { + var aliasId, entityAlias; if (angular.isUndefined(configuration.entityAliases)) { configuration.entityAliases = {}; if (configuration.deviceAliases) { var deviceAliases = configuration.deviceAliases; - for (var aliasId in deviceAliases) { + for (aliasId in deviceAliases) { var deviceAlias = deviceAliases[aliasId]; - var alias = deviceAlias.alias; - var entityFilter = { - useFilter: false, - entityNameFilter: '', - entityList: [] - } - if (deviceAlias.deviceFilter) { - entityFilter.useFilter = deviceAlias.deviceFilter.useFilter; - entityFilter.entityNameFilter = deviceAlias.deviceFilter.deviceNameFilter; - entityFilter.entityList = deviceAlias.deviceFilter.deviceList; - } else if (deviceAlias.deviceId) { - entityFilter.entityList = [deviceAlias.deviceId]; - } - var entityAlias = { - id: aliasId, - alias: alias, - entityType: types.entityType.device, - entityFilter: entityFilter - }; + entityAlias = validateAndUpdateDeviceAlias(aliasId, deviceAlias); configuration.entityAliases[aliasId] = entityAlias; } delete configuration.deviceAliases; } + } else { + var entityAliases = configuration.entityAliases; + for (aliasId in entityAliases) { + entityAlias = entityAliases[aliasId]; + entityAliases[aliasId] = validateAndUpdateEntityAlias(entityAlias); + } } return configuration; } + function validateAndUpdateDeviceAlias(aliasId, deviceAlias) { + var alias = deviceAlias.alias; + var entityAlias = { + id: aliasId, + alias: alias, + filter: { + type: null, + entityType: types.entityType.device, + resolveMultiple: false + }, + } + if (deviceAlias.deviceFilter) { + entityAlias.filter.type = + deviceAlias.deviceFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value; + if (entityAlias.filter.type == types.aliasFilterType.entityList.value) { + entityAlias.filter.entityList = deviceAlias.deviceFilter.deviceList; + entityAlias.filter.stateEntity = false; + } else { + entityAlias.filter.entityNameFilter = deviceAlias.deviceFilter.deviceNameFilter; + } + } else { + entityAlias.filter.type = types.aliasFilterType.entityList.value; + entityAlias.filter.entityList = [deviceAlias.deviceId]; + entityAlias.filter.stateEntity = false; + } + return entityAlias; + } + + function validateAndUpdateEntityAlias(entityAlias) { + if (!entityAlias.filter) { + entityAlias.filter = { + type: entityAlias.entityFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value, + entityType: entityAlias.entityType, + resolveMultiple: false + } + if (entityAlias.filter.type == types.aliasFilterType.entityList.value) { + entityAlias.filter.entityList = entityAlias.entityFilter.entityList; + entityAlias.filter.stateEntity = false; + } else { + entityAlias.filter.entityNameFilter = entityAlias.entityFilter.entityNameFilter; + } + delete entityAlias.entityType; + delete entityAlias.entityFilter; + } + return entityAlias; + } + function validateAndUpdateWidget(widget) { if (!widget.config) { widget.config = {}; diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 44e59a260d..40b66391fd 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -65,6 +65,36 @@ export default angular.module('thingsboard.types', []) clearedUnack: "CLEARED_UNACK", clearedAck: "CLEARED_ACK" }, + aliasFilterType: { + entityList: { + value: 'entityList', + name: 'alias.filter-type-entity-list' + }, + entityName: { + value: 'entityName', + name: 'alias.filter-type-entity-name' + }, + assetType: { + value: 'assetType', + name: 'alias.filter-type-asset-type' + }, + deviceType: { + value: 'deviceType', + name: 'alias.filter-type-device-type' + }, + relationsQuery: { + value: 'relationsQuery', + name: 'alias.filter-type-relations-query' + }, + assetSearchQuery: { + value: 'assetSearchQuery', + name: 'alias.filter-type-asset-search-query' + }, + deviceSearchQuery: { + value: 'deviceSearchQuery', + name: 'alias.filter-type-device-search-query' + } + }, position: { top: { value: "top", diff --git a/ui/src/app/components/dashboard.directive.js b/ui/src/app/components/dashboard.directive.js index f26121ccea..965b83854f 100644 --- a/ui/src/app/components/dashboard.directive.js +++ b/ui/src/app/components/dashboard.directive.js @@ -52,7 +52,7 @@ function Dashboard() { bindToController: { widgets: '=', widgetLayouts: '=?', - aliasesInfo: '=', + aliasController: '=', stateController: '=', dashboardTimewindow: '=?', columns: '=', @@ -329,10 +329,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t $scope.$broadcast('toggleDashboardEditMode', vm.isEdit); }); - $scope.$watch('vm.aliasesInfo.entityAliases', function () { - $scope.$broadcast('entityAliasListChanged', vm.aliasesInfo); - }, true); - $scope.$on('gridster-resized', function (event, sizes, theGridster) { if (checkIsLocalGridsterElement(theGridster)) { vm.gridster = theGridster; diff --git a/ui/src/app/components/dashboard.tpl.html b/ui/src/app/components/dashboard.tpl.html index 69934ac215..8d77a6fcca 100644 --- a/ui/src/app/components/dashboard.tpl.html +++ b/ui/src/app/components/dashboard.tpl.html @@ -89,7 +89,7 @@
diff --git a/ui/src/app/components/datasource.directive.js b/ui/src/app/components/datasource.directive.js index 2c06a381ee..eb9eafc4a4 100644 --- a/ui/src/app/components/datasource.directive.js +++ b/ui/src/app/components/datasource.directive.js @@ -76,7 +76,7 @@ function Datasource($compile, $templateCache, types) { restrict: "E", require: "^ngModel", scope: { - entityAliases: '=', + aliasController: '=', widgetType: '=', functionsOnly: '=', datakeySettingsSchema: '=', diff --git a/ui/src/app/components/datasource.tpl.html b/ui/src/app/components/datasource.tpl.html index 88ff247fa0..0e91d9832e 100644 --- a/ui/src/app/components/datasource.tpl.html +++ b/ui/src/app/components/datasource.tpl.html @@ -37,7 +37,7 @@ ng-switch-when="entity" ng-required="model.type === types.datasourceType.entity" widget-type="widgetType" - entity-aliases="entityAliases" + alias-controller="aliasController" generate-data-key="generateDataKey({chip: chip, type: type})" fetch-entity-keys="fetchEntityKeys({entityAliasId: entityAliasId, query: query, type: type})" on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})"> diff --git a/ui/src/app/components/entity-alias-select.directive.js b/ui/src/app/components/entity-alias-select.directive.js index 204b83fe6a..ca2db855f0 100644 --- a/ui/src/app/components/entity-alias-select.directive.js +++ b/ui/src/app/components/entity-alias-select.directive.js @@ -31,7 +31,7 @@ export default angular.module('thingsboard.directives.entityAliasSelect', []) .name; /*@ngInject*/ -function EntityAliasSelect($compile, $templateCache, $mdConstant) { +function EntityAliasSelect($compile, $templateCache, $mdConstant, entityService) { var linker = function (scope, element, attrs, ngModelCtrl) { var template = $templateCache.get(entityAliasSelectTemplate); @@ -49,19 +49,18 @@ function EntityAliasSelect($compile, $templateCache, $mdConstant) { ngModelCtrl.$setValidity('entityAlias', valid); }; - scope.$watch('entityAliases', function () { + scope.$watch('aliasController', function () { scope.entityAliasList = []; - for (var aliasId in scope.entityAliases) { + var entityAliases = scope.aliasController.getEntityAliases(); + for (var aliasId in entityAliases) { if (scope.allowedEntityTypes) { - if (scope.allowedEntityTypes.indexOf(scope.entityAliases[aliasId].entityType) === -1) { + if (!entityService.filterAliasByEntityTypes(entityAliases[aliasId], scope.allowedEntityTypes)) { continue; } } - var entityAlias = {id: aliasId, alias: scope.entityAliases[aliasId].alias, - entityType: scope.entityAliases[aliasId].entityType, entityId: scope.entityAliases[aliasId].entityId}; - scope.entityAliasList.push(entityAlias); + scope.entityAliasList.push(entityAliases[aliasId]); } - }, true); + }); scope.$watch('entityAlias', function () { scope.updateView(); @@ -141,7 +140,7 @@ function EntityAliasSelect($compile, $templateCache, $mdConstant) { link: linker, scope: { tbRequired: '=?', - entityAliases: '=', + aliasController: '=', allowedEntityTypes: '=?', onCreateEntityAlias: '&' } diff --git a/ui/src/app/components/widget-config.directive.js b/ui/src/app/components/widget-config.directive.js index c3793be294..d5b423987f 100644 --- a/ui/src/app/components/widget-config.directive.js +++ b/ui/src/app/components/widget-config.directive.js @@ -128,13 +128,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti } else if (scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) { if (config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0) { var aliasId = config.targetDeviceAliasIds[0]; - if (scope.entityAliases[aliasId]) { - scope.targetDeviceAlias.value = { - id: aliasId, - alias: scope.entityAliases[aliasId].alias, - entityType: scope.entityAliases[aliasId].entityType, - entityId: scope.entityAliases[aliasId].entityId - }; + var entityAliases = scope.aliasController.getEntityAliases(); + if (entityAliases[aliasId]) { + scope.targetDeviceAlias.value = entityAliases[aliasId]; } else { scope.targetDeviceAlias.value = null; } @@ -395,7 +391,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti widgetType: '=', widgetSettingsSchema: '=', datakeySettingsSchema: '=', - entityAliases: '=', + aliasController: '=', functionsOnly: '=', fetchEntityKeys: '&', onCreateEntityAlias: '&', diff --git a/ui/src/app/components/widget-config.tpl.html b/ui/src/app/components/widget-config.tpl.html index 808e07b487..0500e91949 100644 --- a/ui/src/app/components/widget-config.tpl.html +++ b/ui/src/app/components/widget-config.tpl.html @@ -60,7 +60,7 @@ style="padding: 0 0 0 10px; margin: 5px;"> diff --git a/ui/src/app/components/widget.controller.js b/ui/src/app/components/widget.controller.js index 6fc4bba646..103ad138e4 100644 --- a/ui/src/app/components/widget.controller.js +++ b/ui/src/app/components/widget.controller.js @@ -22,7 +22,7 @@ import Subscription from '../api/subscription'; /*@ngInject*/ export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, tbRaf, types, utils, timeService, datasourceService, entityService, deviceService, visibleRect, isEdit, stDiff, dashboardTimewindow, - dashboardTimewindowApi, widget, aliasesInfo, stateController, widgetType) { + dashboardTimewindowApi, widget, aliasController, stateController, widgetType) { var vm = this; @@ -37,6 +37,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q $scope.executingRpcRequest = false; var gridsterItemInited = false; + var subscriptionInited = false; var cafs = {}; @@ -149,7 +150,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q dashboardTimewindowApi: dashboardTimewindowApi, types: types, stDiff: stDiff, - aliasesInfo: aliasesInfo + aliasController: aliasController }; var widgetTypeInstance; @@ -203,8 +204,13 @@ export default function WidgetController($scope, $timeout, $window, $element, $q vm.gridsterItemInitialized = gridsterItemInitialized; - initialize(); - + initialize().then( + function(){ + if (checkSize()) { + onInit(); + } + } + ); /* options = { @@ -233,28 +239,42 @@ export default function WidgetController($scope, $timeout, $window, $element, $q } } - entityService.createDatasoucesFromSubscriptionsInfo(subscriptionsInfo).then( + entityService.createDatasourcesFromSubscriptionsInfo(subscriptionsInfo).then( function (datasources) { options.datasources = datasources; - var subscription = createSubscription(options, subscribe); - if (useDefaultComponents) { - defaultSubscriptionOptions(subscription, options); - } - deferred.resolve(subscription); + createSubscription(options, subscribe).then( + function success(subscription) { + if (useDefaultComponents) { + defaultSubscriptionOptions(subscription, options); + } + deferred.resolve(subscription); + }, + function fail() { + deferred.reject(); + } + ); } ); return deferred.promise; } function createSubscription(options, subscribe) { + var deferred = $q.defer(); options.dashboardTimewindow = dashboardTimewindow; - var subscription = - new Subscription(subscriptionContext, options); - widgetContext.subscriptions[subscription.id] = subscription; - if (subscribe) { - subscription.subscribe(); - } - return subscription; + new Subscription(subscriptionContext, options).then( + function success(subscription) { + widgetContext.subscriptions[subscription.id] = subscription; + if (subscribe) { + subscription.subscribe(); + } + deferred.resolve(subscription); + }, + function fail() { + deferred.reject(); + } + ); + + return deferred.promise; } function defaultComponentsOptions(options) { @@ -310,8 +330,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q } function createDefaultSubscription() { - var subscription; var options; + var deferred = $q.defer(); if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) { options = { type: widget.type, @@ -319,16 +339,23 @@ export default function WidgetController($scope, $timeout, $window, $element, $q }; defaultComponentsOptions(options); - subscription = createSubscription(options); - - defaultSubscriptionOptions(subscription, options); + createSubscription(options).then( + function success(subscription) { + defaultSubscriptionOptions(subscription, options); - // backward compatibility + // backward compatibility - widgetContext.datasources = subscription.datasources; - widgetContext.data = subscription.data; - widgetContext.hiddenData = subscription.hiddenData; - widgetContext.timeWindow = subscription.timeWindow; + widgetContext.datasources = subscription.datasources; + widgetContext.data = subscription.data; + widgetContext.hiddenData = subscription.hiddenData; + widgetContext.timeWindow = subscription.timeWindow; + widgetContext.defaultSubscription = subscription; + deferred.resolve(); + }, + function fail() { + deferred.reject(); + } + ); } else if (widget.type === types.widgetType.rpc.value) { $scope.loadingData = false; @@ -356,24 +383,27 @@ export default function WidgetController($scope, $timeout, $window, $element, $q $scope.rpcRejection = null; } } - subscription = createSubscription(options); + createSubscription(options).then( + function success(subscription) { + widgetContext.defaultSubscription = subscription; + deferred.resolve(); + }, + function fail() { + deferred.reject(); + } + ); } else if (widget.type === types.widgetType.static.value) { $scope.loadingData = false; + deferred.resolve(); + } else { + deferred.resolve(); } - if (subscription) { - widgetContext.defaultSubscription = subscription; - } + return deferred.promise; } function initialize() { - if (!vm.useCustomDatasources) { - createDefaultSubscription(); - } else { - $scope.loadingData = false; - } - $scope.$on('toggleDashboardEditMode', function (event, isEdit) { onEditModeChanged(isEdit); }); @@ -398,11 +428,14 @@ export default function WidgetController($scope, $timeout, $window, $element, $q onMobileModeChanged(newIsMobile); }); - $scope.$on('entityAliasListChanged', function (event, aliasesInfo) { - subscriptionContext.aliasesInfo = aliasesInfo; + $scope.$on('entityAliasesChanged', function (event, aliasIds) { + var subscriptionChanged = false; for (var id in widgetContext.subscriptions) { var subscription = widgetContext.subscriptions[id]; - subscription.onAliasesChanged(); + subscriptionChanged = subscriptionChanged || subscription.onAliasesChanged(aliasIds); + } + if (subscriptionChanged && !vm.useCustomDatasources) { + reInit(); } }); @@ -410,6 +443,44 @@ export default function WidgetController($scope, $timeout, $window, $element, $q removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef onDestroy(); }); + + var deferred = $q.defer(); + if (!vm.useCustomDatasources) { + createDefaultSubscription().then( + function success() { + subscriptionInited = true; + deferred.resolve(); + }, + function fail() { + subscriptionInited = true; + deferred.reject(); + } + ); + } else { + $scope.loadingData = false; + subscriptionInited = true; + deferred.resolve(); + } + return deferred.promise; + } + + function reInit() { + onDestroy(); + if (!vm.useCustomDatasources) { + createDefaultSubscription().then( + function success() { + subscriptionInited = true; + onInit(); + }, + function fail() { + subscriptionInited = true; + onInit(); + } + ); + } else { + subscriptionInited = true; + onInit(); + } } function handleWidgetException(e) { @@ -418,7 +489,9 @@ export default function WidgetController($scope, $timeout, $window, $element, $q } function onInit() { - if (!widgetContext.inited) { + if (!widgetContext.inited && + subscriptionInited && + gridsterItemInited) { widgetContext.inited = true; try { widgetTypeInstance.onInit(); @@ -462,7 +535,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q handleWidgetException(e); } }); - } else if (gridsterItemInited) { + } else { onInit(); } } @@ -544,6 +617,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q var subscription = widgetContext.subscriptions[id]; subscription.destroy(); } + subscriptionInited = false; widgetContext.subscriptions = []; if (widgetContext.inited) { widgetContext.inited = false; diff --git a/ui/src/app/dashboard/add-widget.controller.js b/ui/src/app/dashboard/add-widget.controller.js index 2f23cf9f38..586fb46fc4 100644 --- a/ui/src/app/dashboard/add-widget.controller.js +++ b/ui/src/app/dashboard/add-widget.controller.js @@ -20,12 +20,13 @@ import entityAliasesTemplate from '../entity/entity-aliases.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ /*@ngInject*/ -export default function AddWidgetController($scope, widgetService, entityService, $mdDialog, $q, $document, types, dashboard, aliasesInfo, widget, widgetInfo) { +export default function AddWidgetController($scope, widgetService, entityService, $mdDialog, $q, $document, types, dashboard, + aliasController, widget, widgetInfo) { var vm = this; vm.dashboard = dashboard; - vm.aliasesInfo = aliasesInfo; + vm.aliasController = aliasController; vm.widget = widget; vm.widgetInfo = widgetInfo; @@ -85,7 +86,7 @@ export default function AddWidgetController($scope, widgetService, entityService } function cancel () { - $mdDialog.cancel({aliasesInfo: vm.aliasesInfo}); + $mdDialog.cancel(); } function add () { @@ -94,23 +95,39 @@ export default function AddWidgetController($scope, widgetService, entityService vm.widget.config = vm.widgetConfig.config; vm.widget.config.mobileOrder = vm.widgetConfig.layout.mobileOrder; vm.widget.config.mobileHeight = vm.widgetConfig.layout.mobileHeight; - $mdDialog.hide({widget: vm.widget, aliasesInfo: vm.aliasesInfo}); + $mdDialog.hide({widget: vm.widget}); } } function fetchEntityKeys (entityAliasId, query, type) { - var entityAlias = vm.aliasesInfo.entityAliases[entityAliasId]; - if (entityAlias && entityAlias.entityId) { - return entityService.getEntityKeys(entityAlias.entityType, entityAlias.entityId, query, type); - } else { - return $q.when([]); - } + var deferred = $q.defer(); + vm.aliasController.getAliasInfo(entityAliasId).then( + function success(aliasInfo) { + var entity = aliasInfo.currentEntity; + if (entity) { + entityService.getEntityKeys(entity.entityType, entity.id, query, type).then( + function success(keys) { + deferred.resolve(keys); + }, + function fail() { + deferred.resolve([]); + } + ); + } else { + deferred.resolve([]); + } + }, + function fail() { + deferred.resolve([]); + } + ); + return deferred.promise; } function createEntityAlias (event, alias, allowedEntityTypes) { var deferred = $q.defer(); - var singleEntityAlias = {id: null, alias: alias, entityType: types.entityType.device, entityFilter: null}; + var singleEntityAlias = {id: null, alias: alias, filter: {}}; $mdDialog.show({ controller: 'EntityAliasesController', @@ -130,16 +147,9 @@ export default function AddWidgetController($scope, widgetService, entityService skipHide: true, targetEvent: event }).then(function (singleEntityAlias) { - vm.dashboard.configuration.entityAliases[singleEntityAlias.id] = - { alias: singleEntityAlias.alias, entityType: singleEntityAlias.entityType, entityFilter: singleEntityAlias.entityFilter }; - entityService.processEntityAliases(vm.dashboard.configuration.entityAliases).then( - function(resolution) { - if (!resolution.error) { - vm.aliasesInfo = resolution.aliasesInfo; - } - deferred.resolve(singleEntityAlias); - } - ); + vm.dashboard.configuration.entityAliases[singleEntityAlias.id] = singleEntityAlias; + vm.aliasController.updateEntityAliases(vm.dashboard.configuration.entityAliases); + deferred.resolve(singleEntityAlias); }, function () { deferred.reject(); }); diff --git a/ui/src/app/dashboard/add-widget.tpl.html b/ui/src/app/dashboard/add-widget.tpl.html index 870e20346a..761776057b 100644 --- a/ui/src/app/dashboard/add-widget.tpl.html +++ b/ui/src/app/dashboard/add-widget.tpl.html @@ -37,7 +37,7 @@ ng-model="vm.widgetConfig" widget-settings-schema="vm.settingsSchema" datakey-settings-schema="vm.dataKeySettingsSchema" - entity-aliases="vm.aliasesInfo.entityAliases" + alias-controller="vm.aliasController" functions-only="vm.functionsOnly" fetch-entity-keys="vm.fetchEntityKeys(entityAliasId, query, type)" on-create-entity-alias="vm.createEntityAlias(event, alias, allowedEntityTypes)" diff --git a/ui/src/app/dashboard/dashboard.controller.js b/ui/src/app/dashboard/dashboard.controller.js index 31892a6ce4..195184b4ff 100644 --- a/ui/src/app/dashboard/dashboard.controller.js +++ b/ui/src/app/dashboard/dashboard.controller.js @@ -24,8 +24,10 @@ import selectTargetLayoutTemplate from './layouts/select-target-layout.tpl.html' /* eslint-enable import/no-unresolved, import/default */ +import AliasController from '../api/alias-controller'; + /*@ngInject*/ -export default function DashboardController(types, dashboardUtils, widgetService, userService, +export default function DashboardController(types, utils, dashboardUtils, widgetService, userService, dashboardService, timeService, entityService, itembuffer, importExport, hotkeys, $window, $rootScope, $scope, $element, $state, $stateParams, $mdDialog, $mdMedia, $timeout, $document, $q, $translate, $filter) { @@ -349,7 +351,13 @@ export default function DashboardController(types, dashboardUtils, widgetService dashboardService.getDashboard($stateParams.dashboardId) .then(function success(dashboard) { vm.dashboard = dashboardUtils.validateAndUpdateDashboard(dashboard); - entityService.processEntityAliases(vm.dashboard.configuration.entityAliases) + vm.dashboardConfiguration = vm.dashboard.configuration; + vm.dashboardCtx.dashboard = vm.dashboard; + vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow; + vm.dashboardCtx.aliasController = new AliasController($scope, $q, $filter, utils, + types, entityService, vm.dashboardCtx.stateController, vm.dashboardConfiguration.entityAliases); + + /* entityService.processEntityAliases(vm.dashboard.configuration.entityAliases) .then( function(resolution) { if (resolution.error && !isTenantAdmin()) { @@ -362,7 +370,7 @@ export default function DashboardController(types, dashboardUtils, widgetService vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow; } } - ); + );*/ }, function fail() { vm.configurationError = true; }); @@ -373,6 +381,7 @@ export default function DashboardController(types, dashboardUtils, widgetService var layoutsData = dashboardUtils.getStateLayoutsData(vm.dashboard, state); if (layoutsData) { vm.dashboardCtx.state = state; + vm.dashboardCtx.aliasController.dashboardStateChanged(); var layoutVisibilityChanged = false; for (var l in vm.layouts) { var layout = vm.layouts[l]; @@ -916,7 +925,7 @@ export default function DashboardController(types, dashboardUtils, widgetService templateUrl: addWidgetTemplate, locals: { dashboard: vm.dashboard, - aliasesInfo: vm.dashboardCtx.aliasesInfo, + aliasController: vm.dashboardCtx.aliasController, widget: newWidget, widgetInfo: widgetTypeInfo }, @@ -930,10 +939,8 @@ export default function DashboardController(types, dashboardUtils, widgetService } }).then(function (result) { var widget = result.widget; - vm.dashboardCtx.aliasesInfo = result.aliasesInfo; addWidget(widget); - }, function (rejection) { - vm.dashboardCtx.aliasesInfo = rejection.aliasesInfo; + }, function () { }); } } @@ -1025,7 +1032,7 @@ export default function DashboardController(types, dashboardUtils, widgetService notifyDashboardUpdated(); } - function showAliasesResolutionError(error) { +/* function showAliasesResolutionError(error) { var alert = $mdDialog.alert() .parent(angular.element($document[0].body)) .clickOutsideToClose(true) @@ -1037,20 +1044,10 @@ export default function DashboardController(types, dashboardUtils, widgetService alert._options.fullscreen = true; $mdDialog.show(alert); - } + }*/ function entityAliasesUpdated() { - var deferred = $q.defer(); - entityService.processEntityAliases(vm.dashboard.configuration.entityAliases) - .then( - function(resolution) { - if (resolution.aliasesInfo) { - vm.dashboardCtx.aliasesInfo = resolution.aliasesInfo; - } - deferred.resolve(); - } - ); - return deferred.promise; + vm.dashboardCtx.aliasController.updateEntityAliases(vm.dashboard.configuration.entityAliases); } function notifyDashboardUpdated() { diff --git a/ui/src/app/dashboard/dashboard.tpl.html b/ui/src/app/dashboard/dashboard.tpl.html index 8338612484..202268fa5e 100644 --- a/ui/src/app/dashboard/dashboard.tpl.html +++ b/ui/src/app/dashboard/dashboard.tpl.html @@ -57,8 +57,7 @@ + alias-controller="vm.dashboardCtx.aliasController"> @@ -179,7 +178,7 @@
diff --git a/ui/src/app/dashboard/edit-widget.directive.js b/ui/src/app/dashboard/edit-widget.directive.js index 7cba4ee6c5..4bd4e3dfff 100644 --- a/ui/src/app/dashboard/edit-widget.directive.js +++ b/ui/src/app/dashboard/edit-widget.directive.js @@ -68,18 +68,34 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid }); scope.fetchEntityKeys = function (entityAliasId, query, type) { - var entityAlias = scope.aliasesInfo.entityAliases[entityAliasId]; - if (entityAlias && entityAlias.entityId) { - return entityService.getEntityKeys(entityAlias.entityType, entityAlias.entityId, query, type); - } else { - return $q.when([]); - } + var deferred = $q.defer(); + scope.aliasController.getAliasInfo(entityAliasId).then( + function success(aliasInfo) { + var entity = aliasInfo.currentEntity; + if (entity) { + entityService.getEntityKeys(entity.entityType, entity.id, query, type).then( + function success(keys) { + deferred.resolve(keys); + }, + function fail() { + deferred.resolve([]); + } + ); + } else { + deferred.resolve([]); + } + }, + function fail() { + deferred.resolve([]); + } + ); + return deferred.promise; }; scope.createEntityAlias = function (event, alias, allowedEntityTypes) { var deferred = $q.defer(); - var singleEntityAlias = {id: null, alias: alias, entityType: types.entityType.device, entityFilter: null}; + var singleEntityAlias = {id: null, alias: alias, filter: {}}; $mdDialog.show({ controller: 'EntityAliasesController', @@ -99,16 +115,9 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid skipHide: true, targetEvent: event }).then(function (singleEntityAlias) { - scope.dashboard.configuration.entityAliases[singleEntityAlias.id] = - { alias: singleEntityAlias.alias, entityType: singleEntityAlias.entityType, entityFilter: singleEntityAlias.entityFilter }; - entityService.processEntityAliases(scope.dashboard.configuration.entityAliases).then( - function(resolution) { - if (!resolution.error) { - scope.aliasesInfo = resolution.aliasesInfo; - } - deferred.resolve(singleEntityAlias); - } - ); + scope.dashboard.configuration.entityAliases[singleEntityAlias.id] = singleEntityAlias; + scope.aliasController.updateEntityAliases(scope.dashboard.configuration.entityAliases); + deferred.resolve(singleEntityAlias); }, function () { deferred.reject(); }); @@ -124,7 +133,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid link: linker, scope: { dashboard: '=', - aliasesInfo: '=', + aliasController: '=', widget: '=', widgetLayout: '=', theForm: '=' diff --git a/ui/src/app/dashboard/edit-widget.tpl.html b/ui/src/app/dashboard/edit-widget.tpl.html index 279d3113a8..a9ff0b6d4d 100644 --- a/ui/src/app/dashboard/edit-widget.tpl.html +++ b/ui/src/app/dashboard/edit-widget.tpl.html @@ -21,7 +21,7 @@ is-data-enabled="isDataEnabled" widget-settings-schema="settingsSchema" datakey-settings-schema="dataKeySettingsSchema" - entity-aliases="aliasesInfo.entityAliases" + alias-controller="aliasController" functions-only="functionsOnly" fetch-entity-keys="fetchEntityKeys(entityAliasId, query, type)" on-create-entity-alias="createEntityAlias(event, alias, allowedEntityTypes)" diff --git a/ui/src/app/dashboard/layouts/dashboard-layout.tpl.html b/ui/src/app/dashboard/layouts/dashboard-layout.tpl.html index ea848585bf..4a4b25ea30 100644 --- a/ui/src/app/dashboard/layouts/dashboard-layout.tpl.html +++ b/ui/src/app/dashboard/layouts/dashboard-layout.tpl.html @@ -45,7 +45,7 @@ widget-layouts="vm.layoutCtx.widgetLayouts" columns="vm.layoutCtx.gridSettings.columns" margins="vm.layoutCtx.gridSettings.margins" - aliases-info="vm.dashboardCtx.aliasesInfo" + alias-controller="vm.dashboardCtx.aliasController" state-controller="vm.dashboardCtx.stateController" dashboard-timewindow="vm.dashboardCtx.dashboardTimewindow" is-edit="vm.isEdit" diff --git a/ui/src/app/entity/aliases-entity-select-panel.controller.js b/ui/src/app/entity/aliases-entity-select-panel.controller.js index 4128f948d5..8439269c1e 100644 --- a/ui/src/app/entity/aliases-entity-select-panel.controller.js +++ b/ui/src/app/entity/aliases-entity-select-panel.controller.js @@ -15,17 +15,30 @@ */ /*@ngInject*/ -export default function AliasesEntitySelectPanelController(mdPanelRef, $scope, types, entityAliases, entityAliasesInfo, onEntityAliasesUpdate) { +export default function AliasesEntitySelectPanelController(mdPanelRef, $scope, types, aliasController, onEntityAliasesUpdate) { var vm = this; vm._mdPanelRef = mdPanelRef; - vm.entityAliases = entityAliases; - vm.entityAliasesInfo = entityAliasesInfo; + vm.aliasController = aliasController; vm.onEntityAliasesUpdate = onEntityAliasesUpdate; + vm.entityAliases = {}; + vm.entityAliasesInfo = {}; - $scope.$watch('vm.entityAliases', function () { + vm.currentAliasEntityChanged = currentAliasEntityChanged; + + var allEntityAliases = vm.aliasController.getEntityAliases(); + for (var aliasId in allEntityAliases) { + var aliasInfo = vm.aliasController.getInstantAliasInfo(aliasId); + if (aliasInfo && !aliasInfo.resolveMultiple && aliasInfo.currentEntity) { + vm.entityAliasesInfo[aliasId] = angular.copy(aliasInfo); + } + } + + function currentAliasEntityChanged(aliasId, currentEntity) { + vm.aliasController.updateCurrentAliasEntity(aliasId, currentEntity); if (onEntityAliasesUpdate) { - onEntityAliasesUpdate(vm.entityAliases); + onEntityAliasesUpdate(); } - }, true); + } + } diff --git a/ui/src/app/entity/aliases-entity-select-panel.tpl.html b/ui/src/app/entity/aliases-entity-select-panel.tpl.html index d9c5f2ff11..edc8e5ade9 100644 --- a/ui/src/app/entity/aliases-entity-select-panel.tpl.html +++ b/ui/src/app/entity/aliases-entity-select-panel.tpl.html @@ -18,12 +18,12 @@
-
+
- - - - {{entityInfo.name}} + + + + {{resolvedEntity.name}} diff --git a/ui/src/app/entity/aliases-entity-select.directive.js b/ui/src/app/entity/aliases-entity-select.directive.js index 7512409f12..7e174ef0d7 100644 --- a/ui/src/app/entity/aliases-entity-select.directive.js +++ b/ui/src/app/entity/aliases-entity-select.directive.js @@ -29,7 +29,7 @@ import aliasesEntitySelectPanelTemplate from './aliases-entity-select-panel.tpl. /*@ngInject*/ export default function AliasesEntitySelectDirective($compile, $templateCache, $mdMedia, types, $mdPanel, $document, $translate) { - var linker = function (scope, element, attrs, ngModelCtrl) { + var linker = function (scope, element, attrs) { /* tbAliasesEntitySelect (ng-model) * { @@ -81,10 +81,8 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $ position: position, fullscreen: false, locals: { - 'entityAliases': angular.copy(scope.model), - 'entityAliasesInfo': scope.entityAliasesInfo, - 'onEntityAliasesUpdate': function (entityAliases) { - scope.model = entityAliases; + 'aliasController': scope.aliasController, + 'onEntityAliasesUpdate': function () { scope.updateView(); } }, @@ -97,40 +95,31 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $ } scope.updateView = function () { - var value = angular.copy(scope.model); - ngModelCtrl.$setViewValue(value); updateDisplayValue(); } - ngModelCtrl.$render = function () { - if (ngModelCtrl.$viewValue) { - var value = ngModelCtrl.$viewValue; - scope.model = angular.copy(value); - updateDisplayValue(); - } - } - function updateDisplayValue() { var displayValue; var singleValue = true; var currentAliasId; - for (var aliasId in scope.model) { - if (!currentAliasId) { - currentAliasId = aliasId; - } else { - singleValue = false; - break; + var entityAliases = scope.aliasController.getEntityAliases(); + for (var aliasId in entityAliases) { + var entityAlias = entityAliases[aliasId]; + if (!entityAlias.filter.resolveMultiple) { + var resolvedAlias = scope.aliasController.getInstantAliasInfo(aliasId); + if (resolvedAlias && resolvedAlias.currentEntity) { + if (!currentAliasId) { + currentAliasId = aliasId; + } else { + singleValue = false; + break; + } + } } } if (singleValue && currentAliasId) { - var entityId = scope.model[currentAliasId].entityId; - var entitiesInfo = scope.entityAliasesInfo[currentAliasId]; - for (var i=0;i
- - - +
- entity.alias - entity.type - entity.entities + entity.alias + alias.entity-filter
@@ -53,23 +48,17 @@
{{$index + 1}}.
- +
entity.alias-required
-
- - -
-
+
+ allowed-entity-types="vm.allowedEntityTypes" + ng-model="entityAlias.filter" + on-matching-entity-change="vm.onFilterEntityChanged(entity, stateEntity, entityAlias)">
+ + + +
+

{{ (vm.isAdd ? 'alias.create-entity-filter' : 'alias.edit-entity-filter') | translate }}

+ + + + +
+
+ + + +
+
+
+ + + + + {{type.name | translate}} + + +
+
alias.filter-type-required
+
+
+
+ {{ 'alias.use-state-entity' | translate }} + + + + + +
+
+ + + + + +
+
entity.entity-name-filter-required
+
+ +
+
+ +
+
+
+
+ + + + {{ 'action.save' | translate }} + + {{ 'action.cancel' | translate }} + + +
\ No newline at end of file diff --git a/ui/src/app/entity/entity-filter.directive.js b/ui/src/app/entity/entity-filter.directive.js index b1d92c6cc2..beded8a162 100644 --- a/ui/src/app/entity/entity-filter.directive.js +++ b/ui/src/app/entity/entity-filter.directive.js @@ -17,13 +17,16 @@ /* eslint-disable import/no-unresolved, import/default */ import entityFilterTemplate from './entity-filter.tpl.html'; +import entityFilterDialogTemplate from './entity-filter-dialog.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ +import EntityFilterDialogController from './entity-filter-dialog.controller'; + import './entity-filter.scss'; /*@ngInject*/ -export default function EntityFilterDirective($compile, $templateCache, $q, entityService) { +export default function EntityFilterDirective($compile, $templateCache, $q, $document, $mdDialog, types) { var linker = function (scope, element, attrs, ngModelCtrl) { @@ -31,8 +34,9 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti element.html(template); scope.ngModelCtrl = ngModelCtrl; + scope.types = types; - scope.fetchEntities = function(searchText, limit) { + /* scope.fetchEntities = function(searchText, limit) { var deferred = $q.defer(); entityService.getEntitiesByNameFilter(scope.entityType, searchText, limit).then(function success(result) { if (result) { @@ -44,13 +48,13 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti deferred.reject(); }); return deferred.promise; - } + }*/ scope.updateValidity = function() { if (ngModelCtrl.$viewValue) { var value = ngModelCtrl.$viewValue; - var valid; - if (value.useFilter) { + ngModelCtrl.$setValidity('filter', value.type ? true : false); + /*if (value.useFilter) { ngModelCtrl.$setValidity('entityList', true); if (angular.isDefined(value.entityNameFilter) && value.entityNameFilter.length > 0) { ngModelCtrl.$setValidity('entityNameFilter', true); @@ -64,18 +68,22 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti ngModelCtrl.$setValidity('entityNameFilterDeviceMatch', true); valid = angular.isDefined(value.entityList) && value.entityList.length > 0; ngModelCtrl.$setValidity('entityList', valid); - } + }*/ + } } ngModelCtrl.$render = function () { - destroyWatchers(); - scope.model = { - useFilter: false, - entityList: [], - entityNameFilter: '' - } + //destroyWatchers(); if (ngModelCtrl.$viewValue) { + scope.model = angular.copy(ngModelCtrl.$viewValue); + } else { + scope.model = { + type: null, + resolveMultiple: false + } + } + /* if (ngModelCtrl.$viewValue) { var value = ngModelCtrl.$viewValue; var model = scope.model; model.useFilter = value.useFilter === true ? true: false; @@ -96,10 +104,52 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti } } ) + }*/ + } + + scope.$watch('model.resolveMultiple', function () { + if (ngModelCtrl.$viewValue) { + var value = ngModelCtrl.$viewValue; + value.resolveMultiple = scope.model.resolveMultiple; + ngModelCtrl.$setViewValue(value); + scope.updateValidity(); } + }); + + scope.editFilter = function($event) { + openEntityFilterDialog($event, false); + } + + scope.createFilter = function($event) { + openEntityFilterDialog($event, true); } - function updateMatchingEntity() { + function openEntityFilterDialog($event, isAdd) { + $mdDialog.show({ + controller: EntityFilterDialogController, + controllerAs: 'vm', + templateUrl: entityFilterDialogTemplate, + locals: { + isAdd: isAdd, + allowedEntityTypes: scope.allowedEntityTypes, + filter: angular.copy(scope.model) + }, + parent: angular.element($document[0].body), + fullscreen: true, + skipHide: true, + targetEvent: $event + }).then(function (result) { + scope.model = result.filter; + ngModelCtrl.$setViewValue(result.filter); + scope.updateValidity(); + if (scope.onMatchingEntityChange) { + scope.onMatchingEntityChange({entity: result.entity, stateEntity: result.stateEntity}); + } + }, function () { + }); + } + + /* function updateMatchingEntity() { if (scope.model.useFilter) { scope.model.matchingEntity = scope.model.matchingFilterEntity; } else { @@ -206,7 +256,7 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti } } }); - } + }*/ $compile(element.contents())(scope); @@ -217,8 +267,7 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti require: "^ngModel", link: linker, scope: { - entityType: '=', - isEdit: '=', + allowedEntityTypes: '=?', onMatchingEntityChange: '&' } }; diff --git a/ui/src/app/entity/entity-filter.tpl.html b/ui/src/app/entity/entity-filter.tpl.html index 7e49ba943f..9a5337a7a8 100644 --- a/ui/src/app/entity/entity-filter.tpl.html +++ b/ui/src/app/entity/entity-filter.tpl.html @@ -17,7 +17,7 @@ -->
-
+ +
+
{{ types.aliasFilterType[model.type].name | translate }}
+ + + {{ 'alias.edit-entity-filter' | translate }} + + + edit + + +
+ + + add + + {{ 'alias.create-entity-filter' | translate }} + +
- - + +
\ No newline at end of file diff --git a/ui/src/app/entity/entity-list.directive.js b/ui/src/app/entity/entity-list.directive.js new file mode 100644 index 0000000000..0863153046 --- /dev/null +++ b/ui/src/app/entity/entity-list.directive.js @@ -0,0 +1,130 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable import/no-unresolved, import/default */ + +import entityListTemplate from './entity-list.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +import './entity-list.scss'; + +/*@ngInject*/ +export default function EntityListDirective($compile, $templateCache, $q, $mdUtil, entityService) { + + var linker = function (scope, element, attrs, ngModelCtrl) { + + var template = $templateCache.get(entityListTemplate); + element.html(template); + + scope.ngModelCtrl = ngModelCtrl; + + scope.$watch('tbRequired', function () { + scope.updateValidity(); + }); + + scope.fetchEntities = function(searchText, limit) { + var deferred = $q.defer(); + entityService.getEntitiesByNameFilter(scope.entityType, searchText, limit).then( + function success(result) { + if (result) { + deferred.resolve(result); + } else { + deferred.resolve([]); + } + }, + function fail() { + deferred.reject(); + } + ); + return deferred.promise; + } + + scope.updateValidity = function() { + var value = ngModelCtrl.$viewValue; + var valid = !scope.tbRequired || value && value.length > 0; + ngModelCtrl.$setValidity('entityList', valid); + } + + ngModelCtrl.$render = function () { + destroyWatchers(); + var value = ngModelCtrl.$viewValue; + scope.entityList = []; + if (value && value.length > 0) { + entityService.getEntities(scope.entityType, value).then(function (entities) { + scope.entityList = entities; + initWatchers(); + }); + } else { + initWatchers(); + } + } + + function initWatchers() { + scope.entityTypeDeregistration = scope.$watch('entityType', function (newEntityType, prevEntityType) { + if (!angular.equals(newEntityType, prevEntityType)) { + scope.entityList = []; + } + }); + scope.entityListDeregistration = scope.$watch('entityList', function () { + var ids = []; + if (scope.entityList && scope.entityList.length > 0) { + for (var i=0;i + + + + {{item.name}} + + + entity.no-entities-matching + + + + + {{$chip.name}} + + + + +
\ No newline at end of file diff --git a/ui/src/app/entity/index.js b/ui/src/app/entity/index.js index e8cc43797d..6be118f8ad 100644 --- a/ui/src/app/entity/index.js +++ b/ui/src/app/entity/index.js @@ -19,6 +19,7 @@ import EntityTypeSelectDirective from './entity-type-select.directive'; import EntitySubtypeSelectDirective from './entity-subtype-select.directive'; import EntitySubtypeAutocompleteDirective from './entity-subtype-autocomplete.directive'; import EntityAutocompleteDirective from './entity-autocomplete.directive'; +import EntityListDirective from './entity-list.directive'; import EntitySelectDirective from './entity-select.directive'; import EntityFilterDirective from './entity-filter.directive'; import AliasesEntitySelectPanelController from './aliases-entity-select-panel.controller'; @@ -38,6 +39,7 @@ export default angular.module('thingsboard.entity', []) .directive('tbEntitySubtypeSelect', EntitySubtypeSelectDirective) .directive('tbEntitySubtypeAutocomplete', EntitySubtypeAutocompleteDirective) .directive('tbEntityAutocomplete', EntityAutocompleteDirective) + .directive('tbEntityList', EntityListDirective) .directive('tbEntitySelect', EntitySelectDirective) .directive('tbEntityFilter', EntityFilterDirective) .directive('tbAliasesEntitySelect', AliasesEntitySelectDirective) diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index fcde13c9a4..a23845eb1c 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -112,6 +112,25 @@ export default angular.module('thingsboard.locale', []) "no-alarms-matching": "No alarms matching '{{entity}}' were found.", "alarm-required": "Alarm is required" }, + "alias": { + "filter-type-entity-list": "Entity list", + "filter-type-entity-name": "Entity name", + "filter-type-asset-type": "Asset type", + "filter-type-device-type": "Device type", + "filter-type-relations-query": "Relations query", + "filter-type-asset-search-query": "Asset search query", + "filter-type-device-search-query": "Device search query", + "entity-filter": "Entity filter", + "create-entity-filter": "Create entity filter", + "edit-entity-filter": "Edit entity filter", + "entity-filter-required": "Entity filter is required.", + "resolve-multiple": "Multiple", + "filter-type": "Filter type", + "filter-type-required": "Filter type is required.", + "use-state-entity": "Use state entity", + "state-entity": "State entity", + "entity-filter-no-entity-matched": "No entities matching specified filter were found.", + }, "asset": { "asset": "Asset", "assets": "Assets", diff --git a/ui/src/app/services/item-buffer.service.js b/ui/src/app/services/item-buffer.service.js index afb63bf049..3aa48097fe 100644 --- a/ui/src/app/services/item-buffer.service.js +++ b/ui/src/app/services/item-buffer.service.js @@ -264,14 +264,9 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) { } dashboardUtils.addWidgetToLayout(theDashboard, targetState, targetLayout, widget, originalColumns, originalSize, row, column); if (callAliasUpdateFunction) { - onAliasesUpdateFunction().then( - function() { - deferred.resolve(theDashboard); - } - ); - } else { - deferred.resolve(theDashboard); + onAliasesUpdateFunction(); } + deferred.resolve(theDashboard); return deferred.promise; } From 75351e356a7f4f32307b3c3a8691f3a3b428c69e Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 8 Jun 2017 09:24:11 +0300 Subject: [PATCH 2/7] TB-61: Update import/export --- ui/src/app/api/alias-controller.js | 31 +++-- ui/src/app/api/entity.service.js | 106 +++++++++--------- ui/src/app/api/subscription.js | 27 +++-- ui/src/app/common/dashboard-utils.service.js | 9 ++ ui/src/app/components/widget.controller.js | 25 +++-- ui/src/app/dashboard/dashboard.controller.js | 2 + ...d-widget-to-dashboard-dialog.controller.js | 17 +-- .../entity/entity-filter-dialog.controller.js | 2 +- .../import-export/import-export.service.js | 66 +++++++---- ui/src/app/services/item-buffer.service.js | 22 ++-- 10 files changed, 178 insertions(+), 129 deletions(-) diff --git a/ui/src/app/api/alias-controller.js b/ui/src/app/api/alias-controller.js index 779436336f..49eb8fe40e 100644 --- a/ui/src/app/api/alias-controller.js +++ b/ui/src/app/api/alias-controller.js @@ -96,6 +96,7 @@ export default class AliasController { function success(aliasInfo) { aliasCtrl.resolvedAliases[aliasId] = aliasInfo; if (entityAlias.filter.stateEntity) { + aliasInfo.stateEntity = true; aliasCtrl.resolvedAliasesToStateEntities[aliasId] = aliasCtrl.stateController.getStateParams().entityId; } @@ -120,12 +121,13 @@ export default class AliasController { function success(aliasInfo) { datasource.aliasName = aliasInfo.alias; if (aliasInfo.resolveMultiple) { + var newDatasource; var resolvedEntities = aliasInfo.resolvedEntities; if (resolvedEntities && resolvedEntities.length) { var datasources = []; for (var i=0;i 0) { - deferred.resolve(true); - } else { - deferred.resolve(false); - } - }, - function fail() { - deferred.resolve(false); - } - ); - return deferred.promise; - } - function createDatasourcesFromSubscriptionsInfo(subscriptionsInfo) { var deferred = $q.defer(); var datasources = []; diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js index 471203c3b2..dd518c7ea5 100644 --- a/ui/src/app/api/subscription.js +++ b/ui/src/app/api/subscription.js @@ -125,17 +125,22 @@ export default class Subscription { initDataSubscription() { var deferred = this.ctx.$q.defer(); - var subscription = this; - this.ctx.aliasController.resolveDatasources(this.datasources).then( - function success(datasources) { - subscription.datasources = datasources; - subscription.configureData(); - deferred.resolve(); - }, - function fail() { - deferred.reject(); - } - ); + if (!this.ctx.aliasController) { + this.configureData(); + deferred.resolve(); + } else { + var subscription = this; + this.ctx.aliasController.resolveDatasources(this.datasources).then( + function success(datasources) { + subscription.datasources = datasources; + subscription.configureData(); + deferred.resolve(); + }, + function fail() { + deferred.reject(); + } + ); + } return deferred.promise; } diff --git a/ui/src/app/common/dashboard-utils.service.js b/ui/src/app/common/dashboard-utils.service.js index 51a0215dd2..58d1dbde7f 100644 --- a/ui/src/app/common/dashboard-utils.service.js +++ b/ui/src/app/common/dashboard-utils.service.js @@ -57,6 +57,9 @@ function DashboardUtils(types, utils, timeService) { for (aliasId in entityAliases) { entityAlias = entityAliases[aliasId]; entityAliases[aliasId] = validateAndUpdateEntityAlias(entityAlias); + if (!entityAliases[aliasId].id) { + entityAliases[aliasId].id = aliasId; + } } } return configuration; @@ -202,6 +205,12 @@ function DashboardUtils(types, utils, timeService) { states[firstStateId].root = true; } } + + /* var datasources = {}; + for (var widgetId in dashboard.configuration.widgets) { + + }*/ + dashboard.configuration = validateAndUpdateEntityAliases(dashboard.configuration); if (angular.isUndefined(dashboard.configuration.timewindow)) { diff --git a/ui/src/app/components/widget.controller.js b/ui/src/app/components/widget.controller.js index 103ad138e4..4407381491 100644 --- a/ui/src/app/components/widget.controller.js +++ b/ui/src/app/components/widget.controller.js @@ -38,6 +38,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q var gridsterItemInited = false; var subscriptionInited = false; + var widgetSizeDetected = false; var cafs = {}; @@ -206,9 +207,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q initialize().then( function(){ - if (checkSize()) { - onInit(); - } + onInit(); } ); @@ -488,10 +487,15 @@ export default function WidgetController($scope, $timeout, $window, $element, $q $scope.widgetErrorData = utils.processWidgetException(e); } - function onInit() { - if (!widgetContext.inited && - subscriptionInited && - gridsterItemInited) { + function isReady() { + return subscriptionInited && gridsterItemInited && widgetSizeDetected; + } + + function onInit(skipSizeCheck) { + if (!skipSizeCheck) { + checkSize(); + } + if (!widgetContext.inited && isReady()) { widgetContext.inited = true; try { widgetTypeInstance.onInit(); @@ -516,6 +520,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q widgetContext.width = width; widgetContext.height = height; sizeChanged = true; + widgetSizeDetected = true; } } return sizeChanged; @@ -536,7 +541,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q } }); } else { - onInit(); + onInit(true); } } } @@ -545,9 +550,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q if (item && item.gridster) { widgetContext.isMobile = item.gridster.isMobile; gridsterItemInited = true; - if (checkSize()) { - onInit(); - } + onInit(); // gridsterItemElement = $(item.$element); //updateVisibility(); } diff --git a/ui/src/app/dashboard/dashboard.controller.js b/ui/src/app/dashboard/dashboard.controller.js index 416886475e..7c97a9462a 100644 --- a/ui/src/app/dashboard/dashboard.controller.js +++ b/ui/src/app/dashboard/dashboard.controller.js @@ -344,6 +344,8 @@ export default function DashboardController(types, utils, dashboardUtils, widget vm.dashboardConfiguration = vm.dashboard.configuration; vm.dashboardCtx.dashboard = vm.dashboard; vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow; + vm.dashboardCtx.aliasController = new AliasController($scope, $q, $filter, utils, + types, entityService, vm.dashboardCtx.stateController, vm.dashboardConfiguration.entityAliases); var parentScope = $window.parent.angular.element($window.frameElement).scope(); parentScope.$root.$broadcast('widgetEditModeInited'); parentScope.$root.$apply(); diff --git a/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js b/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js index e5822a44a5..b28a46bc48 100644 --- a/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js +++ b/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js @@ -22,7 +22,8 @@ import selectTargetLayoutTemplate from '../../dashboard/layouts/select-target-la /* eslint-enable import/no-unresolved, import/default */ /*@ngInject*/ -export default function AddWidgetToDashboardDialogController($scope, $mdDialog, $state, $q, $document, itembuffer, dashboardService, entityId, entityType, entityName, widget) { +export default function AddWidgetToDashboardDialogController($scope, $mdDialog, $state, $q, $document, + types, itembuffer, dashboardService, entityId, entityType, entityName, widget) { var vm = this; @@ -125,12 +126,14 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog, targetDeviceAliases: {} }; aliasesInfo.datasourceAliases[0] = { - aliasName: entityName, - entityType: entityType, - entityFilter: { - useFilter: false, - entityNameFilter: '', - entityList: [entityId] + id: 1, + alias: entityName, + filter: { + type: types.aliasFilterType.entityList.value, + stateEntity: false, + entityList: [entityId], + entityType: entityType, + resolveMultiple: false } }; itembuffer.addWidgetToDashboard(theDashboard, targetState, targetLayout, vm.widget, aliasesInfo, null, 48, null, -1, -1).then( diff --git a/ui/src/app/entity/entity-filter-dialog.controller.js b/ui/src/app/entity/entity-filter-dialog.controller.js index 2b1a30d8f4..17a19d61d2 100644 --- a/ui/src/app/entity/entity-filter-dialog.controller.js +++ b/ui/src/app/entity/entity-filter-dialog.controller.js @@ -64,7 +64,7 @@ export default function EntityFilterDialogController($scope, $mdDialog, $q, enti entity: null, stateEntity: false } - entityService.resolveAliasFilter(vm.filter).then( + entityService.resolveAliasFilter(vm.filter, null, 1).then( function success(result) { validationResult.stateEntity = result.stateEntity; var entities = result.entities; diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js index af04687d5b..7f7f774dff 100644 --- a/ui/src/app/import-export/import-export.service.js +++ b/ui/src/app/import-export/import-export.service.js @@ -359,30 +359,50 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, } function prepareEntityAlias(aliasInfo) { - var entityFilter; - var entityType; + var alias; + var filter; if (aliasInfo.deviceId) { - entityFilter = { - useFilter: false, - entityNameFilter: '', - entityList: [aliasInfo.deviceId] - } - entityType = types.entityType.device; + alias = aliasInfo.aliasName; + filter = { + type: types.aliasFilterType.entityList.value, + stateEntity: false, + entityType: types.entityType.device, + entityList: [aliasInfo.deviceId], + resolveMultiple: false + }; } else if (aliasInfo.deviceFilter) { - entityFilter = { - useFilter: aliasInfo.deviceFilter.useFilter, - entityNameFilter: aliasInfo.deviceFilter.deviceNameFilter, - entityList: aliasInfo.deviceFilter.deviceList + alias = aliasInfo.aliasName; + filter = { + type: aliasInfo.deviceFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value, + entityType: types.entityType.device, + resolveMultiple: false + } + if (filter.type == types.aliasFilterType.entityList.value) { + filter.stateEntity = false; + filter.entityList = aliasInfo.deviceFilter.deviceList + } else { + filter.entityNameFilter = aliasInfo.deviceFilter.deviceNameFilter; + } + } else if (aliasInfo.entityFilter) { + alias = aliasInfo.aliasName; + filter = { + type: aliasInfo.entityFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value, + entityType: aliasInfo.entityType, + resolveMultiple: false + } + if (filter.type == types.aliasFilterType.entityList.value) { + filter.stateEntity = false; + filter.entityList = aliasInfo.entityFilter.entityList; + } else { + filter.entityNameFilter = aliasInfo.entityFilter.entityNameFilter; } - entityType = types.entityType.device; } else { - entityFilter = aliasInfo.entityFilter; - entityType = aliasInfo.entityType; + alias = aliasInfo.alias; + filter = aliasInfo.filter; } return { - aliasName: aliasInfo.aliasName, - entityType: entityType, - entityFilter: entityFilter + alias: alias, + filter: filter }; } @@ -411,6 +431,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, for (datasourceIndex in datasourceAliases) { datasourceAliasesMap[aliasId] = datasourceIndex; entityAliases[aliasId] = datasourceAliases[datasourceIndex]; + entityAliases[aliasId].id = aliasId; aliasId++; } } @@ -418,6 +439,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, for (datasourceIndex in targetDeviceAliases) { targetDeviceAliasesMap[aliasId] = datasourceIndex; entityAliases[aliasId] = targetDeviceAliases[datasourceIndex]; + entityAliases[aliasId].id = aliasId; aliasId++; } } @@ -435,12 +457,10 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, var datasourceIndex; if (datasourceAliasesMap[aliasId]) { datasourceIndex = datasourceAliasesMap[aliasId]; - datasourceAliases[datasourceIndex].entityType = entityAlias.entityType; - datasourceAliases[datasourceIndex].entityFilter = entityAlias.entityFilter; + datasourceAliases[datasourceIndex] = entityAlias; } else if (targetDeviceAliasesMap[aliasId]) { datasourceIndex = targetDeviceAliasesMap[aliasId]; - targetDeviceAliases[datasourceIndex].entityType = entityAlias.entityType; - targetDeviceAliases[datasourceIndex].entityFilter = entityAlias.entityFilter; + targetDeviceAliases[datasourceIndex] = entityAlias; } } addImportedWidget(dashboard, targetState, targetLayoutFunction, $event, widget, @@ -622,7 +642,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, checkNextEntityAliasOrComplete(index, aliasIds, entityAliases, missingEntityAliases, deferred); } else { var missingEntityAlias = angular.copy(entityAlias); - missingEntityAlias.entityFilter = null; + missingEntityAlias.filter = null; missingEntityAliases[aliasId] = missingEntityAlias; checkNextEntityAliasOrComplete(index, aliasIds, entityAliases, missingEntityAliases, deferred); } diff --git a/ui/src/app/services/item-buffer.service.js b/ui/src/app/services/item-buffer.service.js index 3aa48097fe..309e53e59c 100644 --- a/ui/src/app/services/item-buffer.service.js +++ b/ui/src/app/services/item-buffer.service.js @@ -46,16 +46,14 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) { aliasesInfo { datasourceAliases: { datasourceIndex: { - aliasName: "...", - entityType: "...", - entityFilter: "..." + alias: "...", + filter: "..." } } targetDeviceAliases: { targetDeviceAliasIndex: { - aliasName: "...", - entityType: "...", - entityFilter: "..." + alias: "...", + filter: "..." } } .... @@ -64,9 +62,8 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) { function prepareAliasInfo(entityAlias) { return { - aliasName: entityAlias.alias, - entityType: entityAlias.entityType, - entityFilter: entityAlias.entityFilter + alias: entityAlias.alias, + filter: entityAlias.filter }; } @@ -288,8 +285,7 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) { } function isEntityAliasEqual(alias1, alias2) { - return alias1.entityType === alias2.entityType && - angular.equals(alias1.entityFilter, alias2.entityFilter); + return angular.equals(alias1.filter, alias2.filter); } function getEntityAliasId(entityAliases, aliasInfo) { @@ -301,13 +297,13 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) { } } if (!newAliasId) { - var newAliasName = createEntityAliasName(entityAliases, aliasInfo.aliasName); + var newAliasName = createEntityAliasName(entityAliases, aliasInfo.alias); newAliasId = 0; for (aliasId in entityAliases) { newAliasId = Math.max(newAliasId, aliasId); } newAliasId++; - entityAliases[newAliasId] = {alias: newAliasName, entityType: aliasInfo.entityType, entityFilter: aliasInfo.entityFilter}; + entityAliases[newAliasId] = {id: newAliasId, alias: newAliasName, filter: aliasInfo.filter}; } return newAliasId; } From 43b23cef4feb81697eb7807670b9ad5adb2edd0f Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 8 Jun 2017 21:15:47 +0300 Subject: [PATCH 3/7] TB-61: Improve Alias list editor. --- ui/src/app/api/alias-controller.js | 4 + ui/src/app/api/subscription.js | 103 ++--------- ui/src/app/common/dashboard-utils.service.js | 85 +++++++-- ui/src/app/common/utils.service.js | 11 ++ ui/src/app/components/dashboard.directive.js | 31 +++- ui/src/app/components/widget.controller.js | 157 +++++++++++------ ui/src/app/components/widget.directive.js | 85 +-------- .../aliases-entity-select-panel.controller.js | 15 +- .../aliases-entity-select-panel.tpl.html | 4 +- .../entity/aliases-entity-select.directive.js | 8 + ...d-widget-to-dashboard-dialog.controller.js | 11 +- .../attribute/attribute-table.directive.js | 43 +++-- .../entity/attribute/attribute-table.tpl.html | 2 +- .../app/entity/entity-aliases.controller.js | 17 +- ui/src/app/entity/entity-aliases.scss | 15 +- ui/src/app/entity/entity-aliases.tpl.html | 56 +++--- ui/src/app/entity/entity-filter.directive.js | 163 +----------------- ui/src/app/entity/entity-filter.scss | 15 +- ui/src/app/entity/entity-filter.tpl.html | 101 ++++------- .../import-export/import-export.service.js | 9 +- ui/src/app/locale/locale.constant.js | 2 +- ui/src/app/services/item-buffer.service.js | 6 +- 22 files changed, 363 insertions(+), 580 deletions(-) diff --git a/ui/src/app/api/alias-controller.js b/ui/src/app/api/alias-controller.js index 49eb8fe40e..b6d29a9599 100644 --- a/ui/src/app/api/alias-controller.js +++ b/ui/src/app/api/alias-controller.js @@ -100,6 +100,7 @@ export default class AliasController { aliasCtrl.resolvedAliasesToStateEntities[aliasId] = aliasCtrl.stateController.getStateParams().entityId; } + aliasCtrl.$scope.$broadcast('entityAliasResolved', aliasId); deferred.resolve(aliasInfo); }, function fail() { @@ -239,6 +240,9 @@ export default class AliasController { datasource.name = name; datasource.aliasName = name; datasource.entityName = name; + } else if (datasource.unresolvedStateEntity) { + datasource.name = "Unresolved"; + datasource.entityName = "Unresolved"; } datasource.dataKeys.forEach(function(dataKey) { if (datasource.generated) { diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js index dd518c7ea5..4cb382ceab 100644 --- a/ui/src/app/api/subscription.js +++ b/ui/src/app/api/subscription.js @@ -73,6 +73,15 @@ export default class Subscription { this.datasources = this.ctx.utils.validateDatasources(options.datasources); this.datasourceListeners = []; + + /* + * data = array of datasourceData + * datasourceData = { + * tbDatasource, + * dataKey, { name, config } + * data = array of [time, value] + * } + */ this.data = []; this.hiddenData = []; this.originalTimewindow = null; @@ -543,39 +552,6 @@ export default class Subscription { var datasource = this.datasources[i]; if (angular.isFunction(datasource)) continue; - /* var entityId = null; - var entityType = null; - if (datasource.type === this.ctx.types.datasourceType.entity) { - var aliasName = null; - var entityName = null; - if (datasource.entityId) { - entityId = datasource.entityId; - entityType = datasource.entityType; - datasource.name = datasource.entityName; - aliasName = datasource.entityName; - entityName = datasource.entityName; - } else if (datasource.entityAliasId) { - if (this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId]) { - entityId = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].entityId; - entityType = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].entityType; - datasource.name = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].alias; - aliasName = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].alias; - entityName = ''; - var entitiesInfo = this.ctx.aliasesInfo.entityAliasesInfo[datasource.entityAliasId]; - for (var d = 0; d < entitiesInfo.length; d++) { - if (entitiesInfo[d].id === entityId) { - entityName = entitiesInfo[d].name; - break; - } - } - } - } - } else { - datasource.name = datasource.name || this.ctx.types.datasourceType.function; - } - for (var dk = 0; dk < datasource.dataKeys.length; dk++) { - updateDataKeyLabel(datasource.dataKeys[dk], datasource.name, entityName, aliasName); - }*/ var subscription = this; @@ -606,6 +582,10 @@ export default class Subscription { this.datasourceListeners.push(listener); this.ctx.datasourceService.subscribeToDatasource(listener); + if (datasource.unresolvedStateEntity) { + this.notifyDataLoaded(); + this.onDataUpdated(); + } } } @@ -625,19 +605,6 @@ export default class Subscription { } else { return false; } - /*var deviceId = null; - if (this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId]) { - deviceId = this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId].entityId; - } - if (!angular.equals(deviceId, this.targetDeviceId)) { - this.targetDeviceId = deviceId; - if (this.targetDeviceId) { - this.rpcEnabled = true; - } else { - this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false; - } - this.callbacks.rpcStateChanged(this); - }*/ } checkSubscriptions(aliasIds) { @@ -650,29 +617,8 @@ export default class Subscription { break; } } - /*var entityId = null; - var entityType = null; - var aliasName = null; - if (listener.datasource.type === this.ctx.types.datasourceType.entity) { - if (listener.datasource.entityAliasId && - this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId]) { - entityId = this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId].entityId; - entityType = this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId].entityType; - aliasName = this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId].alias; - } - if (!angular.equals(entityId, listener.entityId) || - !angular.equals(entityType, listener.entityType) || - !angular.equals(aliasName, listener.datasource.name)) { - subscriptionsChanged = true; - break; - } - }*/ } return subscriptionsChanged; - /*if (subscriptionsChanged) { - this.unsubscribe(); - this.subscribe(); - }*/ } destroy() { @@ -691,29 +637,6 @@ export default class Subscription { } -/*const varsRegex = /\$\{([^\}]*)\}/g; - -function updateDataKeyLabel(dataKey, dsName, entityName, aliasName) { - var pattern = dataKey.pattern; - var label = dataKey.pattern; - var match = varsRegex.exec(pattern); - while (match !== null) { - var variable = match[0]; - var variableName = match[1]; - if (variableName === 'dsName') { - label = label.split(variable).join(dsName); - } else if (variableName === 'entityName') { - label = label.split(variable).join(entityName); - } else if (variableName === 'deviceName') { - label = label.split(variable).join(entityName); - } else if (variableName === 'aliasName') { - label = label.split(variable).join(aliasName); - } - match = varsRegex.exec(pattern); - } - dataKey.label = label; -}*/ - function calculateMin(data) { if (data.length > 0) { var result = Number(data[0][1]); diff --git a/ui/src/app/common/dashboard-utils.service.js b/ui/src/app/common/dashboard-utils.service.js index 58d1dbde7f..fe8aa93f7b 100644 --- a/ui/src/app/common/dashboard-utils.service.js +++ b/ui/src/app/common/dashboard-utils.service.js @@ -23,8 +23,10 @@ function DashboardUtils(types, utils, timeService) { var service = { validateAndUpdateDashboard: validateAndUpdateDashboard, + validateAndUpdateWidget: validateAndUpdateWidget, getRootStateId: getRootStateId, createSingleWidgetDashboard: createSingleWidgetDashboard, + createSingleEntityFilter: createSingleEntityFilter, getStateLayoutsData: getStateLayoutsData, createDefaultState: createDefaultState, createDefaultLayoutData: createDefaultLayoutData, @@ -39,7 +41,7 @@ function DashboardUtils(types, utils, timeService) { return service; - function validateAndUpdateEntityAliases(configuration) { + function validateAndUpdateEntityAliases(configuration, datasourcesByAliasId, targetDevicesByAliasId) { var aliasId, entityAlias; if (angular.isUndefined(configuration.entityAliases)) { configuration.entityAliases = {}; @@ -47,8 +49,8 @@ function DashboardUtils(types, utils, timeService) { var deviceAliases = configuration.deviceAliases; for (aliasId in deviceAliases) { var deviceAlias = deviceAliases[aliasId]; - entityAlias = validateAndUpdateDeviceAlias(aliasId, deviceAlias); - configuration.entityAliases[aliasId] = entityAlias; + entityAlias = validateAndUpdateDeviceAlias(aliasId, deviceAlias, datasourcesByAliasId, targetDevicesByAliasId); + configuration.entityAliases[entityAlias.id] = entityAlias; } delete configuration.deviceAliases; } @@ -56,16 +58,43 @@ function DashboardUtils(types, utils, timeService) { var entityAliases = configuration.entityAliases; for (aliasId in entityAliases) { entityAlias = entityAliases[aliasId]; - entityAliases[aliasId] = validateAndUpdateEntityAlias(entityAlias); - if (!entityAliases[aliasId].id) { - entityAliases[aliasId].id = aliasId; + entityAlias = validateAndUpdateEntityAlias(aliasId, entityAlias, datasourcesByAliasId, targetDevicesByAliasId); + if (aliasId != entityAlias.id) { + delete entityAliases[aliasId]; } + entityAliases[entityAlias.id] = entityAlias; } } return configuration; } - function validateAndUpdateDeviceAlias(aliasId, deviceAlias) { + function validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId) { + if (!aliasId || !angular.isString(aliasId) || aliasId.length != 36) { + var newAliasId = utils.guid(); + var aliasDatasources = datasourcesByAliasId[aliasId]; + if (aliasDatasources) { + aliasDatasources.forEach( + function(datasource) { + datasource.entityAliasId = newAliasId; + } + ); + } + var targetDeviceAliasIdsList = targetDevicesByAliasId[aliasId]; + if (targetDeviceAliasIdsList) { + targetDeviceAliasIdsList.forEach( + function(targetDeviceAliasIds) { + targetDeviceAliasIds[0] = newAliasId; + } + ); + } + return newAliasId; + } else { + return aliasId; + } + } + + function validateAndUpdateDeviceAlias(aliasId, deviceAlias, datasourcesByAliasId, targetDevicesByAliasId) { + aliasId = validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId); var alias = deviceAlias.alias; var entityAlias = { id: aliasId, @@ -93,7 +122,8 @@ function DashboardUtils(types, utils, timeService) { return entityAlias; } - function validateAndUpdateEntityAlias(entityAlias) { + function validateAndUpdateEntityAlias(aliasId, entityAlias, datasourcesByAliasId, targetDevicesByAliasId) { + entityAlias.id = validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId); if (!entityAlias.filter) { entityAlias.filter = { type: entityAlias.entityFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value, @@ -206,12 +236,33 @@ function DashboardUtils(types, utils, timeService) { } } - /* var datasources = {}; + var datasourcesByAliasId = {}; + var targetDevicesByAliasId = {}; for (var widgetId in dashboard.configuration.widgets) { + widget = dashboard.configuration.widgets[widgetId]; + widget.config.datasources.forEach(function (datasource) { + if (datasource.entityAliasId) { + var aliasId = datasource.entityAliasId; + var aliasDatasources = datasourcesByAliasId[aliasId]; + if (!aliasDatasources) { + aliasDatasources = []; + datasourcesByAliasId[aliasId] = aliasDatasources; + } + aliasDatasources.push(datasource); + } + }); + if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length) { + var aliasId = widget.config.targetDeviceAliasIds[0]; + var targetDeviceAliasIdsList = targetDevicesByAliasId[aliasId]; + if (!targetDeviceAliasIdsList) { + targetDeviceAliasIdsList = []; + targetDevicesByAliasId[aliasId] = targetDeviceAliasIdsList; + } + targetDeviceAliasIdsList.push(widget.config.targetDeviceAliasIds); + } + } - }*/ - - dashboard.configuration = validateAndUpdateEntityAliases(dashboard.configuration); + dashboard.configuration = validateAndUpdateEntityAliases(dashboard.configuration, datasourcesByAliasId, targetDevicesByAliasId); if (angular.isUndefined(dashboard.configuration.timewindow)) { dashboard.configuration.timewindow = timeService.defaultTimewindow(); @@ -288,6 +339,16 @@ function DashboardUtils(types, utils, timeService) { return dashboard; } + function createSingleEntityFilter(entityType, entityId) { + return { + type: types.aliasFilterType.entityList.value, + stateEntity: false, + entityList: [entityId], + entityType: entityType, + resolveMultiple: false + }; + } + function getStateLayoutsData(dashboard, targetState) { var dashboardConfiguration = dashboard.configuration; var states = dashboardConfiguration.states; diff --git a/ui/src/app/common/utils.service.js b/ui/src/app/common/utils.service.js index 7b4d65ef78..8bf76c4f0f 100644 --- a/ui/src/app/common/utils.service.js +++ b/ui/src/app/common/utils.service.js @@ -106,6 +106,7 @@ function Utils($mdColorPalette, $rootScope, $window, types) { isDescriptorSchemaNotEmpty: isDescriptorSchemaNotEmpty, filterSearchTextEntities: filterSearchTextEntities, guid: guid, + cleanCopy: cleanCopy, isLocalUrl: isLocalUrl, validateDatasources: validateDatasources, createKey: createKey, @@ -291,6 +292,16 @@ function Utils($mdColorPalette, $rootScope, $window, types) { s4() + '-' + s4() + s4() + s4(); } + function cleanCopy(object) { + var copy = angular.copy(object); + for (var prop in copy) { + if (prop && prop.startsWith('$$')) { + delete copy[prop]; + } + } + return copy; + } + function genNextColor(datasources) { var index = 0; if (datasources) { diff --git a/ui/src/app/components/dashboard.directive.js b/ui/src/app/components/dashboard.directive.js index 965b83854f..a88e656fe2 100644 --- a/ui/src/app/components/dashboard.directive.js +++ b/ui/src/app/components/dashboard.directive.js @@ -85,7 +85,7 @@ function Dashboard() { } /*@ngInject*/ -function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, timeService, types, utils) { +function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $mdUtil, timeService, types, utils) { var highlightedMode = false; var highlightedWidget = null; @@ -792,7 +792,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t } function dashboardLoaded() { - $timeout(function () { + $mdUtil.nextTick(function () { if (vm.dashboardTimewindowWatch) { vm.dashboardTimewindowWatch(); vm.dashboardTimewindowWatch = null; @@ -802,14 +802,27 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t }, true); adoptMaxRows(); vm.dashboardLoading = false; - $timeout(function () { - var gridsterScope = gridsterElement.scope(); - vm.gridster = gridsterScope.gridster; - if (vm.onInit) { - vm.onInit({dashboard: vm}); + if ($scope.gridsterScopeWatcher) { + $scope.gridsterScopeWatcher(); + } + $scope.gridsterScopeWatcher = $scope.$watch( + function() { + var hasScope = gridsterElement.scope() ? true : false; + return hasScope; + }, + function(hasScope) { + if (hasScope) { + $scope.gridsterScopeWatcher(); + $scope.gridsterScopeWatcher = null; + var gridsterScope = gridsterElement.scope(); + vm.gridster = gridsterScope.gridster; + if (vm.onInit) { + vm.onInit({dashboard: vm}); + } + } } - }, 0, false); - }, 0, false); + ); + }); } function loading() { diff --git a/ui/src/app/components/widget.controller.js b/ui/src/app/components/widget.controller.js index 4407381491..08885b841e 100644 --- a/ui/src/app/components/widget.controller.js +++ b/ui/src/app/components/widget.controller.js @@ -20,9 +20,9 @@ import Subscription from '../api/subscription'; /* eslint-disable angular/angularelement */ /*@ngInject*/ -export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, tbRaf, types, utils, timeService, +export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, $compile, tbRaf, types, utils, timeService, datasourceService, entityService, deviceService, visibleRect, isEdit, stDiff, dashboardTimewindow, - dashboardTimewindowApi, widget, aliasController, stateController, widgetType) { + dashboardTimewindowApi, widget, aliasController, stateController, widgetInfo, widgetType) { var vm = this; @@ -42,20 +42,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q var cafs = {}; - /* - * data = array of datasourceData - * datasourceData = { - * tbDatasource, - * dataKey, { name, config } - * data = array of [time, value] - * } - */ - var widgetContext = { inited: false, $scope: $scope, - $container: $('#container', $element), - $containerParent: $($element), + $container: null, + $containerParent: null, width: 0, height: 0, isEdit: isEdit, @@ -82,30 +73,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q createSubscription: function(options, subscribe) { return createSubscription(options, subscribe); }, - - - // type: "timeseries" or "latest" or "rpc" - /* subscriptionInfo = [ - { - entityType: "" - entityId: "" - entityName: "" - timeseries: [{ name: "", label: "" }, ..] - attributes: [{ name: "", label: "" }, ..] - } - .. - ]*/ - - // options = { - // timeWindowConfig, - // useDashboardTimewindow, - // legendConfig, - // decimals, - // units, - // callbacks [ onDataUpdated(subscription, apply) ] - // } - // - createSubscriptionFromInfo: function (type, subscriptionsInfo, options, useDefaultComponents, subscribe) { return createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe); }, @@ -211,21 +178,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q } ); - /* - options = { - type, - targetDeviceAliasIds, // RPC - targetDeviceIds, // RPC - datasources, - timeWindowConfig, - useDashboardTimewindow, - legendConfig, - decimals, - units, - callbacks - } - */ - function createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe) { var deferred = $q.defer(); options.type = type; @@ -400,6 +352,101 @@ export default function WidgetController($scope, $timeout, $window, $element, $q return deferred.promise; } + function configureWidgetElement() { + + $scope.displayLegend = angular.isDefined(widget.config.showLegend) ? + widget.config.showLegend : widget.type === types.widgetType.timeseries.value; + + if ($scope.displayLegend) { + $scope.legendConfig = widget.config.legendConfig || + { + position: types.position.bottom.value, + showMin: false, + showMax: false, + showAvg: widget.type === types.widgetType.timeseries.value, + showTotal: false + }; + $scope.legendData = { + keys: [], + data: [] + }; + } + + var html = '
' + + 'Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}' + + '
' + + '
' + + '' + + '
'; + + var containerHtml = '
' + widgetInfo.templateHtml + '
'; + if ($scope.displayLegend) { + var layoutType; + if ($scope.legendConfig.position === types.position.top.value || + $scope.legendConfig.position === types.position.bottom.value) { + layoutType = 'column'; + } else { + layoutType = 'row'; + } + + var legendStyle; + switch($scope.legendConfig.position) { + case types.position.top.value: + legendStyle = 'padding-bottom: 8px;'; + break; + case types.position.bottom.value: + legendStyle = 'padding-top: 8px;'; + break; + case types.position.left.value: + legendStyle = 'padding-right: 0px;'; + break; + case types.position.right.value: + legendStyle = 'padding-left: 0px;'; + break; + } + + var legendHtml = ''; + containerHtml = '
' + containerHtml + '
'; + html += '
'; + if ($scope.legendConfig.position === types.position.top.value || + $scope.legendConfig.position === types.position.left.value) { + html += legendHtml; + html += containerHtml; + } else { + html += containerHtml; + html += legendHtml; + } + html += '
'; + } else { + html += containerHtml; + } + + //TODO: + /*if (progressElement) { + progressScope.$destroy(); + progressScope = null; + + progressElement.remove(); + progressElement = null; + }*/ + + $element.html(html); + + var containerElement = $scope.displayLegend ? angular.element($element[0].querySelector('#widget-container')) : $element; + widgetContext.$container = $('#container', containerElement); + widgetContext.$containerParent = $(containerElement); + + $compile($element.contents())($scope); + + addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef + } + + function destroyWidgetElement() { + removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef + $element.html(''); + widgetContext.$container = null; + widgetContext.$containerParent = null; + } function initialize() { @@ -407,8 +454,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q onEditModeChanged(isEdit); }); - addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef - $scope.$watch(function () { return widget.row + ',' + widget.col + ',' + widget.config.mobileOrder; }, function () { @@ -439,10 +484,10 @@ export default function WidgetController($scope, $timeout, $window, $element, $q }); $scope.$on("$destroy", function () { - removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef onDestroy(); }); + configureWidgetElement(); var deferred = $q.defer(); if (!vm.useCustomDatasources) { createDefaultSubscription().then( @@ -465,6 +510,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q function reInit() { onDestroy(); + configureWidgetElement(); if (!vm.useCustomDatasources) { createDefaultSubscription().then( function success() { @@ -636,6 +682,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q handleWidgetException(e); } } + destroyWidgetElement(); } //TODO: widgets visibility diff --git a/ui/src/app/components/widget.directive.js b/ui/src/app/components/widget.directive.js index c92e5443bc..a0f1b83c48 100644 --- a/ui/src/app/components/widget.directive.js +++ b/ui/src/app/components/widget.directive.js @@ -28,7 +28,7 @@ export default angular.module('thingsboard.directives.widget', [thingsboardLegen .name; /*@ngInject*/ -function Widget($controller, $compile, types, widgetService) { +function Widget($controller, widgetService) { return { scope: true, link: function (scope, elem, attrs) { @@ -81,90 +81,9 @@ function Widget($controller, $compile, types, widgetService) { elem.addClass(widgetNamespace); - var html = '
' + - 'Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}' + - '
' + - '
' + - '' + - '
'; - - scope.displayLegend = angular.isDefined(widget.config.showLegend) ? - widget.config.showLegend : widget.type === types.widgetType.timeseries.value; - - - var containerHtml = '
' + widgetInfo.templateHtml + '
'; - if (scope.displayLegend) { - scope.legendConfig = widget.config.legendConfig || - { - position: types.position.bottom.value, - showMin: false, - showMax: false, - showAvg: widget.type === types.widgetType.timeseries.value, - showTotal: false - }; - scope.legendData = { - keys: [], - data: [] - }; - - var layoutType; - if (scope.legendConfig.position === types.position.top.value || - scope.legendConfig.position === types.position.bottom.value) { - layoutType = 'column'; - } else { - layoutType = 'row'; - } - - var legendStyle; - switch(scope.legendConfig.position) { - case types.position.top.value: - legendStyle = 'padding-bottom: 8px;'; - break; - case types.position.bottom.value: - legendStyle = 'padding-top: 8px;'; - break; - case types.position.left.value: - legendStyle = 'padding-right: 0px;'; - break; - case types.position.right.value: - legendStyle = 'padding-left: 0px;'; - break; - } - - var legendHtml = ''; - containerHtml = '
' + containerHtml + '
'; - html += '
'; - if (scope.legendConfig.position === types.position.top.value || - scope.legendConfig.position === types.position.left.value) { - html += legendHtml; - html += containerHtml; - } else { - html += containerHtml; - html += legendHtml; - } - html += '
'; - } else { - html += containerHtml; - } - - //TODO: - /*if (progressElement) { - progressScope.$destroy(); - progressScope = null; - - progressElement.remove(); - progressElement = null; - }*/ - - elem.html(html); - - var containerElement = scope.displayLegend ? angular.element(elem[0].querySelector('#widget-container')) : elem; - - $compile(elem.contents())(scope); - var widgetType = widgetService.getWidgetTypeFunction(widget.bundleAlias, widget.typeAlias, widget.isSystemType); - angular.extend(locals, {$scope: scope, $element: containerElement, widgetType: widgetType}); + angular.extend(locals, {$scope: scope, $element: elem, widgetInfo: widgetInfo, widgetType: widgetType}); widgetController = $controller('WidgetController', locals); diff --git a/ui/src/app/entity/aliases-entity-select-panel.controller.js b/ui/src/app/entity/aliases-entity-select-panel.controller.js index 8439269c1e..5365db74be 100644 --- a/ui/src/app/entity/aliases-entity-select-panel.controller.js +++ b/ui/src/app/entity/aliases-entity-select-panel.controller.js @@ -15,7 +15,7 @@ */ /*@ngInject*/ -export default function AliasesEntitySelectPanelController(mdPanelRef, $scope, types, aliasController, onEntityAliasesUpdate) { +export default function AliasesEntitySelectPanelController(mdPanelRef, $scope, $filter, types, aliasController, onEntityAliasesUpdate) { var vm = this; vm._mdPanelRef = mdPanelRef; @@ -31,13 +31,18 @@ export default function AliasesEntitySelectPanelController(mdPanelRef, $scope, t var aliasInfo = vm.aliasController.getInstantAliasInfo(aliasId); if (aliasInfo && !aliasInfo.resolveMultiple && aliasInfo.currentEntity) { vm.entityAliasesInfo[aliasId] = angular.copy(aliasInfo); + vm.entityAliasesInfo[aliasId].selectedId = aliasInfo.currentEntity.id; } } - function currentAliasEntityChanged(aliasId, currentEntity) { - vm.aliasController.updateCurrentAliasEntity(aliasId, currentEntity); - if (onEntityAliasesUpdate) { - onEntityAliasesUpdate(); + function currentAliasEntityChanged(aliasId, selectedId) { + var resolvedEntities = vm.entityAliasesInfo[aliasId].resolvedEntities; + var selected = $filter('filter')(resolvedEntities, {id: selectedId}); + if (selected && selected.length) { + vm.aliasController.updateCurrentAliasEntity(aliasId, selected[0]); + if (onEntityAliasesUpdate) { + onEntityAliasesUpdate(); + } } } diff --git a/ui/src/app/entity/aliases-entity-select-panel.tpl.html b/ui/src/app/entity/aliases-entity-select-panel.tpl.html index edc8e5ade9..d197d0bd9c 100644 --- a/ui/src/app/entity/aliases-entity-select-panel.tpl.html +++ b/ui/src/app/entity/aliases-entity-select-panel.tpl.html @@ -21,8 +21,8 @@
- - + + {{resolvedEntity.name}} diff --git a/ui/src/app/entity/aliases-entity-select.directive.js b/ui/src/app/entity/aliases-entity-select.directive.js index 7e174ef0d7..9a61311b1e 100644 --- a/ui/src/app/entity/aliases-entity-select.directive.js +++ b/ui/src/app/entity/aliases-entity-select.directive.js @@ -94,6 +94,14 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $ $mdPanel.open(config); } + scope.$on('entityAliasesChanged', function() { + scope.updateView(); + }); + + scope.$on('entityAliasResolved', function() { + scope.updateView(); + }); + scope.updateView = function () { updateDisplayValue(); } diff --git a/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js b/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js index b28a46bc48..bcf37dd825 100644 --- a/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js +++ b/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js @@ -22,7 +22,7 @@ import selectTargetLayoutTemplate from '../../dashboard/layouts/select-target-la /* eslint-enable import/no-unresolved, import/default */ /*@ngInject*/ -export default function AddWidgetToDashboardDialogController($scope, $mdDialog, $state, $q, $document, +export default function AddWidgetToDashboardDialogController($scope, $mdDialog, $state, $q, $document, dashboardUtils, types, itembuffer, dashboardService, entityId, entityType, entityName, widget) { var vm = this; @@ -126,15 +126,8 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog, targetDeviceAliases: {} }; aliasesInfo.datasourceAliases[0] = { - id: 1, alias: entityName, - filter: { - type: types.aliasFilterType.entityList.value, - stateEntity: false, - entityList: [entityId], - entityType: entityType, - resolveMultiple: false - } + filter: dashboardUtils.createSingleEntityFilter(entityType, entityId) }; itembuffer.addWidgetToDashboard(theDashboard, targetState, targetLayout, vm.widget, aliasesInfo, null, 48, null, -1, -1).then( function(theDashboard) { diff --git a/ui/src/app/entity/attribute/attribute-table.directive.js b/ui/src/app/entity/attribute/attribute-table.directive.js index da7697d5d1..62d2081853 100644 --- a/ui/src/app/entity/attribute/attribute-table.directive.js +++ b/ui/src/app/entity/attribute/attribute-table.directive.js @@ -26,10 +26,12 @@ import editAttributeValueTemplate from './edit-attribute-value.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ import EditAttributeValueController from './edit-attribute-value.controller'; +import AliasController from '../../api/alias-controller'; /*@ngInject*/ export default function AttributeTableDirective($compile, $templateCache, $rootScope, $q, $mdEditDialog, $mdDialog, - $document, $translate, $filter, utils, types, dashboardService, attributeService, widgetService) { + $mdUtil, $document, $translate, $filter, utils, types, dashboardUtils, + dashboardService, entityService, attributeService, widgetService) { var linker = function (scope, element, attrs) { @@ -246,15 +248,19 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS } scope.nextWidget = function() { - if (scope.widgetsCarousel.index < scope.widgetsList.length-1) { - scope.widgetsCarousel.index++; - } + $mdUtil.nextTick(function () { + if (scope.widgetsCarousel.index < scope.widgetsList.length - 1) { + scope.widgetsCarousel.index++; + } + }); } scope.prevWidget = function() { - if (scope.widgetsCarousel.index > 0) { - scope.widgetsCarousel.index--; - } + $mdUtil.nextTick(function () { + if (scope.widgetsCarousel.index > 0) { + scope.widgetsCarousel.index--; + } + }); } scope.enterWidgetMode = function() { @@ -281,23 +287,28 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS scope.firstBundle = true; scope.selectedWidgetsBundleAlias = types.systemBundleAlias.cards; - scope.aliasesInfo = { - entityAliases: { - '1': {alias: scope.entityName, entityType: scope.entityType, entityId: scope.entityId} - }, - entityAliasesInfo: { - '1': [ - {name: scope.entityName, entityType: scope.entityType, id: scope.entityId} - ] + var entityAlias = { + id: utils.guid(), + alias: scope.entityName, + filter: dashboardUtils.createSingleEntityFilter(scope.entityType, scope.entityId) + }; + var entitiAliases = {}; + entitiAliases[entityAlias.id] = entityAlias; + + var stateController = { + getStateParams: function() { + return {}; } }; + scope.aliasController = new AliasController(scope, $q, $filter, utils, + types, entityService, stateController, entitiAliases); var dataKeyType = scope.attributeScope === types.latestTelemetry ? types.dataKeyType.timeseries : types.dataKeyType.attribute; var datasource = { type: types.datasourceType.entity, - entityAliasId: '1', + entityAliasId: entityAlias.id, dataKeys: [] } var i = 0; diff --git a/ui/src/app/entity/attribute/attribute-table.tpl.html b/ui/src/app/entity/attribute/attribute-table.tpl.html index 1afd1bb452..2566a08161 100644 --- a/ui/src/app/entity/attribute/attribute-table.tpl.html +++ b/ui/src/app/entity/attribute/attribute-table.tpl.html @@ -156,7 +156,7 @@ rn-swipe-disabled="true">
  • +
    + +
    + entity.alias +
    + alias.entity-filter + alias.resolve-multiple +
    + +
    +
    +
    - +
    -
    - -
    - entity.alias - alias.entity-filter - -
    -
    -
    +
    {{$index + 1}}.
    @@ -54,13 +59,12 @@
    entity.alias-required
    -
    - - -
    + + @@ -73,18 +77,18 @@
    -
    - - - {{ 'entity.add-alias' | translate }} - - action.add - -
    + + + {{ 'entity.add-alias' | translate }} + + action.add + {{ 'action.save' | translate }} diff --git a/ui/src/app/entity/entity-filter.directive.js b/ui/src/app/entity/entity-filter.directive.js index beded8a162..0f9cf5c896 100644 --- a/ui/src/app/entity/entity-filter.directive.js +++ b/ui/src/app/entity/entity-filter.directive.js @@ -35,46 +35,16 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc scope.ngModelCtrl = ngModelCtrl; scope.types = types; - - /* scope.fetchEntities = function(searchText, limit) { - var deferred = $q.defer(); - entityService.getEntitiesByNameFilter(scope.entityType, searchText, limit).then(function success(result) { - if (result) { - deferred.resolve(result); - } else { - deferred.resolve([]); - } - }, function fail() { - deferred.reject(); - }); - return deferred.promise; - }*/ + scope.hideLabels = angular.isDefined(attrs.hideLabels); scope.updateValidity = function() { if (ngModelCtrl.$viewValue) { var value = ngModelCtrl.$viewValue; ngModelCtrl.$setValidity('filter', value.type ? true : false); - /*if (value.useFilter) { - ngModelCtrl.$setValidity('entityList', true); - if (angular.isDefined(value.entityNameFilter) && value.entityNameFilter.length > 0) { - ngModelCtrl.$setValidity('entityNameFilter', true); - valid = angular.isDefined(scope.model.matchingFilterEntity) && scope.model.matchingFilterEntity != null; - ngModelCtrl.$setValidity('entityNameFilterEntityMatch', valid); - } else { - ngModelCtrl.$setValidity('entityNameFilter', false); - } - } else { - ngModelCtrl.$setValidity('entityNameFilter', true); - ngModelCtrl.$setValidity('entityNameFilterDeviceMatch', true); - valid = angular.isDefined(value.entityList) && value.entityList.length > 0; - ngModelCtrl.$setValidity('entityList', valid); - }*/ - } } ngModelCtrl.$render = function () { - //destroyWatchers(); if (ngModelCtrl.$viewValue) { scope.model = angular.copy(ngModelCtrl.$viewValue); } else { @@ -83,28 +53,6 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc resolveMultiple: false } } - /* if (ngModelCtrl.$viewValue) { - var value = ngModelCtrl.$viewValue; - var model = scope.model; - model.useFilter = value.useFilter === true ? true: false; - model.entityList = []; - model.entityNameFilter = value.entityNameFilter || ''; - processEntityNameFilter(model.entityNameFilter).then( - function(entity) { - scope.model.matchingFilterEntity = entity; - if (value.entityList && value.entityList.length > 0) { - entityService.getEntities(scope.entityType, value.entityList).then(function (entities) { - model.entityList = entities; - updateMatchingEntity(); - initWatchers(); - }); - } else { - updateMatchingEntity(); - initWatchers(); - } - } - ) - }*/ } scope.$watch('model.resolveMultiple', function () { @@ -149,115 +97,6 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc }); } - /* function updateMatchingEntity() { - if (scope.model.useFilter) { - scope.model.matchingEntity = scope.model.matchingFilterEntity; - } else { - if (scope.model.entityList && scope.model.entityList.length > 0) { - scope.model.matchingEntity = scope.model.entityList[0]; - } else { - scope.model.matchingEntity = null; - } - } - } - - function processEntityNameFilter(entityNameFilter) { - var deferred = $q.defer(); - if (angular.isDefined(entityNameFilter) && entityNameFilter.length > 0) { - scope.fetchEntities(entityNameFilter, 1).then(function (entities) { - if (entities && entities.length > 0) { - deferred.resolve(entities[0]); - } else { - deferred.resolve(null); - } - }); - } else { - deferred.resolve(null); - } - return deferred.promise; - } - - function destroyWatchers() { - if (scope.entityTypeDeregistration) { - scope.entityTypeDeregistration(); - scope.entityTypeDeregistration = null; - } - if (scope.entityListDeregistration) { - scope.entityListDeregistration(); - scope.entityListDeregistration = null; - } - if (scope.useFilterDeregistration) { - scope.useFilterDeregistration(); - scope.useFilterDeregistration = null; - } - if (scope.entityNameFilterDeregistration) { - scope.entityNameFilterDeregistration(); - scope.entityNameFilterDeregistration = null; - } - if (scope.matchingEntityDeregistration) { - scope.matchingEntityDeregistration(); - scope.matchingEntityDeregistration = null; - } - } - - function initWatchers() { - - scope.entityTypeDeregistration = scope.$watch('entityType', function (newEntityType, prevEntityType) { - if (!angular.equals(newEntityType, prevEntityType)) { - scope.model.entityList = []; - scope.model.entityNameFilter = ''; - } - }); - - scope.entityListDeregistration = scope.$watch('model.entityList', function () { - if (ngModelCtrl.$viewValue) { - var value = ngModelCtrl.$viewValue; - value.entityList = []; - if (scope.model.entityList && scope.model.entityList.length > 0) { - for (var i=0;i -
    -
    - -
    -
    {{ types.aliasFilterType[model.type].name | translate }}
    - - - {{ 'alias.edit-entity-filter' | translate }} - - +
    +
    +
    {{ types.aliasFilterType[model.type].name | translate }}
    + + - edit + add + {{ 'alias.create-entity-filter' | translate }} -
    - - - add - - {{ 'alias.create-entity-filter' | translate }} - -
    -
    -
    - - -
    + + + {{ 'alias.edit-entity-filter' | translate }} + + + edit + + +
    +
    + + +
    - -
    \ No newline at end of file +
    diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js index 7f7f774dff..9b36c38d46 100644 --- a/ui/src/app/import-export/import-export.service.js +++ b/ui/src/app/import-export/import-export.service.js @@ -24,7 +24,7 @@ import entityAliasesTemplate from '../entity/entity-aliases.tpl.html'; /* eslint-disable no-undef, angular/window-service, angular/document-service */ /*@ngInject*/ -export default function ImportExport($log, $translate, $q, $mdDialog, $document, itembuffer, types, dashboardUtils, +export default function ImportExport($log, $translate, $q, $mdDialog, $document, itembuffer, utils, types, dashboardUtils, entityService, dashboardService, pluginService, ruleService, widgetService, toast) { @@ -415,6 +415,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, deferred.reject(); } else { var widget = widgetItem.widget; + widget = dashboardUtils.validateAndUpdateWidget(widget); var aliasesInfo = prepareAliasesInfo(widgetItem.aliasesInfo); var originalColumns = widgetItem.originalColumns; var originalSize = widgetItem.originalSize; @@ -425,22 +426,22 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, var entityAliases = {}; var datasourceAliasesMap = {}; var targetDeviceAliasesMap = {}; - var aliasId = 1; + var aliasId; var datasourceIndex; if (datasourceAliases) { for (datasourceIndex in datasourceAliases) { + aliasId = utils.guid(); datasourceAliasesMap[aliasId] = datasourceIndex; entityAliases[aliasId] = datasourceAliases[datasourceIndex]; entityAliases[aliasId].id = aliasId; - aliasId++; } } if (targetDeviceAliases) { for (datasourceIndex in targetDeviceAliases) { + aliasId = utils.guid(); targetDeviceAliasesMap[aliasId] = datasourceIndex; entityAliases[aliasId] = targetDeviceAliases[datasourceIndex]; entityAliases[aliasId].id = aliasId; - aliasId++; } } diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index 84c7097ed3..9c6695b96c 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -124,7 +124,7 @@ export default angular.module('thingsboard.locale', []) "create-entity-filter": "Create entity filter", "edit-entity-filter": "Edit entity filter", "entity-filter-required": "Entity filter is required.", - "resolve-multiple": "Multiple", + "resolve-multiple": "Resolve as multiple entities", "filter-type": "Filter type", "filter-type-required": "Filter type is required.", "use-state-entity": "Use state entity", diff --git a/ui/src/app/services/item-buffer.service.js b/ui/src/app/services/item-buffer.service.js index 309e53e59c..d29ad18cb0 100644 --- a/ui/src/app/services/item-buffer.service.js +++ b/ui/src/app/services/item-buffer.service.js @@ -298,11 +298,7 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) { } if (!newAliasId) { var newAliasName = createEntityAliasName(entityAliases, aliasInfo.alias); - newAliasId = 0; - for (aliasId in entityAliases) { - newAliasId = Math.max(newAliasId, aliasId); - } - newAliasId++; + newAliasId = utils.guid(); entityAliases[newAliasId] = {id: newAliasId, alias: newAliasName, filter: aliasInfo.filter}; } return newAliasId; From 9ef646765e4bf4e07396b55f70534ca37671cd36 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 9 Jun 2017 20:26:19 +0300 Subject: [PATCH 4/7] TB-61: Improve Aliases management. Implement Asset type and Device type alias filter. --- ui/src/app/api/alias-controller.js | 3 +- ui/src/app/api/entity.service.js | 245 ++++++++++++------ ui/src/app/api/subscription.js | 6 +- ui/src/app/common/dashboard-utils.service.js | 4 - ui/src/app/common/types.constant.js | 51 ++++ ui/src/app/common/utils.service.js | 26 +- ui/src/app/dashboard/add-widget.controller.js | 17 +- ui/src/app/dashboard/edit-widget.directive.js | 17 +- ...r.js => entity-alias-dialog.controller.js} | 69 ++--- ui/src/app/entity/entity-alias-dialog.scss | 27 ++ .../app/entity/entity-alias-dialog.tpl.html | 73 ++++++ .../app/entity/entity-aliases.controller.js | 133 +++++----- ui/src/app/entity/entity-aliases.scss | 12 + ui/src/app/entity/entity-aliases.tpl.html | 98 +++---- .../app/entity/entity-filter-dialog.tpl.html | 100 ------- .../entity/entity-filter-view.directive.js | 106 ++++++++ ui/src/app/entity/entity-filter-view.scss | 33 +++ ui/src/app/entity/entity-filter-view.tpl.html | 24 ++ ui/src/app/entity/entity-filter.directive.js | 100 ++++--- ui/src/app/entity/entity-filter.scss | 19 +- ui/src/app/entity/entity-filter.tpl.html | 104 +++++--- .../entity/entity-type-select.directive.js | 2 +- ui/src/app/entity/index.js | 4 + .../relation/relation-table.directive.js | 4 +- .../import-export/import-export.service.js | 5 - ui/src/app/locale/locale.constant.js | 55 +++- 26 files changed, 832 insertions(+), 505 deletions(-) rename ui/src/app/entity/{entity-filter-dialog.controller.js => entity-alias-dialog.controller.js} (59%) create mode 100644 ui/src/app/entity/entity-alias-dialog.scss create mode 100644 ui/src/app/entity/entity-alias-dialog.tpl.html delete mode 100644 ui/src/app/entity/entity-filter-dialog.tpl.html create mode 100644 ui/src/app/entity/entity-filter-view.directive.js create mode 100644 ui/src/app/entity/entity-filter-view.scss create mode 100644 ui/src/app/entity/entity-filter-view.tpl.html diff --git a/ui/src/app/api/alias-controller.js b/ui/src/app/api/alias-controller.js index b6d29a9599..60e8a31005 100644 --- a/ui/src/app/api/alias-controller.js +++ b/ui/src/app/api/alias-controller.js @@ -95,8 +95,7 @@ export default class AliasController { this.entityService.resolveAlias(entityAlias, this.stateController.getStateParams()).then( function success(aliasInfo) { aliasCtrl.resolvedAliases[aliasId] = aliasInfo; - if (entityAlias.filter.stateEntity) { - aliasInfo.stateEntity = true; + if (aliasInfo.stateEntity) { aliasCtrl.resolvedAliasesToStateEntities[aliasId] = aliasCtrl.stateController.getStateParams().entityId; } diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js index 91c8df3c2d..e647d9cfb2 100644 --- a/ui/src/app/api/entity.service.js +++ b/ui/src/app/api/entity.service.js @@ -31,6 +31,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device resolveAliasFilter: resolveAliasFilter, checkEntityAlias: checkEntityAlias, filterAliasByEntityTypes: filterAliasByEntityTypes, + getAliasFilterTypesByEntityTypes: getAliasFilterTypesByEntityTypes, getEntityKeys: getEntityKeys, createDatasourcesFromSubscriptionsInfo: createDatasourcesFromSubscriptionsInfo, getRelatedEntities: getRelatedEntities, @@ -223,17 +224,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device return promise; } - function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config, subType) { - var deferred = $q.defer(); - var pageLink = {limit: limit, textSearch: entityNameFilter}; + function getEntitiesByPageLink(entityType, pageLink, config, subType, data, deferred) { var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config, subType); if (promise) { promise.then( function success(result) { - if (result.data && result.data.length > 0) { - deferred.resolve(result.data); + data = data.concat(result.data); + if (result.hasNext) { + pageLink = result.nextPageLink; + getEntitiesByPageLink(entityType, pageLink, config, subType, data, deferred); } else { - deferred.resolve(null); + if (data && data.length > 0) { + deferred.resolve(data); + } else { + deferred.resolve(null); + } } }, function fail() { @@ -243,6 +248,34 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device } else { deferred.resolve(null); } + } + + function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config, subType) { + var deferred = $q.defer(); + var pageLink = {limit: limit, textSearch: entityNameFilter}; + if (limit == -1) { // all + var data = []; + pageLink.limit = 100; + getEntitiesByPageLink(entityType, pageLink, config, subType, data, deferred); + } else { + var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config, subType); + if (promise) { + promise.then( + function success(result) { + if (result.data && result.data.length > 0) { + deferred.resolve(result.data); + } else { + deferred.resolve(null); + } + }, + function fail() { + deferred.resolve(null); + } + ); + } else { + deferred.resolve(null); + } + } return deferred.promise; } @@ -261,11 +294,12 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device function resolveAlias(entityAlias, stateParams) { var deferred = $q.defer(); var filter = entityAlias.filter; - resolveAliasFilter(filter, stateParams, 100).then( + resolveAliasFilter(filter, stateParams, -1).then( function (result) { var entities = result.entities; var aliasInfo = { alias: entityAlias.alias, + stateEntity: result.stateEntity, resolveMultiple: filter.resolveMultiple }; var resolvedEntities = entitiesToEntitiesInfo(entities); @@ -291,39 +325,68 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device }; switch (filter.type) { case types.aliasFilterType.entityList.value: - if (filter.stateEntity) { - result.stateEntity = true; - if (stateParams && stateParams.entityId) { - getEntity(stateParams.entityId.entityType, stateParams.entityId.id).then( - function success(entity) { - result.entities = [entity]; - deferred.resolve(result); - }, - function fail() { - deferred.reject(); - } - ); - } else { - deferred.resolve(result); + getEntities(filter.entityType, filter.entityList).then( + function success(entities) { + if (entities && entities.length) { + result.entities = entities; + deferred.resolve(result); + } else { + deferred.reject(); + } + }, + function fail() { + deferred.reject(); } - } else { - getEntities(filter.entityType, filter.entityList).then( - function success(entities) { - if (entities && entities.length) { - result.entities = entities; - deferred.resolve(result); - } else { - deferred.reject(); - } + ); + break; + case types.aliasFilterType.entityName.value: + getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems).then( + function success(entities) { + if (entities && entities.length) { + result.entities = entities; + deferred.resolve(result); + } else { + deferred.reject(); + } + }, + function fail() { + deferred.reject(); + } + ); + break; + case types.aliasFilterType.stateEntity.value: + result.stateEntity = true; + if (stateParams && stateParams.entityId) { + getEntity(stateParams.entityId.entityType, stateParams.entityId.id).then( + function success(entity) { + result.entities = [entity]; + deferred.resolve(result); }, function fail() { deferred.reject(); } ); + } else { + deferred.resolve(result); } break; - case types.aliasFilterType.entityName.value: - getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems).then( + case types.aliasFilterType.assetType.value: + getEntitiesByNameFilter(types.entityType.asset, filter.assetNameFilter, maxItems, null, filter.assetType).then( + function success(entities) { + if (entities && entities.length) { + result.entities = entities; + deferred.resolve(result); + } else { + deferred.reject(); + } + }, + function fail() { + deferred.reject(); + } + ); + break; + case types.aliasFilterType.deviceType.value: + getEntitiesByNameFilter(types.entityType.device, filter.deviceNameFilter, maxItems, null, filter.deviceType).then( function success(entities) { if (entities && entities.length) { result.entities = entities; @@ -337,27 +400,81 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device } ); break; - //TODO: + + //TODO: Alias filter } return deferred.promise; } function filterAliasByEntityTypes(entityAlias, entityTypes) { var filter = entityAlias.filter; - switch (filter.type) { - case types.aliasFilterType.entityList.value: - if (filter.stateEntity) { - return true; - } else { + if (filterAliasFilterTypeByEntityTypes(filter.type, entityTypes)) { + switch (filter.type) { + case types.aliasFilterType.entityList.value: return entityTypes.indexOf(filter.entityType) > -1 ? true : false; - } + case types.aliasFilterType.entityName.value: + return entityTypes.indexOf(filter.entityType) > -1 ? true : false; + case types.aliasFilterType.stateEntity.value: + return true; + case types.aliasFilterType.assetType.value: + return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false; + case types.aliasFilterType.deviceType.value: + return entityTypes.indexOf(types.entityType.device) > -1 ? true : false; + } + } + //TODO: Alias filter + return false; + } + + function filterAliasFilterTypeByEntityType(aliasFilterType, entityType) { + switch (aliasFilterType) { + case types.aliasFilterType.entityList.value: + return true; case types.aliasFilterType.entityName.value: - return entityTypes.indexOf(filter.entityType) > -1 ? true : false; + return true; + case types.aliasFilterType.stateEntity.value: + return true; + case types.aliasFilterType.assetType.value: + return entityType === types.entityType.asset; + case types.aliasFilterType.deviceType.value: + return entityType === types.entityType.device; + case types.aliasFilterType.relationsQuery.value: + return true; + case types.aliasFilterType.assetSearchQuery.value: + return entityType === types.entityType.asset; + case types.aliasFilterType.deviceSearchQuery.value: + return entityType === types.entityType.device; } - //TODO: return false; } + function filterAliasFilterTypeByEntityTypes(aliasFilterType, entityTypes) { + if (!entityTypes || !entityTypes.length) { + return true; + } + var valid = false; + entityTypes.forEach(function(entityType) { + valid = valid || filterAliasFilterTypeByEntityType(aliasFilterType, entityType); + }); + return valid; + } + + function getAliasFilterTypesByEntityTypes(entityTypes) { + var allAliasFilterTypes = types.aliasFilterType; + if (!entityTypes || !entityTypes.length) { + return allAliasFilterTypes; + } + var result = {}; + for (var type in allAliasFilterTypes) { + var aliasFilterType = allAliasFilterTypes[type]; + if (filterAliasFilterTypeByEntityTypes(aliasFilterType.value, entityTypes)) { + result[type] = aliasFilterType; + } + } + return result; + } + + function checkEntityAlias(entityAlias) { var deferred = $q.defer(); resolveAliasFilter(entityAlias.filter, null, 1).then( @@ -380,50 +497,6 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device return deferred.promise; } - /*function processEntityAlias(index, aliasIds, entityAliases, stateParams, resolution, deferred) { - if (index < aliasIds.length) { - var aliasId = aliasIds[index]; - var entityAlias = entityAliases[aliasId]; - var alias = entityAlias.alias; - var filter = entityAlias.filter; - resolveAliasFilter(filter, stateParams).then( - function (entities) { - if (entities && entities.length) { - var entity = entities[0]; - var resolvedAlias = {alias: alias, entityType: entity.id.entityType, entityId: entity.id.id}; - resolution.aliasesInfo.entityAliases[aliasId] = resolvedAlias; - resolution.aliasesInfo.entityAliasesInfo[aliasId] = entitiesToEntitiesInfo(entities); - index++; - processEntityAlias(index, aliasIds, entityAliases, stateParams, resolution, deferred); - } else { - if (!resolution.error) { - resolution.error = 'dashboard.invalid-aliases-config'; - } - index++; - processEntityAlias(index, aliasIds, entityAliases, stateParams, resolution, deferred); - } - } - ); - } else { - deferred.resolve(resolution); - } - }*/ - - /*function processEntityAliases(entityAliases, stateParams) { - var deferred = $q.defer(); - var resolution = { - aliasesInfo: {} - }; - var aliasIds = []; - if (entityAliases) { - for (var aliasId in entityAliases) { - aliasIds.push(aliasId); - } - } - processEntityAlias(0, aliasIds, entityAliases, stateParams, resolution, deferred); - return deferred.promise; - }*/ - function getEntityKeys(entityType, entityId, query, type) { var deferred = $q.defer(); var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/keys/'; @@ -889,4 +962,4 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device } } -} \ No newline at end of file +} diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js index 4cb382ceab..1d4eb301c4 100644 --- a/ui/src/app/api/subscription.js +++ b/ui/src/app/api/subscription.js @@ -258,17 +258,17 @@ export default class Subscription { } else { subscription.rpcEnabled = subscription.ctx.$scope.widgetEditMode ? true : false; } - subscription.callbacks.rpcStateChanged(this); + subscription.callbacks.rpcStateChanged(subscription); deferred.resolve(); } else { subscription.rpcEnabled = false; - subscription.callbacks.rpcStateChanged(this); + subscription.callbacks.rpcStateChanged(subscription); deferred.resolve(); } }, function fail () { subscription.rpcEnabled = false; - subscription.callbacks.rpcStateChanged(this); + subscription.callbacks.rpcStateChanged(subscription); deferred.resolve(); } ); diff --git a/ui/src/app/common/dashboard-utils.service.js b/ui/src/app/common/dashboard-utils.service.js index fe8aa93f7b..915d5501eb 100644 --- a/ui/src/app/common/dashboard-utils.service.js +++ b/ui/src/app/common/dashboard-utils.service.js @@ -110,14 +110,12 @@ function DashboardUtils(types, utils, timeService) { deviceAlias.deviceFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value; if (entityAlias.filter.type == types.aliasFilterType.entityList.value) { entityAlias.filter.entityList = deviceAlias.deviceFilter.deviceList; - entityAlias.filter.stateEntity = false; } else { entityAlias.filter.entityNameFilter = deviceAlias.deviceFilter.deviceNameFilter; } } else { entityAlias.filter.type = types.aliasFilterType.entityList.value; entityAlias.filter.entityList = [deviceAlias.deviceId]; - entityAlias.filter.stateEntity = false; } return entityAlias; } @@ -132,7 +130,6 @@ function DashboardUtils(types, utils, timeService) { } if (entityAlias.filter.type == types.aliasFilterType.entityList.value) { entityAlias.filter.entityList = entityAlias.entityFilter.entityList; - entityAlias.filter.stateEntity = false; } else { entityAlias.filter.entityNameFilter = entityAlias.entityFilter.entityNameFilter; } @@ -342,7 +339,6 @@ function DashboardUtils(types, utils, timeService) { function createSingleEntityFilter(entityType, entityId) { return { type: types.aliasFilterType.entityList.value, - stateEntity: false, entityList: [entityId], entityType: entityType, resolveMultiple: false diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 40b66391fd..522abff190 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -74,6 +74,10 @@ export default angular.module('thingsboard.types', []) value: 'entityName', name: 'alias.filter-type-entity-name' }, + stateEntity: { + value: 'stateEntity', + name: 'alias.filter-type-state-entity' + }, assetType: { value: 'assetType', name: 'alias.filter-type-asset-type' @@ -139,6 +143,53 @@ export default angular.module('thingsboard.types', []) dashboard: "DASHBOARD", alarm: "ALARM" }, + entityTypeTranslations: { + "DEVICE": { + type: 'entity.type-device', + list: 'entity.list-of-devices', + nameStartsWith: 'entity.device-name-starts-with' + }, + "ASSET": { + type: 'entity.type-asset', + list: 'entity.list-of-assets', + nameStartsWith: 'entity.asset-name-starts-with' + }, + "RULE": { + type: 'entity.type-rule', + list: 'entity.list-of-rules', + nameStartsWith: 'entity.rule-name-starts-with' + }, + "PLUGIN": { + type: 'entity.type-plugin', + list: 'entity.list-of-plugins', + nameStartsWith: 'entity.plugin-name-starts-with' + }, + "TENANT": { + type: 'entity.type-tenant', + list: 'entity.list-of-tenants', + nameStartsWith: 'entity.tenant-name-starts-with' + }, + "CUSTOMER": { + type: 'entity.type-customer', + list: 'entity.list-of-customers', + nameStartsWith: 'entity.customer-name-starts-with' + }, + "USER": { + type: 'entity.type-user', + list: 'entity.list-of-users', + nameStartsWith: 'entity.user-name-starts-with' + }, + "DASHBOARD": { + type: 'entity.type-dashboard', + list: 'entity.list-of-dashboards', + nameStartsWith: 'entity.dashboard-name-starts-with' + }, + "ALARM": { + type: 'entity.type-alarm', + list: 'entity.list-of-alarms', + nameStartsWith: 'entity.alarm-name-starts-with' + } + }, entitySearchDirection: { from: "FROM", to: "TO" diff --git a/ui/src/app/common/utils.service.js b/ui/src/app/common/utils.service.js index 8bf76c4f0f..8d2e565f4a 100644 --- a/ui/src/app/common/utils.service.js +++ b/ui/src/app/common/utils.service.js @@ -109,8 +109,7 @@ function Utils($mdColorPalette, $rootScope, $window, types) { cleanCopy: cleanCopy, isLocalUrl: isLocalUrl, validateDatasources: validateDatasources, - createKey: createKey, - entityTypeName: entityTypeName + createKey: createKey } return service; @@ -358,27 +357,4 @@ function Utils($mdColorPalette, $rootScope, $window, types) { return dataKey; } - function entityTypeName (type) { - switch (type) { - case types.entityType.device: - return 'entity.type-device'; - case types.entityType.asset: - return 'entity.type-asset'; - case types.entityType.rule: - return 'entity.type-rule'; - case types.entityType.plugin: - return 'entity.type-plugin'; - case types.entityType.tenant: - return 'entity.type-tenant'; - case types.entityType.customer: - return 'entity.type-customer'; - case types.entityType.user: - return 'entity.type-user'; - case types.entityType.dashboard: - return 'entity.type-dashboard'; - case types.entityType.alarm: - return 'entity.type-alarm'; - } - } - } diff --git a/ui/src/app/dashboard/add-widget.controller.js b/ui/src/app/dashboard/add-widget.controller.js index 586fb46fc4..a9a68c74af 100644 --- a/ui/src/app/dashboard/add-widget.controller.js +++ b/ui/src/app/dashboard/add-widget.controller.js @@ -15,7 +15,7 @@ */ /* eslint-disable import/no-unresolved, import/default */ -import entityAliasesTemplate from '../entity/entity-aliases.tpl.html'; +import entityAliasDialogTemplate from '../entity/entity-alias-dialog.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ @@ -130,17 +130,14 @@ export default function AddWidgetController($scope, widgetService, entityService var singleEntityAlias = {id: null, alias: alias, filter: {}}; $mdDialog.show({ - controller: 'EntityAliasesController', + controller: 'EntityAliasDialogController', controllerAs: 'vm', - templateUrl: entityAliasesTemplate, + templateUrl: entityAliasDialogTemplate, locals: { - config: { - entityAliases: angular.copy(vm.dashboard.configuration.entityAliases), - widgets: null, - isSingleEntityAlias: true, - singleEntityAlias: singleEntityAlias, - allowedEntityTypes: allowedEntityTypes - } + isAdd: true, + allowedEntityTypes: allowedEntityTypes, + entityAliases: vm.dashboard.configuration.entityAliases, + alias: singleEntityAlias }, parent: angular.element($document[0].body), fullscreen: true, diff --git a/ui/src/app/dashboard/edit-widget.directive.js b/ui/src/app/dashboard/edit-widget.directive.js index 4bd4e3dfff..5b788cc82c 100644 --- a/ui/src/app/dashboard/edit-widget.directive.js +++ b/ui/src/app/dashboard/edit-widget.directive.js @@ -15,7 +15,7 @@ */ /* eslint-disable import/no-unresolved, import/default */ -import entityAliasesTemplate from '../entity/entity-aliases.tpl.html'; +import entityAliasDialogTemplate from '../entity/entity-alias-dialog.tpl.html'; import editWidgetTemplate from './edit-widget.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ @@ -98,17 +98,14 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid var singleEntityAlias = {id: null, alias: alias, filter: {}}; $mdDialog.show({ - controller: 'EntityAliasesController', + controller: 'EntityAliasDialogController', controllerAs: 'vm', - templateUrl: entityAliasesTemplate, + templateUrl: entityAliasDialogTemplate, locals: { - config: { - entityAliases: angular.copy(scope.dashboard.configuration.entityAliases), - widgets: null, - isSingleEntityAlias: true, - singleEntityAlias: singleEntityAlias, - allowedEntityTypes: allowedEntityTypes - } + isAdd: true, + allowedEntityTypes: allowedEntityTypes, + entityAliases: scope.dashboard.configuration.entityAliases, + alias: singleEntityAlias }, parent: angular.element($document[0].body), fullscreen: true, diff --git a/ui/src/app/entity/entity-filter-dialog.controller.js b/ui/src/app/entity/entity-alias-dialog.controller.js similarity index 59% rename from ui/src/app/entity/entity-filter-dialog.controller.js rename to ui/src/app/entity/entity-alias-dialog.controller.js index 17a19d61d2..a5b221adf7 100644 --- a/ui/src/app/entity/entity-filter-dialog.controller.js +++ b/ui/src/app/entity/entity-alias-dialog.controller.js @@ -14,22 +14,48 @@ * limitations under the License. */ +import './entity-alias-dialog.scss'; + /*@ngInject*/ -export default function EntityFilterDialogController($scope, $mdDialog, $q, entityService, types, isAdd, allowedEntityTypes, filter) { +export default function EntityAliasDialogController($scope, $mdDialog, $q, $filter, utils, entityService, types, isAdd, allowedEntityTypes, entityAliases, alias) { var vm = this; vm.types = types; vm.isAdd = isAdd; vm.allowedEntityTypes = allowedEntityTypes; - vm.filter = filter; + if (angular.isArray(entityAliases)) { + vm.entityAliases = entityAliases; + } else { + vm.entityAliases = []; + for (var aliasId in entityAliases) { + vm.entityAliases.push(entityAliases[aliasId]); + } + } + if (vm.isAdd && !alias) { + vm.alias = { + alias: '', + filter: { + resolveMultiple: false + } + }; + } else { + vm.alias = alias; + } vm.cancel = cancel; vm.save = save; - $scope.$watch('vm.filter.type', function (newType, prevType) { - if (newType && newType != prevType) { - updateFilter(); + $scope.$watch('vm.alias.alias', function (newAlias) { + if (newAlias) { + var valid = true; + var result = $filter('filter')(vm.entityAliases, {alias: newAlias}, true); + if (result && result.length) { + if (vm.isAdd || vm.alias.id != result[0].id) { + valid = false; + } + } + $scope.theForm.aliasName.$setValidity('duplicateAliasName', valid); } }); @@ -39,32 +65,13 @@ export default function EntityFilterDialogController($scope, $mdDialog, $q, enti } }); - function updateFilter() { - var filter = {}; - filter.type = vm.filter.type; - filter.resolveMultiple = vm.filter.resolveMultiple; - switch (filter.type) { - case types.aliasFilterType.entityList.value: - filter.entityType = null; - filter.entityList = []; - filter.stateEntity = false; - break; - case types.aliasFilterType.entityName.value: - filter.entityType = null; - filter.entityNameFilter = ''; - break; - //TODO: - } - vm.filter = filter; - } - function validate() { var deferred = $q.defer(); var validationResult = { entity: null, stateEntity: false } - entityService.resolveAliasFilter(vm.filter, null, 1).then( + entityService.resolveAliasFilter(vm.alias.filter, null, 1).then( function success(result) { validationResult.stateEntity = result.stateEntity; var entities = result.entities; @@ -87,12 +94,11 @@ export default function EntityFilterDialogController($scope, $mdDialog, $q, enti function save() { $scope.theForm.$setPristine(); validate().then( - function success(validationResult) { - $mdDialog.hide({ - filter: vm.filter, - entity: validationResult.entity, - stateEntity: validationResult.stateEntity - }); + function success() { + if (vm.isAdd) { + vm.alias.id = utils.guid(); + } + $mdDialog.hide(vm.alias); }, function fail() { $scope.theForm.$setValidity('entityFilter', false); @@ -101,4 +107,3 @@ export default function EntityFilterDialogController($scope, $mdDialog, $q, enti } } - diff --git a/ui/src/app/entity/entity-alias-dialog.scss b/ui/src/app/entity/entity-alias-dialog.scss new file mode 100644 index 0000000000..96df6fd1e8 --- /dev/null +++ b/ui/src/app/entity/entity-alias-dialog.scss @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.tb-entity-alias-dialog { + .tb-resolve-multiple-switch { + padding-left: 10px; + .resolve-multiple-switch { + margin: 0; + } + .resolve-multiple-label { + margin: 5px 0; + } + } +} \ No newline at end of file diff --git a/ui/src/app/entity/entity-alias-dialog.tpl.html b/ui/src/app/entity/entity-alias-dialog.tpl.html new file mode 100644 index 0000000000..b78a795e61 --- /dev/null +++ b/ui/src/app/entity/entity-alias-dialog.tpl.html @@ -0,0 +1,73 @@ + + +
    + +
    +

    {{ (vm.isAdd ? 'alias.add' : 'alias.edit') | translate }}

    + + + + +
    +
    + + + +
    +
    +
    +
    + + + +
    +
    alias.name-required
    +
    alias.duplicate-alias
    +
    +
    +
    + + + +
    +
    + + + +
    +
    +
    +
    + + + + {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }} + + {{ 'action.cancel' | translate }} + +
    +
    diff --git a/ui/src/app/entity/entity-aliases.controller.js b/ui/src/app/entity/entity-aliases.controller.js index 50bbfd9a86..b72b9eca6e 100644 --- a/ui/src/app/entity/entity-aliases.controller.js +++ b/ui/src/app/entity/entity-aliases.controller.js @@ -15,22 +15,27 @@ */ import './entity-aliases.scss'; +/* eslint-disable import/no-unresolved, import/default */ + +import entityAliasDialogTemplate from './entity-alias-dialog.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + /*@ngInject*/ export default function EntityAliasesController(utils, entityService, toast, $scope, $mdDialog, $document, $q, $translate, types, config) { var vm = this; - vm.isSingleEntityAlias = config.isSingleEntityAlias; - vm.singleEntityAlias = config.singleEntityAlias; + vm.types = types; vm.entityAliases = []; vm.title = config.customTitle ? config.customTitle : 'entity.aliases'; vm.disableAdd = config.disableAdd; vm.aliasToWidgetsMap = {}; vm.allowedEntityTypes = config.allowedEntityTypes; - vm.onFilterEntityChanged = onFilterEntityChanged; vm.addAlias = addAlias; + vm.editAlias = editAlias; vm.removeAlias = removeAlias; vm.cancel = cancel; @@ -79,42 +84,61 @@ export default function EntityAliasesController(utils, entityService, toast, $sc } } - if (vm.isSingleEntityAlias) { - checkEntityAlias(vm.singleEntityAlias); - } - for (aliasId in config.entityAliases) { var entityAlias = config.entityAliases[aliasId]; - var result = {id: aliasId, alias: entityAlias.alias, filter: entityAlias.filter, changed: true}; - checkEntityAlias(result); + var filter = entityAlias.filter; + if (!filter) { + filter = { + resolveMultiple: false + }; + } + if (!filter.resolveMultiple) { + filter.resolveMultiple = false; + } + var result = {id: aliasId, alias: entityAlias.alias, filter: filter}; vm.entityAliases.push(result); } } - function checkEntityAlias(entityAlias) { - if (!entityAlias.filter || entityAlias.filter == null) { - entityAlias.filter = {}; - } + function addAlias($event) { + openAliasDialog($event); } - function onFilterEntityChanged(entity, stateEntity, entityAlias) { - if (entityAlias) { - if (!entityAlias.alias || entityAlias.alias.length == 0) { - entityAlias.changed = false; - } - if (!entityAlias.changed && entityAlias.filter && entityAlias.filter.type) { - if (stateEntity) { - entityAlias.alias = $translate.instant('alias.state-entity'); - } else { - entityAlias.alias = entity.name; - } - } - } + function editAlias($event, entityAlias) { + openAliasDialog($event, entityAlias); } - function addAlias() { - var entityAlias = {id: utils.guid(), alias: '', filter: {}, changed: false}; - vm.entityAliases.push(entityAlias); + function openAliasDialog($event, entityAlias) { + var isAdd = entityAlias ? false : true; + var aliasIndex; + if (!isAdd) { + aliasIndex = vm.entityAliases.indexOf(entityAlias); + } + $mdDialog.show({ + controller: 'EntityAliasDialogController', + controllerAs: 'vm', + templateUrl: entityAliasDialogTemplate, + locals: { + isAdd: isAdd, + allowedEntityTypes: vm.allowedEntityTypes, + entityAliases: vm.entityAliases, + alias: isAdd ? null : angular.copy(entityAlias) + }, + parent: angular.element($document[0].body), + fullscreen: true, + skipHide: true, + targetEvent: $event + }).then(function (alias) { + if (isAdd) { + vm.entityAliases.push(alias); + } else { + vm.entityAliases[aliasIndex] = alias; + } + if ($scope.theForm) { + $scope.theForm.$setDirty(); + } + }, function () { + }); } function removeAlias($event, entityAlias) { @@ -157,43 +181,30 @@ export default function EntityAliasesController(utils, entityService, toast, $sc var uniqueAliasList = {}; var valid = true; - var aliasId; - var alias; - var i; - - if (vm.isSingleEntityAlias) { - if (!vm.singleEntityAlias.id) { - vm.singleEntityAlias.id = utils.guid(); - } - for (i = 0; i < vm.entityAliases.length; i ++) { - alias = vm.entityAliases[i].alias; - if (alias === vm.singleEntityAlias.alias) { - valid = false; - break; - } - } - } else { - for (i = 0; i < vm.entityAliases.length; i++) { - aliasId = vm.entityAliases[i].id; - alias = vm.entityAliases[i].alias; - if (!uniqueAliasList[alias]) { - uniqueAliasList[alias] = alias; - entityAliases[aliasId] = {id: aliasId, alias: alias, filter: vm.entityAliases[i].filter}; - } else { - valid = false; - break; - } + var message, aliasId, alias, filter; + + for (var i = 0; i < vm.entityAliases.length; i++) { + aliasId = vm.entityAliases[i].id; + alias = vm.entityAliases[i].alias; + filter = vm.entityAliases[i].filter; + if (uniqueAliasList[alias]) { + valid = false; + message = $translate.instant('entity.duplicate-alias-error', {alias: alias}); + break; + } else if (!filter || !filter.type) { + valid = false; + message = $translate.instant('entity.missing-entity-filter-error', {alias: alias}); + break; + } else { + uniqueAliasList[alias] = alias; + entityAliases[aliasId] = {id: aliasId, alias: alias, filter: filter}; } } if (valid) { $scope.theForm.$setPristine(); - if (vm.isSingleEntityAlias) { - $mdDialog.hide(vm.singleEntityAlias); - } else { - $mdDialog.hide(entityAliases); - } + $mdDialog.hide(entityAliases); } else { - toast.showError($translate.instant('entity.duplicate-alias-error', {alias: alias})); + toast.showError(message); } } diff --git a/ui/src/app/entity/entity-aliases.scss b/ui/src/app/entity/entity-aliases.scss index 4180bcedfb..9803be1cce 100644 --- a/ui/src/app/entity/entity-aliases.scss +++ b/ui/src/app/entity/entity-aliases.scss @@ -17,6 +17,7 @@ .tb-aliases-dialog { .md-dialog-content { padding-bottom: 0px; + padding-top: 0px; } .tb-aliases-header { min-height: 40px; @@ -33,5 +34,16 @@ md-input-container { margin: 0px; } + .tb-resolve-multiple-switch { + padding-left: 10px; + .resolve-multiple-switch { + margin: 0; + } + } + .md-button { + &.md-icon-button { + margin: 0px; + } + } } } diff --git a/ui/src/app/entity/entity-aliases.tpl.html b/ui/src/app/entity/entity-aliases.tpl.html index 4fd33d942b..40010a2a97 100644 --- a/ui/src/app/entity/entity-aliases.tpl.html +++ b/ui/src/app/entity/entity-aliases.tpl.html @@ -19,7 +19,7 @@
    -

    {{ vm.isSingleEntityAlias ? ('entity.configure-alias' | translate:vm.singleEntityAlias ) : (vm.title | translate) }}

    +

    {{ vm.title | translate }}

    @@ -28,66 +28,72 @@ -
    +
    - entity.alias -
    - alias.entity-filter - alias.resolve-multiple -
    - + alias.name + alias.entity-filter + alias.resolve-multiple +
    - +
    -
    - - -
    -
    -
    - {{$index + 1}}. -
    - - -
    -
    entity.alias-required
    -
    -
    - - - - - {{ 'entity.remove-alias' | translate }} - - - close - - -
    -
    +
    + {{$index + 1}}. + + + +
    +
    entity.alias-required
    +
    +
    + + +
    + + +
    + + + {{ 'alias.edit' | translate }} + + + edit + + + + + {{ 'entity.remove-alias' | translate }} + + + close + + +
    - + aria-label="{{ 'alias.add' | translate }}"> - {{ 'entity.add-alias' | translate }} + {{ 'alias.add' | translate }} - action.add + alias.add diff --git a/ui/src/app/entity/entity-filter-dialog.tpl.html b/ui/src/app/entity/entity-filter-dialog.tpl.html deleted file mode 100644 index 4343b391fc..0000000000 --- a/ui/src/app/entity/entity-filter-dialog.tpl.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - -
    -

    {{ (vm.isAdd ? 'alias.create-entity-filter' : 'alias.edit-entity-filter') | translate }}

    - - - - -
    -
    - - - -
    -
    -
    - - - - - {{type.name | translate}} - - -
    -
    alias.filter-type-required
    -
    -
    -
    - {{ 'alias.use-state-entity' | translate }} - - - - - -
    -
    - - - - - -
    -
    entity.entity-name-filter-required
    -
    - -
    -
    - -
    -
    -
    -
    - - - - {{ 'action.save' | translate }} - - {{ 'action.cancel' | translate }} - - -
    \ No newline at end of file diff --git a/ui/src/app/entity/entity-filter-view.directive.js b/ui/src/app/entity/entity-filter-view.directive.js new file mode 100644 index 0000000000..6a358b2e16 --- /dev/null +++ b/ui/src/app/entity/entity-filter-view.directive.js @@ -0,0 +1,106 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable import/no-unresolved, import/default */ + +import entityFilterViewTemplate from './entity-filter-view.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +import './entity-filter-view.scss'; + +/*@ngInject*/ +export default function EntityFilterViewDirective($compile, $templateCache, $q, $document, $mdDialog, $translate, types/*, entityService*/) { + + var linker = function (scope, element, attrs, ngModelCtrl) { + + var template = $templateCache.get(entityFilterViewTemplate); + element.html(template); + + scope.ngModelCtrl = ngModelCtrl; + scope.types = types; + scope.filterDisplayValue = ''; + + scope.$watch('filter', function () { + scope.updateDisplayValue(); + }); + + scope.updateDisplayValue = function() { + if (scope.filter && scope.filter.type) { + var entityType; + var prefix; + switch (scope.filter.type) { + case types.aliasFilterType.entityList.value: + entityType = scope.filter.entityType; + var count = scope.filter.entityList.length; + scope.filterDisplayValue = $translate.instant(types.entityTypeTranslations[entityType].list, {count: count}, 'messageformat'); + break; + case types.aliasFilterType.entityName.value: + entityType = scope.filter.entityType; + prefix = scope.filter.entityNameFilter; + scope.filterDisplayValue = $translate.instant(types.entityTypeTranslations[entityType].nameStartsWith, {prefix: prefix}); + break; + case types.aliasFilterType.stateEntity.value: + scope.filterDisplayValue = $translate.instant('alias.filter-type-state-entity-description'); + break; + case types.aliasFilterType.assetType.value: + var assetType = scope.filter.assetType; + prefix = scope.filter.assetNameFilter; + if (prefix && prefix.length) { + scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-type-and-name-description', {assetType: assetType, prefix: prefix}); + } else { + scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-type-description', {assetType: assetType}); + } + break; + case types.aliasFilterType.deviceType.value: + var deviceType = scope.filter.deviceType; + prefix = scope.filter.deviceNameFilter; + if (prefix && prefix.length) { + scope.filterDisplayValue = $translate.instant('alias.filter-type-device-type-and-name-description', {deviceType: deviceType, prefix: prefix}); + } else { + scope.filterDisplayValue = $translate.instant('alias.filter-type-device-type-description', {deviceType: deviceType}); + } + break; + //TODO: Alias filter + default: + scope.filterDisplayValue = scope.filter.type; + break; + } + } else { + scope.filterDisplayValue = ''; + } + } + + ngModelCtrl.$render = function () { + if (ngModelCtrl.$viewValue) { + scope.filter = ngModelCtrl.$viewValue; + } else { + scope.filter = null; + } + } + + $compile(element.contents())(scope); + + } + + return { + restrict: "E", + require: "^ngModel", + link: linker, + scope: true + }; + +} diff --git a/ui/src/app/entity/entity-filter-view.scss b/ui/src/app/entity/entity-filter-view.scss new file mode 100644 index 0000000000..437f296f6d --- /dev/null +++ b/ui/src/app/entity/entity-filter-view.scss @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.tb-entity-filter-view { + .entity-filter-empty { + color: rgba(221, 44, 0, 0.87); + font-size: 14px; + line-height: 16px; + } + .entity-filter-type { + font-size: 14px; + line-height: 16px; + color: rgba(0, 0, 0, 0.570588); + } + .entity-filter-value { + font-size: 14px; + line-height: 16px; + color: rgba(0, 0, 0, 0.570588); + } +} \ No newline at end of file diff --git a/ui/src/app/entity/entity-filter-view.tpl.html b/ui/src/app/entity/entity-filter-view.tpl.html new file mode 100644 index 0000000000..84c540555c --- /dev/null +++ b/ui/src/app/entity/entity-filter-view.tpl.html @@ -0,0 +1,24 @@ + + +
    +
    alias.no-entity-filter-specified
    +
    +
    {{ filterDisplayValue }}
    +
    +
    diff --git a/ui/src/app/entity/entity-filter.directive.js b/ui/src/app/entity/entity-filter.directive.js index 0f9cf5c896..9566716d3e 100644 --- a/ui/src/app/entity/entity-filter.directive.js +++ b/ui/src/app/entity/entity-filter.directive.js @@ -17,16 +17,13 @@ /* eslint-disable import/no-unresolved, import/default */ import entityFilterTemplate from './entity-filter.tpl.html'; -import entityFilterDialogTemplate from './entity-filter-dialog.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ -import EntityFilterDialogController from './entity-filter-dialog.controller'; - import './entity-filter.scss'; /*@ngInject*/ -export default function EntityFilterDirective($compile, $templateCache, $q, $document, $mdDialog, types) { +export default function EntityFilterDirective($compile, $templateCache, $q, $document, $mdDialog, types, entityService) { var linker = function (scope, element, attrs, ngModelCtrl) { @@ -35,66 +32,59 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc scope.ngModelCtrl = ngModelCtrl; scope.types = types; - scope.hideLabels = angular.isDefined(attrs.hideLabels); + scope.aliasFilterTypes = entityService.getAliasFilterTypesByEntityTypes(scope.allowedEntityTypes); - scope.updateValidity = function() { - if (ngModelCtrl.$viewValue) { - var value = ngModelCtrl.$viewValue; - ngModelCtrl.$setValidity('filter', value.type ? true : false); + scope.$watch('filter.type', function (newType, prevType) { + if (newType && newType != prevType) { + updateFilter(); } - } + }); - ngModelCtrl.$render = function () { - if (ngModelCtrl.$viewValue) { - scope.model = angular.copy(ngModelCtrl.$viewValue); - } else { - scope.model = { - type: null, - resolveMultiple: false - } + function updateFilter() { + var filter = {}; + filter.type = scope.filter.type; + filter.resolveMultiple = scope.filter.resolveMultiple; + switch (filter.type) { + case types.aliasFilterType.entityList.value: + filter.entityType = null; + filter.entityList = []; + break; + case types.aliasFilterType.entityName.value: + filter.entityType = null; + filter.entityNameFilter = ''; + break; + case types.aliasFilterType.stateEntity.value: + break; + case types.aliasFilterType.assetType.value: + filter.assetType = null; + filter.assetNameFilter = ''; + break; + case types.aliasFilterType.deviceType.value: + filter.deviceType = null; + filter.deviceNameFilter = ''; + break; + //TODO: Alias filter } + scope.filter = filter; } - scope.$watch('model.resolveMultiple', function () { - if (ngModelCtrl.$viewValue) { - var value = ngModelCtrl.$viewValue; - value.resolveMultiple = scope.model.resolveMultiple; - ngModelCtrl.$setViewValue(value); - scope.updateValidity(); - } + scope.$watch('filter', function () { + scope.updateView(); }); - scope.editFilter = function($event) { - openEntityFilterDialog($event, false); + scope.updateView = function() { + ngModelCtrl.$setViewValue(scope.filter); } - scope.createFilter = function($event) { - openEntityFilterDialog($event, true); - } - - function openEntityFilterDialog($event, isAdd) { - $mdDialog.show({ - controller: EntityFilterDialogController, - controllerAs: 'vm', - templateUrl: entityFilterDialogTemplate, - locals: { - isAdd: isAdd, - allowedEntityTypes: scope.allowedEntityTypes, - filter: angular.copy(scope.model) - }, - parent: angular.element($document[0].body), - fullscreen: true, - skipHide: true, - targetEvent: $event - }).then(function (result) { - scope.model = result.filter; - ngModelCtrl.$setViewValue(result.filter); - scope.updateValidity(); - if (scope.onMatchingEntityChange) { - scope.onMatchingEntityChange({entity: result.entity, stateEntity: result.stateEntity}); + ngModelCtrl.$render = function () { + if (ngModelCtrl.$viewValue) { + scope.filter = ngModelCtrl.$viewValue; + } else { + scope.filter = { + type: null, + resolveMultiple: false } - }, function () { - }); + } } $compile(element.contents())(scope); @@ -106,8 +96,8 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc require: "^ngModel", link: linker, scope: { - allowedEntityTypes: '=?', - onMatchingEntityChange: '&' + theForm: '=', + allowedEntityTypes: '=?' } }; diff --git a/ui/src/app/entity/entity-filter.scss b/ui/src/app/entity/entity-filter.scss index 10ddcd34c2..ebbea3dea8 100644 --- a/ui/src/app/entity/entity-filter.scss +++ b/ui/src/app/entity/entity-filter.scss @@ -13,22 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + .tb-entity-filter { - .tb-filter-switch { - padding-left: 10px; - .filter-switch { - margin: 0; - } - .filter-label { - margin: 5px 0; - } - } - .tb-error-messages { - margin-top: -11px; - height: 35px; - .tb-error-message { - padding-left: 8px; - padding-top: 14px; - } - } + } \ No newline at end of file diff --git a/ui/src/app/entity/entity-filter.tpl.html b/ui/src/app/entity/entity-filter.tpl.html index 6ce95fa680..77b6d3cef5 100644 --- a/ui/src/app/entity/entity-filter.tpl.html +++ b/ui/src/app/entity/entity-filter.tpl.html @@ -15,37 +15,77 @@ limitations under the License. --> -
    -
    -
    -
    {{ types.aliasFilterType[model.type].name | translate }}
    - - - add - - {{ 'alias.create-entity-filter' | translate }} - -
    - - - {{ 'alias.edit-entity-filter' | translate }} - - - edit - - +
    + + + + + {{type.name | translate}} + + +
    +
    alias.filter-type-required
    +
    +
    +
    + + + +
    -
    - - - +
    + + + + + +
    +
    entity.entity-name-filter-required
    +
    +
    -
    +
    +
    +
    + + + + + + +
    +
    + + + + + + +
    +
    diff --git a/ui/src/app/entity/entity-type-select.directive.js b/ui/src/app/entity/entity-type-select.directive.js index 1dc674173f..bca2ee0cfe 100644 --- a/ui/src/app/entity/entity-type-select.directive.js +++ b/ui/src/app/entity/entity-type-select.directive.js @@ -71,7 +71,7 @@ export default function EntityTypeSelect($compile, $templateCache, utils, userSe } scope.typeName = function(type) { - return utils.entityTypeName(type); + return type ? types.entityTypeTranslations[type].type : ''; } scope.updateValidity = function () { diff --git a/ui/src/app/entity/index.js b/ui/src/app/entity/index.js index 6be118f8ad..2fc1d6300b 100644 --- a/ui/src/app/entity/index.js +++ b/ui/src/app/entity/index.js @@ -15,6 +15,7 @@ */ import EntityAliasesController from './entity-aliases.controller'; +import EntityAliasDialogController from './entity-alias-dialog.controller'; import EntityTypeSelectDirective from './entity-type-select.directive'; import EntitySubtypeSelectDirective from './entity-subtype-select.directive'; import EntitySubtypeAutocompleteDirective from './entity-subtype-autocomplete.directive'; @@ -22,6 +23,7 @@ import EntityAutocompleteDirective from './entity-autocomplete.directive'; import EntityListDirective from './entity-list.directive'; import EntitySelectDirective from './entity-select.directive'; import EntityFilterDirective from './entity-filter.directive'; +import EntityFilterViewDirective from './entity-filter-view.directive'; import AliasesEntitySelectPanelController from './aliases-entity-select-panel.controller'; import AliasesEntitySelectDirective from './aliases-entity-select.directive'; import AddAttributeDialogController from './attribute/add-attribute-dialog.controller'; @@ -32,6 +34,7 @@ import RelationTypeAutocompleteDirective from './relation/relation-type-autocomp export default angular.module('thingsboard.entity', []) .controller('EntityAliasesController', EntityAliasesController) + .controller('EntityAliasDialogController', EntityAliasDialogController) .controller('AliasesEntitySelectPanelController', AliasesEntitySelectPanelController) .controller('AddAttributeDialogController', AddAttributeDialogController) .controller('AddWidgetToDashboardDialogController', AddWidgetToDashboardDialogController) @@ -42,6 +45,7 @@ export default angular.module('thingsboard.entity', []) .directive('tbEntityList', EntityListDirective) .directive('tbEntitySelect', EntitySelectDirective) .directive('tbEntityFilter', EntityFilterDirective) + .directive('tbEntityFilterView', EntityFilterViewDirective) .directive('tbAliasesEntitySelect', AliasesEntitySelectDirective) .directive('tbAttributeTable', AttributeTableDirective) .directive('tbRelationTable', RelationTableDirective) diff --git a/ui/src/app/entity/relation/relation-table.directive.js b/ui/src/app/entity/relation/relation-table.directive.js index 729ff36bc6..f0c192b9d1 100644 --- a/ui/src/app/entity/relation/relation-table.directive.js +++ b/ui/src/app/entity/relation/relation-table.directive.js @@ -218,9 +218,9 @@ function RelationTableController($scope, $q, $mdDialog, $document, $translate, $ function success(allRelations) { allRelations.forEach(function(relation) { if (vm.direction == vm.types.entitySearchDirection.from) { - relation.toEntityTypeName = $translate.instant(utils.entityTypeName(relation.to.entityType)); + relation.toEntityTypeName = $translate.instant(types.entityTypeTranslations[relation.to.entityType].type); } else { - relation.fromEntityTypeName = $translate.instant(utils.entityTypeName(relation.from.entityType)); + relation.fromEntityTypeName = $translate.instant(types.entityTypeTranslations[relation.from.entityType].type); } }); vm.allRelations = allRelations; diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js index 9b36c38d46..8d4a09828b 100644 --- a/ui/src/app/import-export/import-export.service.js +++ b/ui/src/app/import-export/import-export.service.js @@ -365,7 +365,6 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, alias = aliasInfo.aliasName; filter = { type: types.aliasFilterType.entityList.value, - stateEntity: false, entityType: types.entityType.device, entityList: [aliasInfo.deviceId], resolveMultiple: false @@ -378,7 +377,6 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, resolveMultiple: false } if (filter.type == types.aliasFilterType.entityList.value) { - filter.stateEntity = false; filter.entityList = aliasInfo.deviceFilter.deviceList } else { filter.entityNameFilter = aliasInfo.deviceFilter.deviceNameFilter; @@ -391,7 +389,6 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, resolveMultiple: false } if (filter.type == types.aliasFilterType.entityList.value) { - filter.stateEntity = false; filter.entityList = aliasInfo.entityFilter.entityList; } else { filter.entityNameFilter = aliasInfo.entityFilter.entityNameFilter; @@ -662,8 +659,6 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, entityAliases: missingEntityAliases, widgets: widgets, isSingleWidget: isSingleWidget, - isSingleEntityAlias: false, - singleEntityAlias: null, customTitle: customTitle, disableAdd: true } diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index 9c6695b96c..88a6021568 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -113,23 +113,30 @@ export default angular.module('thingsboard.locale', []) "alarm-required": "Alarm is required" }, "alias": { + "add": "Add alias", + "edit": "Edit alias", + "name": "Alias name", + "name-required": "Alias name is required", + "duplicate-alias": "Alias with same name is already exists.", "filter-type-entity-list": "Entity list", "filter-type-entity-name": "Entity name", + "filter-type-state-entity": "Entity from dashboard state", + "filter-type-state-entity-description": "Entity taken from dashboard state parameters", "filter-type-asset-type": "Asset type", + "filter-type-asset-type-description": "Assets of type '{{assetType}}'", + "filter-type-asset-type-and-name-description": "Assets of type '{{assetType}}' and with name starting with '{{prefix}}'", "filter-type-device-type": "Device type", + "filter-type-device-type-description": "Devices of type '{{deviceType}}'", + "filter-type-device-type-and-name-description": "Devices of type '{{deviceType}}' and with name starting with '{{prefix}}'", "filter-type-relations-query": "Relations query", "filter-type-asset-search-query": "Asset search query", "filter-type-device-search-query": "Device search query", "entity-filter": "Entity filter", - "create-entity-filter": "Create entity filter", - "edit-entity-filter": "Edit entity filter", - "entity-filter-required": "Entity filter is required.", "resolve-multiple": "Resolve as multiple entities", "filter-type": "Filter type", "filter-type-required": "Filter type is required.", - "use-state-entity": "Use state entity", - "state-entity": "State entity", "entity-filter-no-entity-matched": "No entities matching specified filter were found.", + "no-entity-filter-specified": "No entity filter specified" }, "asset": { "asset": "Asset", @@ -185,7 +192,8 @@ export default angular.module('thingsboard.locale', []) "idCopiedMessage": "Asset Id has been copied to clipboard", "select-asset": "Select asset", "no-assets-matching": "No assets matching '{{entity}}' were found.", - "asset-required": "Asset is required" + "asset-required": "Asset is required", + "name-starts-with": "Asset name starts with" }, "attribute": { "attributes": "Attributes", @@ -463,7 +471,7 @@ export default angular.module('thingsboard.locale', []) "alias-required": "Device alias is required.", "remove-alias": "Remove device alias", "add-alias": "Add device alias", - "name-starts-with": "Name starts with", + "name-starts-with": "Device name starts with", "device-list": "Device list", "use-device-name-filter": "Use filter", "device-list-empty": "No devices selected.", @@ -549,6 +557,7 @@ export default angular.module('thingsboard.locale', []) "unable-delete-entity-alias-title": "Unable to delete entity alias", "unable-delete-entity-alias-text": "Entity alias '{{entityAlias}}' can't be deleted as it used by the following widget(s):
    {{widgetsList}}", "duplicate-alias-error": "Duplicate alias found '{{alias}}'.
    Entity aliases must be unique whithin the dashboard.", + "missing-entity-filter-error": "Filter is missing for alias '{{alias}}'.", "configure-alias": "Configure '{{alias}}' alias", "alias": "Alias", "alias-required": "Entity alias is required.", @@ -562,24 +571,42 @@ export default angular.module('thingsboard.locale', []) "entity-name-filter-required": "Entity name filter is required.", "entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.", "all-subtypes": "All", + "select-entities": "Select entities", + "no-aliases-found": "No aliases found.", + "no-alias-matching": "'{{alias}}' not found.", + "create-new-alias": "Create a new one!", + "no-keys-found": "No keys found.", + "no-key-matching": "'{{key}}' not found.", + "create-new-key": "Create a new one!", "type": "Type", "type-required": "Entity type is required.", "type-device": "Device", + "list-of-devices": "{ count, select, 1 {One device} other {List of # devices} }", + "device-name-starts-with": "Devices whose names start with '{{prefix}}'", "type-asset": "Asset", + "list-of-assets": "{ count, select, 1 {One asset} other {List of # assets} }", + "asset-name-starts-with": "Assets whose names start with '{{prefix}}'", "type-rule": "Rule", + "list-of-rules": "{ count, select, 1 {One rule} other {List of # rules} }", + "rule-name-starts-with": "Rules whose names start with '{{prefix}}'", "type-plugin": "Plugin", + "list-of-plugins": "{ count, select, 1 {One plugin} other {List of # plugins} }", + "plugin-name-starts-with": "Plugins whose names start with '{{prefix}}'", "type-tenant": "Tenant", + "list-of-tenants": "{ count, select, 1 {One tenant} other {List of # tenants} }", + "tenant-name-starts-with": "Tenants whose names start with '{{prefix}}'", "type-customer": "Customer", + "list-of-customers": "{ count, select, 1 {One customer} other {List of # customers} }", + "customer-name-starts-with": "Customers whose names start with '{{prefix}}'", "type-user": "User", + "list-of-users": "{ count, select, 1 {One user} other {List of # users} }", + "user-name-starts-with": "Users whose names start with '{{prefix}}'", "type-dashboard": "Dashboard", + "list-of-dashboards": "{ count, select, 1 {One dashboard} other {List of # dashboards} }", + "dashboard-name-starts-with": "Dashboards whose names start with '{{prefix}}'", "type-alarm": "Alarm", - "select-entities": "Select entities", - "no-aliases-found": "No aliases found.", - "no-alias-matching": "'{{alias}}' not found.", - "create-new-alias": "Create a new one!", - "no-keys-found": "No keys found.", - "no-key-matching": "'{{key}}' not found.", - "create-new-key": "Create a new one!" + "list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }", + "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'" }, "event": { "event-type": "Event type", From 9cdfe4ef54e38e4a0b962665abae1bef431a548d Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 12 Jun 2017 12:59:51 +0300 Subject: [PATCH 5/7] TB-61: Implemented new alias filters. --- .../controller/EntityRelationController.java | 15 ++ .../dao/relation/BaseRelationService.java | 26 +- .../server/dao/relation/RelationService.java | 2 + ui/src/app/api/entity-relation.service.js | 14 +- ui/src/app/api/entity.service.js | 249 +++++++++++++++++- ui/src/app/common/types.constant.js | 9 + .../components/datasource-func.directive.js | 8 + ui/src/app/components/datasource-func.scss | 38 +-- .../app/components/datasource-func.tpl.html | 111 ++++---- ui/src/app/dashboard/add-widget.controller.js | 2 +- ui/src/app/dashboard/dashboard.controller.js | 2 +- ui/src/app/dashboard/edit-widget.directive.js | 2 +- .../aliases-entity-select-button.tpl.html | 0 .../aliases-entity-select-panel.controller.js | 0 .../aliases-entity-select-panel.tpl.html | 0 .../aliases-entity-select.directive.js | 0 .../{ => alias}/aliases-entity-select.scss | 0 .../entity-alias-dialog.controller.js | 0 .../{ => alias}/entity-alias-dialog.scss | 0 .../{ => alias}/entity-alias-dialog.tpl.html | 0 .../{ => alias}/entity-aliases.controller.js | 0 .../entity/{ => alias}/entity-aliases.scss | 0 .../{ => alias}/entity-aliases.tpl.html | 0 .../entity/entity-filter-view.directive.js | 101 ++++++- ui/src/app/entity/entity-filter.directive.js | 18 +- ui/src/app/entity/entity-filter.scss | 17 ++ ui/src/app/entity/entity-filter.tpl.html | 142 ++++++++++ .../entity-subtype-autocomplete.directive.js | 3 + .../entity/entity-subtype-list.directive.js | 146 ++++++++++ ui/src/app/entity/entity-subtype-list.scss | 30 +++ .../app/entity/entity-subtype-list.tpl.html | 54 ++++ .../app/entity/entity-type-list.directive.js | 111 ++++++++ ui/src/app/entity/entity-type-list.scss | 30 +++ ui/src/app/entity/entity-type-list.tpl.html | 54 ++++ .../entity/entity-type-select.directive.js | 33 +-- ui/src/app/entity/index.js | 14 +- .../relation/relation-filters.directive.js | 85 ++++++ .../app/entity/relation/relation-filters.scss | 77 ++++++ .../entity/relation/relation-filters.tpl.html | 67 +++++ .../relation-type-autocomplete.directive.js | 2 + .../relation-type-autocomplete.tpl.html | 2 +- .../import-export/import-export.service.js | 2 +- ui/src/app/locale/locale.constant.js | 50 +++- ui/src/scss/main.scss | 9 + 44 files changed, 1399 insertions(+), 126 deletions(-) rename ui/src/app/entity/{ => alias}/aliases-entity-select-button.tpl.html (100%) rename ui/src/app/entity/{ => alias}/aliases-entity-select-panel.controller.js (100%) rename ui/src/app/entity/{ => alias}/aliases-entity-select-panel.tpl.html (100%) rename ui/src/app/entity/{ => alias}/aliases-entity-select.directive.js (100%) rename ui/src/app/entity/{ => alias}/aliases-entity-select.scss (100%) rename ui/src/app/entity/{ => alias}/entity-alias-dialog.controller.js (100%) rename ui/src/app/entity/{ => alias}/entity-alias-dialog.scss (100%) rename ui/src/app/entity/{ => alias}/entity-alias-dialog.tpl.html (100%) rename ui/src/app/entity/{ => alias}/entity-aliases.controller.js (100%) rename ui/src/app/entity/{ => alias}/entity-aliases.scss (100%) rename ui/src/app/entity/{ => alias}/entity-aliases.tpl.html (100%) create mode 100644 ui/src/app/entity/entity-subtype-list.directive.js create mode 100644 ui/src/app/entity/entity-subtype-list.scss create mode 100644 ui/src/app/entity/entity-subtype-list.tpl.html create mode 100644 ui/src/app/entity/entity-type-list.directive.js create mode 100644 ui/src/app/entity/entity-type-list.scss create mode 100644 ui/src/app/entity/entity-type-list.tpl.html create mode 100644 ui/src/app/entity/relation/relation-filters.directive.js create mode 100644 ui/src/app/entity/relation/relation-filters.scss create mode 100644 ui/src/app/entity/relation/relation-filters.tpl.html diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java index 3ddc5975a7..4aa1a0a5cb 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java @@ -250,6 +250,21 @@ public class EntityRelationController extends BaseController { } } + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/relations/info", method = RequestMethod.POST) + @ResponseBody + public List findInfoByQuery(@RequestBody EntityRelationsQuery query) throws ThingsboardException { + checkNotNull(query); + checkNotNull(query.getParameters()); + checkNotNull(query.getFilters()); + checkEntityId(query.getParameters().getEntityId()); + try { + return checkNotNull(relationService.findInfoByQuery(query).get()); + } catch (Exception e) { + throw handleException(e); + } + } + private RelationTypeGroup parseRelationTypeGroup(String strRelationTypeGroup, RelationTypeGroup defaultValue) { RelationTypeGroup result = defaultValue; if (strRelationTypeGroup != null && strRelationTypeGroup.trim().length()>0) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java index 36ec56735b..296874e20f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java @@ -191,7 +191,7 @@ public class BaseRelationService implements RelationService { @Override public ListenableFuture> findByQuery(EntityRelationsQuery query) { - log.trace("Executing findByQuery [{}][{}]", query); + log.trace("Executing findByQuery [{}]", query); RelationsSearchParameters params = query.getParameters(); final List filters = query.getFilters(); if (filters == null || filters.isEmpty()) { @@ -224,6 +224,30 @@ public class BaseRelationService implements RelationService { } } + @Override + public ListenableFuture> findInfoByQuery(EntityRelationsQuery query) { + log.trace("Executing findInfoByQuery [{}]", query); + ListenableFuture> relations = findByQuery(query); + EntitySearchDirection direction = query.getParameters().getDirection(); + ListenableFuture> relationsInfo = Futures.transform(relations, + (AsyncFunction, List>) relations1 -> { + List> futures = new ArrayList<>(); + relations1.stream().forEach(relation -> + futures.add(fetchRelationInfoAsync(relation, + relation2 -> direction == EntitySearchDirection.FROM ? relation2.getTo() : relation2.getFrom(), + (EntityRelationInfo relationInfo, String entityName) -> { + if (direction == EntitySearchDirection.FROM) { + relationInfo.setToName(entityName); + } else { + relationInfo.setFromName(entityName); + } + })) + ); + return Futures.successfulAsList(futures); + }); + return relationsInfo; + } + protected void validate(EntityRelation relation) { if (relation == null) { throw new DataValidationException("Relation type should be specified!"); diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java index a810454f2a..bd2e785a5b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java @@ -52,6 +52,8 @@ public interface RelationService { ListenableFuture> findByQuery(EntityRelationsQuery query); + ListenableFuture> findInfoByQuery(EntityRelationsQuery query); + // TODO: This method may be useful for some validations in the future // ListenableFuture checkRecursiveRelation(EntityId from, EntityId to); diff --git a/ui/src/app/api/entity-relation.service.js b/ui/src/app/api/entity-relation.service.js index 875b2fac38..351c252c56 100644 --- a/ui/src/app/api/entity-relation.service.js +++ b/ui/src/app/api/entity-relation.service.js @@ -30,7 +30,8 @@ function EntityRelationService($http, $q) { findByTo: findByTo, findInfoByTo: findInfoByTo, findByToAndType: findByToAndType, - findByQuery: findByQuery + findByQuery: findByQuery, + findInfoByQuery: findInfoByQuery } return service; @@ -159,4 +160,15 @@ function EntityRelationService($http, $q) { return deferred.promise; } + function findInfoByQuery(query) { + var deferred = $q.defer(); + var url = '/api/relations/info'; + $http.post(url, query).then(function success(response) { + deferred.resolve(response.data); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + } diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js index e647d9cfb2..20157ce918 100644 --- a/ui/src/app/api/entity.service.js +++ b/ui/src/app/api/entity.service.js @@ -32,6 +32,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device checkEntityAlias: checkEntityAlias, filterAliasByEntityTypes: filterAliasByEntityTypes, getAliasFilterTypesByEntityTypes: getAliasFilterTypesByEntityTypes, + prepareAllowedEntityTypesList: prepareAllowedEntityTypesList, getEntityKeys: getEntityKeys, createDatasourcesFromSubscriptionsInfo: createDatasourcesFromSubscriptionsInfo, getRelatedEntities: getRelatedEntities, @@ -176,6 +177,54 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device return deferred.promise; } + function getSingleTenantByPageLinkPromise(pageLink) { + var user = userService.getCurrentUser(); + var tenantId = user.tenantId; + var deferred = $q.defer(); + tenantService.getTenant(tenantId).then( + function success(tenant) { + var tenantName = tenant.name; + var result = { + data: [], + nextPageLink: pageLink, + hasNext: false + }; + if (tenantName.toLowerCase().startsWith(pageLink.textSearch)) { + result.data.push(tenant); + } + deferred.resolve(result); + }, + function fail() { + deferred.reject(); + } + ); + return deferred.promise; + } + + function getSingleCustomerByPageLinkPromise(pageLink) { + var user = userService.getCurrentUser(); + var customerId = user.customerId; + var deferred = $q.defer(); + customerService.getCustomer(customerId).then( + function success(customer) { + var customerName = customer.name; + var result = { + data: [], + nextPageLink: pageLink, + hasNext: false + }; + if (customerName.toLowerCase().startsWith(pageLink.textSearch)) { + result.data.push(customer); + } + deferred.resolve(result); + }, + function fail() { + deferred.reject(); + } + ); + return deferred.promise; + } + function getEntitiesByPageLinkPromise(entityType, pageLink, config, subType) { var promise; var user = userService.getCurrentUser(); @@ -196,10 +245,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device } break; case types.entityType.tenant: - promise = tenantService.getTenants(pageLink); + if (user.authority === 'TENANT_ADMIN') { + promise = getSingleTenantByPageLinkPromise(pageLink); + } else { + promise = tenantService.getTenants(pageLink); + } break; case types.entityType.customer: - promise = customerService.getCustomers(pageLink); + if (user.authority === 'CUSTOMER_USER') { + promise = getSingleCustomerByPageLinkPromise(pageLink); + } else { + promise = customerService.getCustomers(pageLink); + } break; case types.entityType.rule: promise = ruleService.getAllRules(pageLink); @@ -283,6 +340,16 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device return { name: entity.name, entityType: entity.id.entityType, id: entity.id.id }; } + function entityRelationInfoToEntityInfo(entityRelationInfo, direction) { + var entityId = direction == types.entitySearchDirection.from ? entityRelationInfo.to : entityRelationInfo.from; + var name = direction == types.entitySearchDirection.from ? entityRelationInfo.toName : entityRelationInfo.fromName; + return { + name: name, + entityType: entityId.entityType, + id: entityId.id + }; + } + function entitiesToEntitiesInfo(entities) { var entitiesInfo = []; for (var d = 0; d < entities.length; d++) { @@ -291,19 +358,26 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device return entitiesInfo; } + function entityRelationInfosToEntitiesInfo(entityRelations, direction) { + var entitiesInfo = []; + for (var d = 0; d < entityRelations.length; d++) { + entitiesInfo.push(entityRelationInfoToEntityInfo(entityRelations[d], direction)); + } + return entitiesInfo; + } + + function resolveAlias(entityAlias, stateParams) { var deferred = $q.defer(); var filter = entityAlias.filter; resolveAliasFilter(filter, stateParams, -1).then( function (result) { - var entities = result.entities; var aliasInfo = { alias: entityAlias.alias, stateEntity: result.stateEntity, resolveMultiple: filter.resolveMultiple }; - var resolvedEntities = entitiesToEntitiesInfo(entities); - aliasInfo.resolvedEntities = resolvedEntities; + aliasInfo.resolvedEntities = result.entities; aliasInfo.currentEntity = null; if (aliasInfo.resolvedEntities.length) { aliasInfo.currentEntity = aliasInfo.resolvedEntities[0]; @@ -328,7 +402,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device getEntities(filter.entityType, filter.entityList).then( function success(entities) { if (entities && entities.length) { - result.entities = entities; + result.entities = entitiesToEntitiesInfo(entities); deferred.resolve(result); } else { deferred.reject(); @@ -343,7 +417,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems).then( function success(entities) { if (entities && entities.length) { - result.entities = entities; + result.entities = entitiesToEntitiesInfo(entities); deferred.resolve(result); } else { deferred.reject(); @@ -359,7 +433,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device if (stateParams && stateParams.entityId) { getEntity(stateParams.entityId.entityType, stateParams.entityId.id).then( function success(entity) { - result.entities = [entity]; + result.entities = entitiesToEntitiesInfo([entity]); deferred.resolve(result); }, function fail() { @@ -374,7 +448,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device getEntitiesByNameFilter(types.entityType.asset, filter.assetNameFilter, maxItems, null, filter.assetType).then( function success(entities) { if (entities && entities.length) { - result.entities = entities; + result.entities = entitiesToEntitiesInfo(entities); deferred.resolve(result); } else { deferred.reject(); @@ -389,7 +463,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device getEntitiesByNameFilter(types.entityType.device, filter.deviceNameFilter, maxItems, null, filter.deviceType).then( function success(entities) { if (entities && entities.length) { - result.entities = entities; + result.entities = entitiesToEntitiesInfo(entities); deferred.resolve(result); } else { deferred.reject(); @@ -400,8 +474,97 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device } ); break; - - //TODO: Alias filter + case types.aliasFilterType.relationsQuery.value: + result.stateEntity = filter.rootStateEntity; + var rootEntityType; + var rootEntityId; + if (result.stateEntity && stateParams && stateParams.entityId) { + rootEntityType = stateParams.entityId.entityType; + rootEntityId = stateParams.entityId.id; + } else if (!result.stateEntity) { + rootEntityType = filter.rootEntity.entityType; + rootEntityId = filter.rootEntity.id; + } + if (rootEntityType && rootEntityId) { + var searchQuery = { + parameters: { + rootId: rootEntityId, + rootType: rootEntityType, + direction: filter.direction + }, + filters: filter.filters + }; + searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1; + entityRelationService.findInfoByQuery(searchQuery).then( + function success(allRelations) { + if (allRelations && allRelations.length) { + if (angular.isDefined(maxItems) && maxItems > 0) { + var limit = Math.min(allRelations.length, maxItems); + allRelations.length = limit; + } + result.entities = entityRelationInfosToEntitiesInfo(allRelations, filter.direction); + deferred.resolve(result); + } else { + deferred.reject(); + } + }, + function fail() { + deferred.reject(); + } + ); + } else { + deferred.resolve(result); + } + break; + case types.aliasFilterType.assetSearchQuery.value: + case types.aliasFilterType.deviceSearchQuery.value: + result.stateEntity = filter.rootStateEntity; + if (result.stateEntity && stateParams && stateParams.entityId) { + rootEntityType = stateParams.entityId.entityType; + rootEntityId = stateParams.entityId.id; + } else if (!result.stateEntity) { + rootEntityType = filter.rootEntity.entityType; + rootEntityId = filter.rootEntity.id; + } + if (rootEntityType && rootEntityId) { + searchQuery = { + parameters: { + rootId: rootEntityId, + rootType: rootEntityType, + direction: filter.direction + }, + relationType: filter.relationType + }; + searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1; + var findByQueryPromise; + if (filter.type == types.aliasFilterType.assetSearchQuery.value) { + searchQuery.assetTypes = filter.assetTypes; + findByQueryPromise = assetService.findByQuery(searchQuery, false); + } else if (filter.type == types.aliasFilterType.deviceSearchQuery.value) { + searchQuery.deviceTypes = filter.deviceTypes; + findByQueryPromise = deviceService.findByQuery(searchQuery, false); + } + findByQueryPromise.then( + function success(entities) { + if (entities && entities.length) { + if (angular.isDefined(maxItems) && maxItems > 0) { + var limit = Math.min(entities.length, maxItems); + entities.length = limit; + } + result.entities = entitiesToEntitiesInfo(entities); + deferred.resolve(result); + } else { + deferred.reject(); + } + }, + function fail() { + deferred.reject(); + } + ); + } else { + deferred.resolve(result); + } + break; } return deferred.promise; } @@ -420,9 +583,33 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false; case types.aliasFilterType.deviceType.value: return entityTypes.indexOf(types.entityType.device) > -1 ? true : false; + case types.aliasFilterType.relationsQuery.value: + if (filter.filters && filter.filters.length) { + var match = false; + for (var f=0;f -1) { + match = true; + break; + } + } + } else { + match = true; + break; + } + } + return match; + } else { + return true; + } + case types.aliasFilterType.assetSearchQuery.value: + return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false; + case types.aliasFilterType.deviceSearchQuery.value: + return entityTypes.indexOf(types.entityType.device) > -1 ? true : false; } } - //TODO: Alias filter return false; } @@ -474,6 +661,42 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device return result; } + function prepareAllowedEntityTypesList(allowedEntityTypes) { + var authority = userService.getAuthority(); + var entityTypes = {}; + switch(authority) { + case 'SYS_ADMIN': + entityTypes.tenant = types.entityType.tenant; + entityTypes.rule = types.entityType.rule; + entityTypes.plugin = types.entityType.plugin; + break; + case 'TENANT_ADMIN': + entityTypes.device = types.entityType.device; + entityTypes.asset = types.entityType.asset; + entityTypes.tenant = types.entityType.tenant; + entityTypes.customer = types.entityType.customer; + entityTypes.rule = types.entityType.rule; + entityTypes.plugin = types.entityType.plugin; + entityTypes.dashboard = types.entityType.dashboard; + break; + case 'CUSTOMER_USER': + entityTypes.device = types.entityType.device; + entityTypes.asset = types.entityType.asset; + entityTypes.customer = types.entityType.customer; + entityTypes.dashboard = types.entityType.dashboard; + break; + } + + if (allowedEntityTypes) { + for (var entityType in entityTypes) { + if (allowedEntityTypes.indexOf(entityTypes[entityType]) === -1) { + delete entityTypes[entityType]; + } + } + } + return entityTypes; + } + function checkEntityAlias(entityAlias) { var deferred = $q.defer(); diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 522abff190..fe3f73ca88 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -146,46 +146,55 @@ export default angular.module('thingsboard.types', []) entityTypeTranslations: { "DEVICE": { type: 'entity.type-device', + typePlural: 'entity.type-devices', list: 'entity.list-of-devices', nameStartsWith: 'entity.device-name-starts-with' }, "ASSET": { type: 'entity.type-asset', + typePlural: 'entity.type-assets', list: 'entity.list-of-assets', nameStartsWith: 'entity.asset-name-starts-with' }, "RULE": { type: 'entity.type-rule', + typePlural: 'entity.type-rules', list: 'entity.list-of-rules', nameStartsWith: 'entity.rule-name-starts-with' }, "PLUGIN": { type: 'entity.type-plugin', + typePlural: 'entity.type-plugins', list: 'entity.list-of-plugins', nameStartsWith: 'entity.plugin-name-starts-with' }, "TENANT": { type: 'entity.type-tenant', + typePlural: 'entity.type-tenants', list: 'entity.list-of-tenants', nameStartsWith: 'entity.tenant-name-starts-with' }, "CUSTOMER": { type: 'entity.type-customer', + typePlural: 'entity.type-customers', list: 'entity.list-of-customers', nameStartsWith: 'entity.customer-name-starts-with' }, "USER": { type: 'entity.type-user', + typePlural: 'entity.type-users', list: 'entity.list-of-users', nameStartsWith: 'entity.user-name-starts-with' }, "DASHBOARD": { type: 'entity.type-dashboard', + typePlural: 'entity.type-dashboards', list: 'entity.list-of-dashboards', nameStartsWith: 'entity.dashboard-name-starts-with' }, "ALARM": { type: 'entity.type-alarm', + typePlural: 'entity.type-alarms', list: 'entity.list-of-alarms', nameStartsWith: 'entity.alarm-name-starts-with' } diff --git a/ui/src/app/components/datasource-func.directive.js b/ui/src/app/components/datasource-func.directive.js index 0f66dcaa29..fe546962c7 100644 --- a/ui/src/app/components/datasource-func.directive.js +++ b/ui/src/app/components/datasource-func.directive.js @@ -71,6 +71,13 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, } }, true); + scope.$watch('datasourceName', function () { + if (ngModelCtrl.$viewValue) { + ngModelCtrl.$viewValue.name = scope.datasourceName; + scope.updateValidity(); + } + }); + ngModelCtrl.$render = function () { if (ngModelCtrl.$viewValue) { var funcDataKeys = []; @@ -78,6 +85,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, funcDataKeys = funcDataKeys.concat(ngModelCtrl.$viewValue.dataKeys); } scope.funcDataKeys = funcDataKeys; + scope.datasourceName = ngModelCtrl.$viewValue.name; } }; diff --git a/ui/src/app/components/datasource-func.scss b/ui/src/app/components/datasource-func.scss index 1a5dcc72ea..8739ed8fee 100644 --- a/ui/src/app/components/datasource-func.scss +++ b/ui/src/app/components/datasource-func.scss @@ -15,23 +15,29 @@ */ @import '../../scss/constants'; -.tb-func-datakey-autocomplete { - .tb-not-found { - display: block; - line-height: 1.5; - height: 48px; - .tb-no-entries { - line-height: 48px; - } +.tb-datasource-func { + @media (min-width: $layout-breakpoint-gt-sm) { + padding-left: 8px; } - li { - height: auto !important; - white-space: normal !important; + + md-input-container.tb-datasource-name { + .md-errors-spacer { + display: none; + } } -} -tb-datasource-func { - @media (min-width: $layout-breakpoint-gt-sm) { - padding-left: 8px; + .tb-func-datakey-autocomplete { + .tb-not-found { + display: block; + line-height: 1.5; + height: 48px; + .tb-no-entries { + line-height: 48px; + } + } + li { + height: auto !important; + white-space: normal !important; + } } -} \ No newline at end of file +} diff --git a/ui/src/app/components/datasource-func.tpl.html b/ui/src/app/components/datasource-func.tpl.html index 1853b9d503..e8e08f7f0c 100644 --- a/ui/src/app/components/datasource-func.tpl.html +++ b/ui/src/app/components/datasource-func.tpl.html @@ -15,59 +15,68 @@ limitations under the License. --> -
    - - - {{item}} - -
    -
    - device.no-keys-found +
    + + + +
    + + + {{item}} + +
    +
    + device.no-keys-found +
    +
    + device.no-key-matching + + device.create-new-key + +
    -
    - device.no-key-matching - - device.create-new-key - -
    -
    - - - -
    -
    -
    -
    -
    -
    - {{$chip.label}} + + + +
    +
    +
    -
    :
    -
    - {{$chip.name}} +
    +
    + {{$chip.label}} +
    +
    :
    +
    + {{$chip.name}} +
    + + edit +
    - - edit - -
    -
    - - + + + +
    diff --git a/ui/src/app/dashboard/add-widget.controller.js b/ui/src/app/dashboard/add-widget.controller.js index a9a68c74af..5372b6217e 100644 --- a/ui/src/app/dashboard/add-widget.controller.js +++ b/ui/src/app/dashboard/add-widget.controller.js @@ -15,7 +15,7 @@ */ /* eslint-disable import/no-unresolved, import/default */ -import entityAliasDialogTemplate from '../entity/entity-alias-dialog.tpl.html'; +import entityAliasDialogTemplate from '../entity/alias/entity-alias-dialog.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ diff --git a/ui/src/app/dashboard/dashboard.controller.js b/ui/src/app/dashboard/dashboard.controller.js index 7c97a9462a..65eb833dd4 100644 --- a/ui/src/app/dashboard/dashboard.controller.js +++ b/ui/src/app/dashboard/dashboard.controller.js @@ -15,7 +15,7 @@ */ /* eslint-disable import/no-unresolved, import/default */ -import entityAliasesTemplate from '../entity/entity-aliases.tpl.html'; +import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html'; import dashboardSettingsTemplate from './dashboard-settings.tpl.html'; import manageDashboardLayoutsTemplate from './layouts/manage-dashboard-layouts.tpl.html'; import manageDashboardStatesTemplate from './states/manage-dashboard-states.tpl.html'; diff --git a/ui/src/app/dashboard/edit-widget.directive.js b/ui/src/app/dashboard/edit-widget.directive.js index 5b788cc82c..65329149e5 100644 --- a/ui/src/app/dashboard/edit-widget.directive.js +++ b/ui/src/app/dashboard/edit-widget.directive.js @@ -15,7 +15,7 @@ */ /* eslint-disable import/no-unresolved, import/default */ -import entityAliasDialogTemplate from '../entity/entity-alias-dialog.tpl.html'; +import entityAliasDialogTemplate from '../entity/alias/entity-alias-dialog.tpl.html'; import editWidgetTemplate from './edit-widget.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ diff --git a/ui/src/app/entity/aliases-entity-select-button.tpl.html b/ui/src/app/entity/alias/aliases-entity-select-button.tpl.html similarity index 100% rename from ui/src/app/entity/aliases-entity-select-button.tpl.html rename to ui/src/app/entity/alias/aliases-entity-select-button.tpl.html diff --git a/ui/src/app/entity/aliases-entity-select-panel.controller.js b/ui/src/app/entity/alias/aliases-entity-select-panel.controller.js similarity index 100% rename from ui/src/app/entity/aliases-entity-select-panel.controller.js rename to ui/src/app/entity/alias/aliases-entity-select-panel.controller.js diff --git a/ui/src/app/entity/aliases-entity-select-panel.tpl.html b/ui/src/app/entity/alias/aliases-entity-select-panel.tpl.html similarity index 100% rename from ui/src/app/entity/aliases-entity-select-panel.tpl.html rename to ui/src/app/entity/alias/aliases-entity-select-panel.tpl.html diff --git a/ui/src/app/entity/aliases-entity-select.directive.js b/ui/src/app/entity/alias/aliases-entity-select.directive.js similarity index 100% rename from ui/src/app/entity/aliases-entity-select.directive.js rename to ui/src/app/entity/alias/aliases-entity-select.directive.js diff --git a/ui/src/app/entity/aliases-entity-select.scss b/ui/src/app/entity/alias/aliases-entity-select.scss similarity index 100% rename from ui/src/app/entity/aliases-entity-select.scss rename to ui/src/app/entity/alias/aliases-entity-select.scss diff --git a/ui/src/app/entity/entity-alias-dialog.controller.js b/ui/src/app/entity/alias/entity-alias-dialog.controller.js similarity index 100% rename from ui/src/app/entity/entity-alias-dialog.controller.js rename to ui/src/app/entity/alias/entity-alias-dialog.controller.js diff --git a/ui/src/app/entity/entity-alias-dialog.scss b/ui/src/app/entity/alias/entity-alias-dialog.scss similarity index 100% rename from ui/src/app/entity/entity-alias-dialog.scss rename to ui/src/app/entity/alias/entity-alias-dialog.scss diff --git a/ui/src/app/entity/entity-alias-dialog.tpl.html b/ui/src/app/entity/alias/entity-alias-dialog.tpl.html similarity index 100% rename from ui/src/app/entity/entity-alias-dialog.tpl.html rename to ui/src/app/entity/alias/entity-alias-dialog.tpl.html diff --git a/ui/src/app/entity/entity-aliases.controller.js b/ui/src/app/entity/alias/entity-aliases.controller.js similarity index 100% rename from ui/src/app/entity/entity-aliases.controller.js rename to ui/src/app/entity/alias/entity-aliases.controller.js diff --git a/ui/src/app/entity/entity-aliases.scss b/ui/src/app/entity/alias/entity-aliases.scss similarity index 100% rename from ui/src/app/entity/entity-aliases.scss rename to ui/src/app/entity/alias/entity-aliases.scss diff --git a/ui/src/app/entity/entity-aliases.tpl.html b/ui/src/app/entity/alias/entity-aliases.tpl.html similarity index 100% rename from ui/src/app/entity/entity-aliases.tpl.html rename to ui/src/app/entity/alias/entity-aliases.tpl.html diff --git a/ui/src/app/entity/entity-filter-view.directive.js b/ui/src/app/entity/entity-filter-view.directive.js index 6a358b2e16..66c1b667fe 100644 --- a/ui/src/app/entity/entity-filter-view.directive.js +++ b/ui/src/app/entity/entity-filter-view.directive.js @@ -74,7 +74,106 @@ export default function EntityFilterViewDirective($compile, $templateCache, $q, scope.filterDisplayValue = $translate.instant('alias.filter-type-device-type-description', {deviceType: deviceType}); } break; - //TODO: Alias filter + case types.aliasFilterType.relationsQuery.value: + var rootEntityText; + var directionText; + var allEntitiesText = $translate.instant('alias.all-entities'); + var anyRelationText = $translate.instant('alias.any-relation'); + if (scope.filter.rootStateEntity) { + rootEntityText = $translate.instant('alias.state-entity'); + } else { + rootEntityText = $translate.instant(types.entityTypeTranslations[scope.filter.rootEntity.entityType].type); + } + directionText = $translate.instant('relation.direction-type.' + scope.filter.direction); + var relationFilters = scope.filter.filters; + if (relationFilters && relationFilters.length) { + var relationFiltersDisplayValues = []; + relationFilters.forEach(function(relationFilter) { + var entitiesText; + if (relationFilter.entityTypes && relationFilter.entityTypes.length) { + var entitiesNamesList = []; + relationFilter.entityTypes.forEach(function(entityType) { + entitiesNamesList.push( + $translate.instant(types.entityTypeTranslations[entityType].typePlural) + ); + }); + entitiesText = entitiesNamesList.join(', '); + } else { + entitiesText = allEntitiesText; + } + var relationTypeText; + if (relationFilter.relationType && relationFilter.relationType.length) { + relationTypeText = "'" + relationFilter.relationType + "'"; + } else { + relationTypeText = anyRelationText; + } + var relationFilterDisplayValue = $translate.instant('alias.filter-type-relations-query-description', + { + entities: entitiesText, + relationType: relationTypeText, + direction: directionText, + rootEntity: rootEntityText + } + ); + relationFiltersDisplayValues.push(relationFilterDisplayValue); + }); + scope.filterDisplayValue = relationFiltersDisplayValues.join(', '); + } else { + scope.filterDisplayValue = $translate.instant('alias.filter-type-relations-query-description', + { + entities: allEntitiesText, + relationType: anyRelationText, + direction: directionText, + rootEntity: rootEntityText + } + ); + } + break; + case types.aliasFilterType.assetSearchQuery.value: + case types.aliasFilterType.deviceSearchQuery.value: + allEntitiesText = $translate.instant('alias.all-entities'); + anyRelationText = $translate.instant('alias.any-relation'); + if (scope.filter.rootStateEntity) { + rootEntityText = $translate.instant('alias.state-entity'); + } else { + rootEntityText = $translate.instant(types.entityTypeTranslations[scope.filter.rootEntity.entityType].type); + } + directionText = $translate.instant('relation.direction-type.' + scope.filter.direction); + var relationTypeText; + if (scope.filter.relationType && scope.filter.relationType.length) { + relationTypeText = "'" + scope.filter.relationType + "'"; + } else { + relationTypeText = anyRelationText; + } + + var translationValues = { + relationType: relationTypeText, + direction: directionText, + rootEntity: rootEntityText + } + + if (scope.filter.type == types.aliasFilterType.assetSearchQuery.value) { + var assetTypesQuoted = []; + scope.filter.assetTypes.forEach(function(assetType) { + assetTypesQuoted.push("'"+assetType+"'"); + }); + var assetTypesText = assetTypesQuoted.join(', '); + translationValues.assetTypes = assetTypesText; + scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-search-query-description', + translationValues + ); + } else { + var deviceTypesQuoted = []; + scope.filter.deviceTypes.forEach(function(deviceType) { + deviceTypesQuoted.push("'"+deviceType+"'"); + }); + var deviceTypesText = deviceTypesQuoted.join(', '); + translationValues.deviceTypes = deviceTypesText; + scope.filterDisplayValue = $translate.instant('alias.filter-type-device-search-query-description', + translationValues + ); + } + break; default: scope.filterDisplayValue = scope.filter.type; break; diff --git a/ui/src/app/entity/entity-filter.directive.js b/ui/src/app/entity/entity-filter.directive.js index 9566716d3e..b81f36941d 100644 --- a/ui/src/app/entity/entity-filter.directive.js +++ b/ui/src/app/entity/entity-filter.directive.js @@ -63,7 +63,23 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc filter.deviceType = null; filter.deviceNameFilter = ''; break; - //TODO: Alias filter + case types.aliasFilterType.relationsQuery.value: + case types.aliasFilterType.assetSearchQuery.value: + case types.aliasFilterType.deviceSearchQuery.value: + filter.rootStateEntity = false; + filter.rootEntity = null; + filter.direction = types.entitySearchDirection.from; + filter.maxLevel = 1; + if (filter.type === types.aliasFilterType.relationsQuery.value) { + filter.filters = []; + } else if (filter.type === types.aliasFilterType.assetSearchQuery.value) { + filter.relationType = null; + filter.assetTypes = []; + } else if (filter.type === types.aliasFilterType.deviceSearchQuery.value) { + filter.relationType = null; + filter.deviceTypes = []; + } + break; } scope.filter = filter; } diff --git a/ui/src/app/entity/entity-filter.scss b/ui/src/app/entity/entity-filter.scss index ebbea3dea8..7e998ca674 100644 --- a/ui/src/app/entity/entity-filter.scss +++ b/ui/src/app/entity/entity-filter.scss @@ -16,4 +16,21 @@ .tb-entity-filter { + #relationsQueryFilter { + padding-top: 20px; + tb-entity-select { + min-height: 92px; + } + } + + .tb-root-state-entity-switch { + padding-left: 10px; + .root-state-entity-switch { + margin: 0; + } + .root-state-entity-label { + margin: 5px 0; + } + } + } \ No newline at end of file diff --git a/ui/src/app/entity/entity-filter.tpl.html b/ui/src/app/entity/entity-filter.tpl.html index 77b6d3cef5..4f5af00bf2 100644 --- a/ui/src/app/entity/entity-filter.tpl.html +++ b/ui/src/app/entity/entity-filter.tpl.html @@ -88,4 +88,146 @@ aria-label="{{ 'device.name-starts-with' | translate }}">
    +
    + +
    + + +
    + + + +
    +
    +
    + + + + + {{ ('relation.search-direction.' + direction) | translate}} + + + + + + + +
    +
    relation.relation-filters
    + + +
    +
    + +
    + + +
    + + + +
    +
    +
    + + + + + {{ ('relation.search-direction.' + direction) | translate}} + + + + + + + +
    +
    relation.relation-type
    + + +
    asset.asset-types
    + + +
    +
    + +
    + + +
    + + + +
    +
    +
    + + + + + {{ ('relation.search-direction.' + direction) | translate}} + + + + + + + +
    +
    relation.relation-type
    + + +
    device.device-types
    + + +
    diff --git a/ui/src/app/entity/entity-subtype-autocomplete.directive.js b/ui/src/app/entity/entity-subtype-autocomplete.directive.js index 98110b05a5..76c15d3250 100644 --- a/ui/src/app/entity/entity-subtype-autocomplete.directive.js +++ b/ui/src/app/entity/entity-subtype-autocomplete.directive.js @@ -114,6 +114,9 @@ export default function EntitySubtypeAutocomplete($compile, $templateCache, $q, scope.selectEntitySubtypeText = 'asset.select-asset-type'; scope.entitySubtypeText = 'asset.asset-type'; scope.entitySubtypeRequiredText = 'asset.asset-type-required'; + scope.$on('assetSaved', function() { + scope.entitySubtypes = null; + }); } else if (scope.entityType == types.entityType.device) { scope.selectEntitySubtypeText = 'device.select-device-type'; scope.entitySubtypeText = 'device.device-type'; diff --git a/ui/src/app/entity/entity-subtype-list.directive.js b/ui/src/app/entity/entity-subtype-list.directive.js new file mode 100644 index 0000000000..c7d63297e2 --- /dev/null +++ b/ui/src/app/entity/entity-subtype-list.directive.js @@ -0,0 +1,146 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable import/no-unresolved, import/default */ + +import entitySubtypeListTemplate from './entity-subtype-list.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +import './entity-subtype-list.scss'; + +/*@ngInject*/ +export default function EntitySubtypeListDirective($compile, $templateCache, $q, $mdUtil, $translate, $filter, types, assetService, deviceService) { + + var linker = function (scope, element, attrs, ngModelCtrl) { + + var template = $templateCache.get(entitySubtypeListTemplate); + element.html(template); + + scope.ngModelCtrl = ngModelCtrl; + + + scope.entitySubtypesList = []; + scope.entitySubtypes = null; + + if (scope.entityType == types.entityType.asset) { + scope.placeholder = scope.tbRequired ? $translate.instant('asset.enter-asset-type') + : $translate.instant('asset.any-asset'); + scope.secondaryPlaceholder = '+' + $translate.instant('asset.asset-type'); + scope.noSubtypesMathingText = 'asset.no-asset-types-matching'; + scope.subtypeListEmptyText = 'asset.asset-type-list-empty'; + } else if (scope.entityType == types.entityType.device) { + scope.placeholder = scope.tbRequired ? $translate.instant('device.enter-device-type') + : $translate.instant('device.any-device'); + scope.secondaryPlaceholder = '+' + $translate.instant('device.device-type'); + scope.noSubtypesMathingText = 'device.no-device-types-matching'; + scope.subtypeListEmptyText = 'device.device-type-list-empty'; + } + + scope.$watch('tbRequired', function () { + scope.updateValidity(); + }); + + scope.fetchEntitySubtypes = function(searchText) { + var deferred = $q.defer(); + loadSubTypes().then( + function success(subTypes) { + var result = $filter('filter')(subTypes, {'$': searchText}); + if (result && result.length) { + deferred.resolve(result); + } else { + deferred.resolve([searchText]); + } + }, + function fail() { + deferred.reject(); + } + ); + return deferred.promise; + } + + scope.updateValidity = function() { + var value = ngModelCtrl.$viewValue; + var valid = !scope.tbRequired || value && value.length > 0; + ngModelCtrl.$setValidity('entitySubtypesList', valid); + } + + ngModelCtrl.$render = function () { + scope.entitySubtypesList = ngModelCtrl.$viewValue; + if (!scope.entitySubtypesList) { + scope.entitySubtypesList = []; + } + } + + scope.$watch('entitySubtypesList', function () { + ngModelCtrl.$setViewValue(scope.entitySubtypesList); + scope.updateValidity(); + }, true); + + function loadSubTypes() { + var deferred = $q.defer(); + if (!scope.entitySubtypes) { + var entitySubtypesPromise; + if (scope.entityType == types.entityType.asset) { + entitySubtypesPromise = assetService.getAssetTypes(); + } else if (scope.entityType == types.entityType.device) { + entitySubtypesPromise = deviceService.getDeviceTypes(); + } + if (entitySubtypesPromise) { + entitySubtypesPromise.then( + function success(types) { + scope.entitySubtypes = []; + types.forEach(function (type) { + scope.entitySubtypes.push(type.type); + }); + deferred.resolve(scope.entitySubtypes); + }, + function fail() { + deferred.reject(); + } + ); + } else { + deferred.reject(); + } + } else { + deferred.resolve(scope.entitySubtypes); + } + return deferred.promise; + } + + $compile(element.contents())(scope); + + $mdUtil.nextTick(function(){ + var inputElement = angular.element('input', element); + inputElement.on('blur', function() { + scope.inputTouched = true; + } ); + }); + + } + + return { + restrict: "E", + require: "^ngModel", + link: linker, + scope: { + disabled:'=ngDisabled', + tbRequired: '=?', + entityType: "=" + } + }; + +} diff --git a/ui/src/app/entity/entity-subtype-list.scss b/ui/src/app/entity/entity-subtype-list.scss new file mode 100644 index 0000000000..bbb2a1cc94 --- /dev/null +++ b/ui/src/app/entity/entity-subtype-list.scss @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*.tb-entity-subtype-list { + #entity_subtype_list_chips { + .md-chips { + padding-bottom: 1px; + } + } + .tb-error-messages { + margin-top: -11px; + height: 35px; + .tb-error-message { + padding-left: 1px; + } + } +}*/ diff --git a/ui/src/app/entity/entity-subtype-list.tpl.html b/ui/src/app/entity/entity-subtype-list.tpl.html new file mode 100644 index 0000000000..2a1519aa9a --- /dev/null +++ b/ui/src/app/entity/entity-subtype-list.tpl.html @@ -0,0 +1,54 @@ + + +
    + + + + {{item}} + + + {{noSubtypesMathingText}} + + + + + {{$chip}} + + + + +
    diff --git a/ui/src/app/entity/entity-type-list.directive.js b/ui/src/app/entity/entity-type-list.directive.js new file mode 100644 index 0000000000..cb07cfc877 --- /dev/null +++ b/ui/src/app/entity/entity-type-list.directive.js @@ -0,0 +1,111 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable import/no-unresolved, import/default */ + +import entityTypeListTemplate from './entity-type-list.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +import './entity-type-list.scss'; + +/*@ngInject*/ +export default function EntityTypeListDirective($compile, $templateCache, $q, $mdUtil, $translate, $filter, types, entityService) { + + var linker = function (scope, element, attrs, ngModelCtrl) { + + var template = $templateCache.get(entityTypeListTemplate); + element.html(template); + + scope.ngModelCtrl = ngModelCtrl; + + scope.placeholder = scope.tbRequired ? $translate.instant('entity.enter-entity-type') + : $translate.instant('entity.any-entity'); + scope.secondaryPlaceholder = '+' + $translate.instant('entity.entity-type'); + + var entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes); + scope.entityTypesList = []; + for (var type in entityTypes) { + var entityTypeInfo = {}; + entityTypeInfo.value = entityTypes[type]; + entityTypeInfo.name = $translate.instant(types.entityTypeTranslations[entityTypeInfo.value].type) + ''; + scope.entityTypesList.push(entityTypeInfo); + } + + scope.$watch('tbRequired', function () { + scope.updateValidity(); + }); + + scope.fetchEntityTypes = function(searchText) { + var deferred = $q.defer(); + var entityTypes = $filter('filter')(scope.entityTypesList, {name: searchText}); + deferred.resolve(entityTypes); + return deferred.promise; + } + + scope.updateValidity = function() { + var value = ngModelCtrl.$viewValue; + var valid = !scope.tbRequired || value && value.length > 0; + ngModelCtrl.$setValidity('entityTypeList', valid); + } + + ngModelCtrl.$render = function () { + scope.entityTypeList = []; + var value = ngModelCtrl.$viewValue; + if (value && value.length) { + value.forEach(function(type) { + var entityTypeInfo = {}; + entityTypeInfo.value = type; + entityTypeInfo.name = $translate.instant(types.entityTypeTranslations[entityTypeInfo.value].type) + ''; + scope.entityTypeList.push(entityTypeInfo); + }); + } + } + + scope.$watch('entityTypeList', function () { + var values = []; + if (scope.entityTypeList && scope.entityTypeList.length) { + scope.entityTypeList.forEach(function(entityType) { + values.push(entityType.value); + }); + } + ngModelCtrl.$setViewValue(values); + scope.updateValidity(); + }, true); + + $compile(element.contents())(scope); + + $mdUtil.nextTick(function(){ + var inputElement = angular.element('input', element); + inputElement.on('blur', function() { + scope.inputTouched = true; + } ); + }); + + } + + return { + restrict: "E", + require: "^ngModel", + link: linker, + scope: { + disabled:'=ngDisabled', + tbRequired: '=?', + allowedEntityTypes: '=?' + } + }; + +} diff --git a/ui/src/app/entity/entity-type-list.scss b/ui/src/app/entity/entity-type-list.scss new file mode 100644 index 0000000000..b94b992f77 --- /dev/null +++ b/ui/src/app/entity/entity-type-list.scss @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*.tb-entity-type-list { + #entity_type_list_chips { + .md-chips { + padding-bottom: 1px; + } + } + .tb-error-messages { + margin-top: -11px; + height: 35px; + .tb-error-message { + padding-left: 1px; + } + } +}*/ \ No newline at end of file diff --git a/ui/src/app/entity/entity-type-list.tpl.html b/ui/src/app/entity/entity-type-list.tpl.html new file mode 100644 index 0000000000..ff10a30d9b --- /dev/null +++ b/ui/src/app/entity/entity-type-list.tpl.html @@ -0,0 +1,54 @@ + + +
    + + + + {{item.name}} + + + entity.no-entity-types-matching + + + + + {{$chip.name}} + + + + +
    diff --git a/ui/src/app/entity/entity-type-select.directive.js b/ui/src/app/entity/entity-type-select.directive.js index bca2ee0cfe..068c1412bf 100644 --- a/ui/src/app/entity/entity-type-select.directive.js +++ b/ui/src/app/entity/entity-type-select.directive.js @@ -23,7 +23,7 @@ import entityTypeSelectTemplate from './entity-type-select.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ /*@ngInject*/ -export default function EntityTypeSelect($compile, $templateCache, utils, userService, types) { +export default function EntityTypeSelect($compile, $templateCache, utils, entityService, userService, types) { var linker = function (scope, element, attrs, ngModelCtrl) { var template = $templateCache.get(entityTypeSelectTemplate); @@ -39,36 +39,7 @@ export default function EntityTypeSelect($compile, $templateCache, utils, userSe scope.ngModelCtrl = ngModelCtrl; - var authority = userService.getAuthority(); - scope.entityTypes = {}; - switch(authority) { - case 'SYS_ADMIN': - scope.entityTypes.tenant = types.entityType.tenant; - scope.entityTypes.rule = types.entityType.rule; - scope.entityTypes.plugin = types.entityType.plugin; - break; - case 'TENANT_ADMIN': - scope.entityTypes.device = types.entityType.device; - scope.entityTypes.asset = types.entityType.asset; - scope.entityTypes.customer = types.entityType.customer; - scope.entityTypes.rule = types.entityType.rule; - scope.entityTypes.plugin = types.entityType.plugin; - scope.entityTypes.dashboard = types.entityType.dashboard; - break; - case 'CUSTOMER_USER': - scope.entityTypes.device = types.entityType.device; - scope.entityTypes.asset = types.entityType.asset; - scope.entityTypes.dashboard = types.entityType.dashboard; - break; - } - - if (scope.allowedEntityTypes) { - for (var entityType in scope.entityTypes) { - if (scope.allowedEntityTypes.indexOf(scope.entityTypes[entityType]) === -1) { - delete scope.entityTypes[entityType]; - } - } - } + scope.entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes); scope.typeName = function(type) { return type ? types.entityTypeTranslations[type].type : ''; diff --git a/ui/src/app/entity/index.js b/ui/src/app/entity/index.js index 2fc1d6300b..2b8d434a9e 100644 --- a/ui/src/app/entity/index.js +++ b/ui/src/app/entity/index.js @@ -14,9 +14,11 @@ * limitations under the License. */ -import EntityAliasesController from './entity-aliases.controller'; -import EntityAliasDialogController from './entity-alias-dialog.controller'; +import EntityAliasesController from './alias/entity-aliases.controller'; +import EntityAliasDialogController from './alias/entity-alias-dialog.controller'; import EntityTypeSelectDirective from './entity-type-select.directive'; +import EntityTypeListDirective from './entity-type-list.directive'; +import EntitySubtypeListDirective from './entity-subtype-list.directive'; import EntitySubtypeSelectDirective from './entity-subtype-select.directive'; import EntitySubtypeAutocompleteDirective from './entity-subtype-autocomplete.directive'; import EntityAutocompleteDirective from './entity-autocomplete.directive'; @@ -24,11 +26,12 @@ import EntityListDirective from './entity-list.directive'; import EntitySelectDirective from './entity-select.directive'; import EntityFilterDirective from './entity-filter.directive'; import EntityFilterViewDirective from './entity-filter-view.directive'; -import AliasesEntitySelectPanelController from './aliases-entity-select-panel.controller'; -import AliasesEntitySelectDirective from './aliases-entity-select.directive'; +import AliasesEntitySelectPanelController from './alias/aliases-entity-select-panel.controller'; +import AliasesEntitySelectDirective from './alias/aliases-entity-select.directive'; import AddAttributeDialogController from './attribute/add-attribute-dialog.controller'; import AddWidgetToDashboardDialogController from './attribute/add-widget-to-dashboard-dialog.controller'; import AttributeTableDirective from './attribute/attribute-table.directive'; +import RelationFiltersDirective from './relation/relation-filters.directive'; import RelationTableDirective from './relation/relation-table.directive'; import RelationTypeAutocompleteDirective from './relation/relation-type-autocomplete.directive'; @@ -39,6 +42,8 @@ export default angular.module('thingsboard.entity', []) .controller('AddAttributeDialogController', AddAttributeDialogController) .controller('AddWidgetToDashboardDialogController', AddWidgetToDashboardDialogController) .directive('tbEntityTypeSelect', EntityTypeSelectDirective) + .directive('tbEntityTypeList', EntityTypeListDirective) + .directive('tbEntitySubtypeList', EntitySubtypeListDirective) .directive('tbEntitySubtypeSelect', EntitySubtypeSelectDirective) .directive('tbEntitySubtypeAutocomplete', EntitySubtypeAutocompleteDirective) .directive('tbEntityAutocomplete', EntityAutocompleteDirective) @@ -48,6 +53,7 @@ export default angular.module('thingsboard.entity', []) .directive('tbEntityFilterView', EntityFilterViewDirective) .directive('tbAliasesEntitySelect', AliasesEntitySelectDirective) .directive('tbAttributeTable', AttributeTableDirective) + .directive('tbRelationFilters', RelationFiltersDirective) .directive('tbRelationTable', RelationTableDirective) .directive('tbRelationTypeAutocomplete', RelationTypeAutocompleteDirective) .name; diff --git a/ui/src/app/entity/relation/relation-filters.directive.js b/ui/src/app/entity/relation/relation-filters.directive.js new file mode 100644 index 0000000000..59821dd2ba --- /dev/null +++ b/ui/src/app/entity/relation/relation-filters.directive.js @@ -0,0 +1,85 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import './relation-filters.scss'; + +/* eslint-disable import/no-unresolved, import/default */ + +import relationFiltersTemplate from './relation-filters.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function RelationFilters($compile, $templateCache) { + + return { + restrict: "E", + require: "^ngModel", + scope: { + allowedEntityTypes: '=?' + }, + link: linker + }; + + function linker( scope, element, attrs, ngModelCtrl ) { + + var template = $templateCache.get(relationFiltersTemplate); + element.html(template); + + scope.relationFilters = []; + + scope.addFilter = addFilter; + scope.removeFilter = removeFilter; + + ngModelCtrl.$render = function () { + if (ngModelCtrl.$viewValue) { + var value = ngModelCtrl.$viewValue; + value.forEach(function (filter) { + scope.relationFilters.push(filter); + }); + } + scope.$watch('relationFilters', function (newVal, prevVal) { + if (!angular.equals(newVal, prevVal)) { + updateValue(); + } + }, true); + } + + function addFilter() { + var filter = { + relationType: null, + entityTypes: [] + }; + scope.relationFilters.push(filter); + } + + function removeFilter($event, filter) { + var index = scope.relationFilters.indexOf(filter); + if (index > -1) { + scope.relationFilters.splice(index, 1); + } + } + + function updateValue() { + var value = []; + scope.relationFilters.forEach(function (filter) { + value.push(filter); + }); + ngModelCtrl.$setViewValue(value); + } + $compile(element.contents())(scope); + } +} diff --git a/ui/src/app/entity/relation/relation-filters.scss b/ui/src/app/entity/relation/relation-filters.scss new file mode 100644 index 0000000000..50d49af3a8 --- /dev/null +++ b/ui/src/app/entity/relation/relation-filters.scss @@ -0,0 +1,77 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.tb-relation-filters { + .header { + padding-left: 5px; + padding-right: 5px; + padding-bottom: 5px; + .cell { + padding-left: 5px; + padding-right: 5px; + color: rgba(0,0,0,.54); + font-size: 12px; + font-weight: 700; + white-space: nowrap; + } + } + .body { + padding-left: 5px; + padding-right: 5px; + max-height: 300px; + overflow: auto; + padding-bottom: 20px; + .row { + padding-top: 5px; + } + .cell { + padding-left: 5px; + padding-right: 5px; + + md-select { + margin: 0 0 24px; + } + + md-input-container { + margin: 0; + } + + md-chips-wrap { + padding: 0px; + margin: 0 0 24px; + .md-chip-input-container { + margin: 0; + } + md-autocomplete { + height: 30px; + md-autocomplete-wrap { + height: 30px; + } + } + } + .md-chips .md-chip-input-container input { + padding: 2px 2px 2px; + height: 26px; + line-height: 26px; + } + + } + + .md-button { + margin: 0; + } + } +} \ No newline at end of file diff --git a/ui/src/app/entity/relation/relation-filters.tpl.html b/ui/src/app/entity/relation/relation-filters.tpl.html new file mode 100644 index 0000000000..679a4884a3 --- /dev/null +++ b/ui/src/app/entity/relation/relation-filters.tpl.html @@ -0,0 +1,67 @@ + +
    +
    +
    + relation.type + entity.entity-types +   +
    +
    +
    +
    +
    + + + + + + + {{ 'relation.remove-relation-filter' | translate }} + + + close + + +
    +
    +
    +
    + relation.any-relation +
    +
    + + + {{ 'relation.add-relation-filter' | translate }} + + + add + + {{ 'action.add' | translate }} + +
    +
    \ No newline at end of file diff --git a/ui/src/app/entity/relation/relation-type-autocomplete.directive.js b/ui/src/app/entity/relation/relation-type-autocomplete.directive.js index a7e5ea45bb..4b5480d61b 100644 --- a/ui/src/app/entity/relation/relation-type-autocomplete.directive.js +++ b/ui/src/app/entity/relation/relation-type-autocomplete.directive.js @@ -29,6 +29,8 @@ export default function RelationTypeAutocomplete($compile, $templateCache, $q, $ element.html(template); scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false; + scope.hideLabel = angular.isDefined(attrs.hideLabel) ? true : false; + scope.relationType = null; scope.relationTypeSearchText = ''; scope.relationTypes = []; diff --git a/ui/src/app/entity/relation/relation-type-autocomplete.tpl.html b/ui/src/app/entity/relation/relation-type-autocomplete.tpl.html index f71c13418a..39f32d6706 100644 --- a/ui/src/app/entity/relation/relation-type-autocomplete.tpl.html +++ b/ui/src/app/entity/relation/relation-type-autocomplete.tpl.html @@ -26,7 +26,7 @@ md-items="item in fetchRelationTypes(relationTypeSearchText)" md-item-text="item" md-min-length="0" - md-floating-label="{{ 'relation.relation-type' | translate }}" + md-floating-label="{{ tbRequired ? ('relation.relation-type' | translate) : ( !relationType ? ('relation.any-relation-type' | translate) : ' ') }}" md-select-on-match="true" md-menu-class="tb-relation-type-autocomplete"> diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js index 8d4a09828b..86d5240953 100644 --- a/ui/src/app/import-export/import-export.service.js +++ b/ui/src/app/import-export/import-export.service.js @@ -16,7 +16,7 @@ /* eslint-disable import/no-unresolved, import/default */ import importDialogTemplate from './import-dialog.tpl.html'; -import entityAliasesTemplate from '../entity/entity-aliases.tpl.html'; +import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index 88a6021568..cd21747fe0 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -129,14 +129,24 @@ export default angular.module('thingsboard.locale', []) "filter-type-device-type-description": "Devices of type '{{deviceType}}'", "filter-type-device-type-and-name-description": "Devices of type '{{deviceType}}' and with name starting with '{{prefix}}'", "filter-type-relations-query": "Relations query", + "filter-type-relations-query-description": "{{entities}} that have {{relationType}} relation {{direction}} {{rootEntity}}", "filter-type-asset-search-query": "Asset search query", + "filter-type-asset-search-query-description": "Assets with types {{assetTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", "filter-type-device-search-query": "Device search query", + "filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", "entity-filter": "Entity filter", "resolve-multiple": "Resolve as multiple entities", "filter-type": "Filter type", "filter-type-required": "Filter type is required.", "entity-filter-no-entity-matched": "No entities matching specified filter were found.", - "no-entity-filter-specified": "No entity filter specified" + "no-entity-filter-specified": "No entity filter specified", + "root-state-entity": "Use dashboard state entity as root", + "root-entity": "Root entity", + "max-relation-level": "Max relation level", + "unlimited-level": "Unlimited level", + "state-entity": "Dashboard state entity", + "all-entities": "All entities", + "any-relation": "any" }, "asset": { "asset": "Asset", @@ -159,6 +169,11 @@ export default angular.module('thingsboard.locale', []) "asset-type": "Asset type", "asset-type-required": "Asset type is required.", "select-asset-type": "Select asset type", + "enter-asset-type": "Enter asset type", + "any-asset": "Any asset", + "no-asset-types-matching": "No asset types matching '{{entitySubtype}}' were found.", + "asset-type-list-empty": "No asset types selected.", + "asset-types": "Asset types", "name": "Name", "name-required": "Name is required.", "description": "Description", @@ -444,6 +459,7 @@ export default angular.module('thingsboard.locale', []) }, "datasource": { "type": "Datasource type", + "name": "Name", "add-datasource-prompt": "Please add datasource" }, "details": { @@ -524,6 +540,11 @@ export default angular.module('thingsboard.locale', []) "device-type": "Device type", "device-type-required": "Device type is required.", "select-device-type": "Select device type", + "enter-device-type": "Enter device type", + "any-device": "Any device", + "no-device-types-matching": "No device types matching '{{entitySubtype}}' were found.", + "device-type-list-empty": "No device types selected.", + "device-types": "Device types", "name": "Name", "name-required": "Name is required.", "description": "Description", @@ -564,10 +585,17 @@ export default angular.module('thingsboard.locale', []) "remove-alias": "Remove entity alias", "add-alias": "Add entity alias", "entity-list": "Entity list", + "entity-type": "Entity type", + "entity-types": "Entity types", + "entity-type-list": "Entity type list", + "any-entity": "Any entity", + "enter-entity-type": "Enter entity type", "no-entities-matching": "No entities matching '{{entity}}' were found.", + "no-entity-types-matching": "No entity types matching '{{entityType}}' were found.", "name-starts-with": "Name starts with", "use-entity-name-filter": "Use filter", "entity-list-empty": "No entities selected.", + "entity-type-list-empty": "No entity types selected.", "entity-name-filter-required": "Entity name filter is required.", "entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.", "all-subtypes": "All", @@ -581,30 +609,39 @@ export default angular.module('thingsboard.locale', []) "type": "Type", "type-required": "Entity type is required.", "type-device": "Device", + "type-devices": "Devices", "list-of-devices": "{ count, select, 1 {One device} other {List of # devices} }", "device-name-starts-with": "Devices whose names start with '{{prefix}}'", "type-asset": "Asset", + "type-assets": "Assets", "list-of-assets": "{ count, select, 1 {One asset} other {List of # assets} }", "asset-name-starts-with": "Assets whose names start with '{{prefix}}'", "type-rule": "Rule", + "type-rules": "Rules", "list-of-rules": "{ count, select, 1 {One rule} other {List of # rules} }", "rule-name-starts-with": "Rules whose names start with '{{prefix}}'", "type-plugin": "Plugin", + "type-plugins": "Plugins", "list-of-plugins": "{ count, select, 1 {One plugin} other {List of # plugins} }", "plugin-name-starts-with": "Plugins whose names start with '{{prefix}}'", "type-tenant": "Tenant", + "type-tenants": "Tenants", "list-of-tenants": "{ count, select, 1 {One tenant} other {List of # tenants} }", "tenant-name-starts-with": "Tenants whose names start with '{{prefix}}'", "type-customer": "Customer", + "type-customers": "Customers", "list-of-customers": "{ count, select, 1 {One customer} other {List of # customers} }", "customer-name-starts-with": "Customers whose names start with '{{prefix}}'", "type-user": "User", + "type-users": "Users", "list-of-users": "{ count, select, 1 {One user} other {List of # users} }", "user-name-starts-with": "Users whose names start with '{{prefix}}'", "type-dashboard": "Dashboard", + "type-dashboards": "Dashboards", "list-of-dashboards": "{ count, select, 1 {One dashboard} other {List of # dashboards} }", "dashboard-name-starts-with": "Dashboards whose names start with '{{prefix}}'", "type-alarm": "Alarm", + "type-alarms": "Alarms", "list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }", "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'" }, @@ -770,6 +807,10 @@ export default angular.module('thingsboard.locale', []) "FROM": "From", "TO": "To" }, + "direction-type": { + "FROM": "from", + "TO": "to" + }, "from-relations": "Outbound relations", "to-relations": "Inbound relations", "selected-relations": "{ count, select, 1 {1 relation} other {# relations} } selected", @@ -783,6 +824,7 @@ export default angular.module('thingsboard.locale', []) "delete": "Delete relation", "relation-type": "Relation type", "relation-type-required": "Relation type is required.", + "any-relation-type": "Any type", "add": "Add relation", "delete-to-relation-title": "Are you sure you want to delete relation to the entity '{{entityName}}'?", "delete-to-relation-text": "Be careful, after the confirmation the entity '{{entityName}}' will be unrelated from the current entity.", @@ -791,7 +833,11 @@ export default angular.module('thingsboard.locale', []) "delete-from-relation-title": "Are you sure you want to delete relation from the entity '{{entityName}}'?", "delete-from-relation-text": "Be careful, after the confirmation current entity will be unrelated from the entity '{{entityName}}'.", "delete-from-relations-title": "Are you sure you want to delete { count, select, 1 {1 relation} other {# relations} }?", - "delete-from-relations-text": "Be careful, after the confirmation all selected relations will be removed and current entity will be unrelated from the corresponding entities." + "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.", + "remove-relation-filter": "Remove relation filter", + "add-relation-filter": "Add relation filter", + "any-relation": "Any relation", + "relation-filters": "Relation filters" }, "rule": { "rule": "Rule", diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss index dbe3543efa..bf444937a4 100644 --- a/ui/src/scss/main.scss +++ b/ui/src/scss/main.scss @@ -236,6 +236,15 @@ div { } } +.md-caption { + &.tb-required:after { + content: ' *'; + font-size: 10px; + vertical-align: top; + color: rgba(0,0,0,0.54); + } +} + pre.tb-highlight { background-color: #f7f7f7; display: block; From 351684a6cdefc028213ace04234b49e6dc13661d Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 12 Jun 2017 18:30:46 +0300 Subject: [PATCH 6/7] TB-63: Update Alarms search query with AlarmSearchStatus parameter. --- .../server/controller/AlarmController.java | 18 +++-- .../server/common/data/alarm/Alarm.java | 15 ++++ .../server/common/data/alarm/AlarmInfo.java | 58 ++++++++++++++ .../server/common/data/alarm/AlarmQuery.java | 2 + .../common/data/alarm/AlarmSearchStatus.java | 23 ++++++ .../server/common/data/alarm/AlarmStatus.java | 9 +++ .../server/dao/alarm/AlarmDao.java | 3 +- .../server/dao/alarm/AlarmDaoImpl.java | 25 ++++-- .../server/dao/alarm/AlarmService.java | 3 +- .../server/dao/alarm/BaseAlarmService.java | 79 ++++++++++++++----- .../server/dao/relation/BaseRelationDao.java | 11 ++- .../server/dao/service/AlarmServiceTest.java | 7 +- ui/src/app/api/alarm.service.js | 13 ++- ui/src/app/common/types.constant.js | 7 ++ 14 files changed, 230 insertions(+), 43 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmSearchStatus.java diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 7a2c273002..19774bbf42 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -22,10 +22,7 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Event; -import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.alarm.AlarmId; -import org.thingsboard.server.common.data.alarm.AlarmQuery; -import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.alarm.*; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.page.TextPageData; @@ -103,24 +100,31 @@ public class AlarmController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm/{entityType}/{entityId}", method = RequestMethod.GET) @ResponseBody - public TimePageData getAlarms( + public TimePageData getAlarms( @PathVariable("entityType") String strEntityType, @PathVariable("entityId") String strEntityId, + @RequestParam(required = false) String searchStatus, @RequestParam(required = false) String status, @RequestParam int limit, @RequestParam(required = false) Long startTime, @RequestParam(required = false) Long endTime, @RequestParam(required = false, defaultValue = "false") boolean ascOrder, - @RequestParam(required = false) String offset + @RequestParam(required = false) String offset, + @RequestParam(required = false) Boolean fetchOriginator ) throws ThingsboardException { checkParameter("EntityId", strEntityId); checkParameter("EntityType", strEntityType); EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId); + AlarmSearchStatus alarmSearchStatus = StringUtils.isEmpty(searchStatus) ? null : AlarmSearchStatus.valueOf(searchStatus); AlarmStatus alarmStatus = StringUtils.isEmpty(status) ? null : AlarmStatus.valueOf(status); + if (alarmSearchStatus != null && alarmStatus != null) { + throw new ThingsboardException("Invalid alarms search query: Both parameters 'searchStatus' " + + "and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } checkEntityId(entityId); try { TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); - return checkNotNull(alarmService.findAlarms(new AlarmQuery(entityId, pageLink, alarmStatus)).get()); + return checkNotNull(alarmService.findAlarms(new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get()); } catch (Exception e) { throw handleException(e); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java index 09695e5ee8..9c6f998a3f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java @@ -53,6 +53,21 @@ public class Alarm extends BaseData implements HasName { super(id); } + public Alarm(Alarm alarm) { + super(alarm.getId()); + this.tenantId = alarm.getTenantId(); + this.type = alarm.getType(); + this.originator = alarm.getOriginator(); + this.severity = alarm.getSeverity(); + this.status = alarm.getStatus(); + this.startTs = alarm.getStartTs(); + this.endTs = alarm.getEndTs(); + this.ackTs = alarm.getAckTs(); + this.clearTs = alarm.getClearTs(); + this.details = alarm.getDetails(); + this.propagate = alarm.isPropagate(); + } + @Override @JsonProperty(access = JsonProperty.Access.READ_ONLY) public String getName() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java new file mode 100644 index 0000000000..ef24f1c2c0 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java @@ -0,0 +1,58 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.alarm; + +public class AlarmInfo extends Alarm { + + private static final long serialVersionUID = 2807343093519543363L; + + private String originatorName; + + public AlarmInfo() { + super(); + } + + public AlarmInfo(Alarm alarm) { + super(alarm); + } + + public String getOriginatorName() { + return originatorName; + } + + public void setOriginatorName(String originatorName) { + this.originatorName = originatorName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + + AlarmInfo alarmInfo = (AlarmInfo) o; + + return originatorName != null ? originatorName.equals(alarmInfo.originatorName) : alarmInfo.originatorName == null; + + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (originatorName != null ? originatorName.hashCode() : 0); + return result; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java index 00ca6c34ab..55019c8e33 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java @@ -32,6 +32,8 @@ public class AlarmQuery { private EntityId affectedEntityId; private TimePageLink pageLink; + private AlarmSearchStatus searchStatus; private AlarmStatus status; + private Boolean fetchOriginator; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmSearchStatus.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmSearchStatus.java new file mode 100644 index 0000000000..c09b1fd37e --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmSearchStatus.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thingsboard.server.common.data.alarm; + +public enum AlarmSearchStatus { + + ANY, ACTIVE, CLEARED, ACK, UNACK + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmStatus.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmStatus.java index 0f1b346f2c..a8704b2f58 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmStatus.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmStatus.java @@ -30,4 +30,13 @@ public enum AlarmStatus { return this == CLEARED_ACK || this == CLEARED_UNACK; } + public AlarmSearchStatus getClearSearchStatus() { + return this.isCleared() ? AlarmSearchStatus.CLEARED : AlarmSearchStatus.ACTIVE; + } + + public AlarmSearchStatus getAckSearchStatus() { + return this.isAck() ? AlarmSearchStatus.ACK : AlarmSearchStatus.UNACK; + } + + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java index 9fdd92e4cf..289d5c9e59 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.alarm; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -37,5 +38,5 @@ public interface AlarmDao extends Dao { AlarmEntity save(Alarm alarm); - ListenableFuture> findAlarms(AlarmQuery query); + ListenableFuture> findAlarms(AlarmQuery query); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDaoImpl.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDaoImpl.java index 1362e6b3a9..72fbae5e6f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDaoImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDaoImpl.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.alarm; import com.datastax.driver.core.querybuilder.QueryBuilder; import com.datastax.driver.core.querybuilder.Select; +import com.google.common.base.Function; import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -25,7 +26,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmQuery; +import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.relation.EntityRelation; @@ -94,15 +97,25 @@ public class AlarmDaoImpl extends AbstractModelDao implements Alarm } @Override - public ListenableFuture> findAlarms(AlarmQuery query) { - log.trace("Try to find alarms by entity [{}], status [{}] and pageLink [{}]", query.getAffectedEntityId(), query.getStatus(), query.getPageLink()); + public ListenableFuture> findAlarms(AlarmQuery query) { + log.trace("Try to find alarms by entity [{}], searchStatus [{}], status [{}] and pageLink [{}]", query.getAffectedEntityId(), query.getSearchStatus(), query.getStatus(), query.getPageLink()); EntityId affectedEntity = query.getAffectedEntityId(); - String relationType = query.getStatus() == null ? BaseAlarmService.ALARM_RELATION : BaseAlarmService.ALARM_RELATION_PREFIX + query.getStatus().name(); + String searchStatusName; + if (query.getSearchStatus() == null && query.getStatus() == null) { + searchStatusName = AlarmSearchStatus.ANY.name(); + } else if (query.getSearchStatus() != null) { + searchStatusName = query.getSearchStatus().name(); + } else { + searchStatusName = query.getStatus().name(); + } + String relationType = BaseAlarmService.ALARM_RELATION_PREFIX + searchStatusName; ListenableFuture> relations = relationDao.findRelations(affectedEntity, relationType, RelationTypeGroup.ALARM, EntityType.ALARM, query.getPageLink()); - return Futures.transform(relations, (AsyncFunction, List>) input -> { - List> alarmFutures = new ArrayList<>(input.size()); + return Futures.transform(relations, (AsyncFunction, List>) input -> { + List> alarmFutures = new ArrayList<>(input.size()); for (EntityRelation relation : input) { - alarmFutures.add(findAlarmByIdAsync(relation.getTo().getId())); + alarmFutures.add(Futures.transform( + findAlarmByIdAsync(relation.getTo().getId()), (Function) + alarm1 -> new AlarmInfo(alarm1))); } return Futures.successfulAsList(alarmFutures); }); diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java index 5399d9dd7f..fb8a80d611 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.alarm; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmId; +import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.page.TimePageData; @@ -34,6 +35,6 @@ public interface AlarmService { ListenableFuture findAlarmByIdAsync(AlarmId alarmId); - ListenableFuture> findAlarms(AlarmQuery query); + ListenableFuture> findAlarms(AlarmQuery query); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 3981f64701..d152d96767 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -17,22 +17,21 @@ package org.thingsboard.server.dao.alarm; import com.google.common.base.Function; +import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.alarm.AlarmId; -import org.thingsboard.server.common.data.alarm.AlarmQuery; -import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.alarm.*; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.BaseEntityService; +import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.model.*; import org.thingsboard.server.dao.relation.EntityRelationsQuery; @@ -45,6 +44,7 @@ import org.thingsboard.server.dao.tenant.TenantDao; import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -59,7 +59,6 @@ import static org.thingsboard.server.dao.service.Validator.*; public class BaseAlarmService extends AbstractEntityService implements AlarmService { public static final String ALARM_RELATION_PREFIX = "ALARM_"; - public static final String ALARM_RELATION = "ALARM_ANY"; @Autowired private AlarmDao alarmDao; @@ -70,6 +69,9 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @Autowired private RelationService relationService; + @Autowired + private EntityService entityService; + protected ExecutorService readResultsProcessingExecutor; @PostConstruct @@ -116,11 +118,9 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ query.setParameters(new RelationsSearchParameters(saved.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE)); List parentEntities = relationService.findByQuery(query).get().stream().map(r -> r.getFrom()).collect(Collectors.toList()); for (EntityId parentId : parentEntities) { - createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION, RelationTypeGroup.ALARM)); - createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION_PREFIX + saved.getStatus().name(), RelationTypeGroup.ALARM)); + createAlarmRelation(parentId, saved.getId(), saved.getStatus(), true); } - createRelation(new EntityRelation(alarm.getOriginator(), saved.getId(), ALARM_RELATION, RelationTypeGroup.ALARM)); - createRelation(new EntityRelation(alarm.getOriginator(), saved.getId(), ALARM_RELATION_PREFIX + saved.getStatus().name(), RelationTypeGroup.ALARM)); + createAlarmRelation(alarm.getOriginator(), saved.getId(), saved.getStatus(), true); return saved; } @@ -199,12 +199,27 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ } @Override - public ListenableFuture> findAlarms(AlarmQuery query) { - ListenableFuture> alarms = alarmDao.findAlarms(query); - return Futures.transform(alarms, new Function, TimePageData>() { + public ListenableFuture> findAlarms(AlarmQuery query) { + ListenableFuture> alarms = alarmDao.findAlarms(query); + if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) { + alarms = Futures.transform(alarms, (AsyncFunction, List>) input -> { + List> alarmFutures = new ArrayList<>(input.size()); + for (AlarmInfo alarmInfo : input) { + alarmFutures.add(Futures.transform( + entityService.fetchEntityNameAsync(alarmInfo.getOriginator()), (Function) + originatorName -> { + alarmInfo.setOriginatorName(originatorName); + return alarmInfo; + } + )); + } + return Futures.successfulAsList(alarmFutures); + }); + } + return Futures.transform(alarms, new Function, TimePageData>() { @Nullable @Override - public TimePageData apply(@Nullable List alarms) { + public TimePageData apply(@Nullable List alarms) { return new TimePageData<>(alarms, query.getPageLink()); } }); @@ -245,17 +260,45 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ query.setParameters(new RelationsSearchParameters(alarm.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE)); List parentEntities = relationService.findByQuery(query).get().stream().map(r -> r.getFrom()).collect(Collectors.toList()); for (EntityId parentId : parentEntities) { - deleteRelation(new EntityRelation(parentId, alarm.getId(), ALARM_RELATION_PREFIX + oldStatus.name(), RelationTypeGroup.ALARM)); - createRelation(new EntityRelation(parentId, alarm.getId(), ALARM_RELATION_PREFIX + newStatus.name(), RelationTypeGroup.ALARM)); - } - deleteRelation(new EntityRelation(alarm.getOriginator(), alarm.getId(), ALARM_RELATION_PREFIX + oldStatus.name(), RelationTypeGroup.ALARM)); - createRelation(new EntityRelation(alarm.getOriginator(), alarm.getId(), ALARM_RELATION_PREFIX + newStatus.name(), RelationTypeGroup.ALARM)); + updateAlarmRelation(parentId, alarm.getId(), oldStatus, newStatus); + } + updateAlarmRelation(alarm.getOriginator(), alarm.getId(), oldStatus, newStatus); } catch (ExecutionException | InterruptedException e) { log.warn("[{}] Failed to update relations. Old status: [{}], New status: [{}]", alarm.getId(), oldStatus, newStatus); throw new RuntimeException(e); } } + private void createAlarmRelation(EntityId entityId, EntityId alarmId, AlarmStatus status, boolean createAnyRelation) { + try { + if (createAnyRelation) { + createRelation(new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + AlarmSearchStatus.ANY.name(), RelationTypeGroup.ALARM)); + } + createRelation(new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.name(), RelationTypeGroup.ALARM)); + createRelation(new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getClearSearchStatus().name(), RelationTypeGroup.ALARM)); + createRelation(new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getAckSearchStatus().name(), RelationTypeGroup.ALARM)); + } catch (ExecutionException | InterruptedException e) { + log.warn("[{}] Failed to create relation. Status: [{}]", alarmId, status); + throw new RuntimeException(e); + } + } + + private void deleteAlarmRelation(EntityId entityId, EntityId alarmId, AlarmStatus status) { + try { + deleteRelation(new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.name(), RelationTypeGroup.ALARM)); + deleteRelation(new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getClearSearchStatus().name(), RelationTypeGroup.ALARM)); + deleteRelation(new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getAckSearchStatus().name(), RelationTypeGroup.ALARM)); + } catch (ExecutionException | InterruptedException e) { + log.warn("[{}] Failed to delete relation. Status: [{}]", alarmId, status); + throw new RuntimeException(e); + } + } + + private void updateAlarmRelation(EntityId entityId, EntityId alarmId, AlarmStatus oldStatus, AlarmStatus newStatus) { + deleteAlarmRelation(entityId, alarmId, oldStatus); + createAlarmRelation(entityId, alarmId, newStatus, false); + } + private ListenableFuture getAndUpdate(AlarmId alarmId, Function function) { validateId(alarmId, "Alarm id should be specified!"); ListenableFuture entity = alarmDao.findAlarmByIdAsync(alarmId.getId()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java index 9fc2382d8d..77f79e1bc4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java @@ -178,9 +178,14 @@ public class BaseRelationDao extends AbstractAsyncDao implements RelationDao { eq(ModelConstants.RELATION_TYPE_GROUP_PROPERTY, typeGroup.name()), eq(ModelConstants.RELATION_TYPE_PROPERTY, relationType), eq(ModelConstants.RELATION_TO_TYPE_PROPERTY, childType.name())), - Arrays.asList(QueryBuilder.asc(ModelConstants.RELATION_TYPE_GROUP_PROPERTY), - QueryBuilder.asc(ModelConstants.RELATION_TYPE_PROPERTY), - QueryBuilder.asc(ModelConstants.RELATION_TO_TYPE_PROPERTY)), + Arrays.asList( + pageLink.isAscOrder() ? QueryBuilder.desc(ModelConstants.RELATION_TYPE_GROUP_PROPERTY) : + QueryBuilder.asc(ModelConstants.RELATION_TYPE_GROUP_PROPERTY), + pageLink.isAscOrder() ? QueryBuilder.desc(ModelConstants.RELATION_TYPE_PROPERTY) : + QueryBuilder.asc(ModelConstants.RELATION_TYPE_PROPERTY), + pageLink.isAscOrder() ? QueryBuilder.desc(ModelConstants.RELATION_TO_TYPE_PROPERTY) : + QueryBuilder.asc(ModelConstants.RELATION_TO_TYPE_PROPERTY) + ), pageLink, ModelConstants.RELATION_TO_ID_PROPERTY); return getFuture(executeAsyncRead(query), rs -> getEntityRelations(rs)); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java index 3b72574302..b22df63e3f 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java @@ -22,10 +22,7 @@ import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.alarm.AlarmQuery; -import org.thingsboard.server.common.data.alarm.AlarmSeverity; -import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.alarm.*; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; @@ -117,7 +114,7 @@ public class AlarmServiceTest extends AbstractServiceTest { Alarm created = alarmService.createOrUpdateAlarm(alarm); // Check child relation - TimePageData alarms = alarmService.findAlarms(AlarmQuery.builder() + TimePageData alarms = alarmService.findAlarms(AlarmQuery.builder() .affectedEntityId(childId) .status(AlarmStatus.ACTIVE_UNACK).pageLink( new TimePageLink(1, 0L, System.currentTimeMillis(), false) diff --git a/ui/src/app/api/alarm.service.js b/ui/src/app/api/alarm.service.js index d9ab4ff338..ca892f292b 100644 --- a/ui/src/app/api/alarm.service.js +++ b/ui/src/app/api/alarm.service.js @@ -91,7 +91,7 @@ function AlarmService($http, $q, $interval, $filter) { return deferred.promise; } - function getAlarms(entityType, entityId, pageLink, alarmStatus, ascOrder, config) { + function getAlarms(entityType, entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator, ascOrder, config) { var deferred = $q.defer(); var url = '/api/alarm/' + entityType + '/' + entityId + '?limit=' + pageLink.limit; @@ -104,9 +104,15 @@ function AlarmService($http, $q, $interval, $filter) { if (angular.isDefined(pageLink.idOffset)) { url += '&offset=' + pageLink.idOffset; } + if (alarmSearchStatus) { + url += '&searchStatus=' + alarmSearchStatus; + } if (alarmStatus) { url += '&status=' + alarmStatus; } + if (fetchOriginator) { + url += '&fetchOriginator=' + ((fetchOriginator===true) ? 'true' : 'false'); + } if (angular.isDefined(ascOrder) && ascOrder != null) { url += '&ascOrder=' + (ascOrder ? 'true' : 'false'); } @@ -121,7 +127,8 @@ function AlarmService($http, $q, $interval, $filter) { function fetchAlarms(alarmsQuery, pageLink, deferred, alarmsList) { getAlarms(alarmsQuery.entityType, alarmsQuery.entityId, - pageLink, alarmsQuery.alarmStatus, false, {ignoreLoading: true}).then( + pageLink, alarmsQuery.alarmSearchStatus, alarmsQuery.alarmStatus, + alarmsQuery.fetchOriginator, false, {ignoreLoading: true}).then( function success(alarms) { if (!alarmsList) { alarmsList = []; @@ -171,7 +178,9 @@ function AlarmService($http, $q, $interval, $filter) { var alarmsQuery = { entityType: entityType, entityId: entityId, + alarmSearchStatus: null, alarmStatus: alarmStatus, + fetchOriginator: false, interval: interval, limit: limit, onAlarms: onAlarms diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index fe3f73ca88..20760937a3 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -65,6 +65,13 @@ export default angular.module('thingsboard.types', []) clearedUnack: "CLEARED_UNACK", clearedAck: "CLEARED_ACK" }, + alarmSearchStatus: { + any: "ANY", + active: "ACTIVE", + cleared: "CLEARED", + ack: "ACK", + unack: "UNACK" + }, aliasFilterType: { entityList: { value: 'entityList', From 3e0ef116780e6845745adf5e2f95e9cb7f01a892 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 13 Jun 2017 12:33:34 +0300 Subject: [PATCH 7/7] TB-63: Add new 'Alarms' tab in entity details. --- .../server/controller/AlarmController.java | 13 ++ .../server/controller/BaseController.java | 12 + .../server/common/data/alarm/Alarm.java | 1 + .../server/dao/alarm/AlarmService.java | 2 + .../server/dao/alarm/BaseAlarmService.java | 17 ++ .../alarm/alarm-details-dialog.controller.js | 134 +++++++++++ ui/src/app/alarm/alarm-details-dialog.scss | 27 +++ .../app/alarm/alarm-details-dialog.tpl.html | 107 +++++++++ ui/src/app/alarm/alarm-header.directive.js | 39 ++++ .../alarm-header.tpl.html} | 9 +- ui/src/app/alarm/alarm-row.directive.js | 67 ++++++ .../alarm-row.tpl.html} | 17 +- ui/src/app/alarm/alarm-table.directive.js | 211 ++++++++++++++++++ ui/src/app/alarm/alarm-table.tpl.html | 49 ++++ ui/src/app/alarm/alarm.scss | 78 +++++++ ui/src/app/alarm/index.js | 27 +++ ui/src/app/api/alarm.service.js | 16 ++ ui/src/app/asset/assets.tpl.html | 7 +- ui/src/app/asset/index.js | 2 - ui/src/app/common/types.constant.js | 26 ++- ui/src/app/customer/customers.tpl.html | 7 +- ui/src/app/device/devices.tpl.html | 7 +- ui/src/app/device/index.js | 2 - .../event/event-content-dialog.controller.js | 2 +- .../app/event/event-content-dialog.tpl.html | 2 +- ui/src/app/event/event-header.directive.js | 4 - ui/src/app/event/event-row.directive.js | 4 - ui/src/app/event/event-table.tpl.html | 2 +- ui/src/app/event/event.scss | 4 +- ui/src/app/layout/index.js | 4 + ui/src/app/locale/locale.constant-es.js | 1 - ui/src/app/locale/locale.constant-ko.js | 1 - ui/src/app/locale/locale.constant-ru.js | 1 - ui/src/app/locale/locale.constant-zh.js | 1 - ui/src/app/locale/locale.constant.js | 37 ++- ui/src/app/plugin/index.js | 2 - ui/src/app/plugin/plugins.tpl.html | 8 +- ui/src/app/rule/index.js | 2 - ui/src/app/rule/rules.tpl.html | 8 +- ui/src/app/tenant/tenants.tpl.html | 7 +- ui/src/scss/main.scss | 18 ++ 41 files changed, 937 insertions(+), 48 deletions(-) create mode 100644 ui/src/app/alarm/alarm-details-dialog.controller.js create mode 100644 ui/src/app/alarm/alarm-details-dialog.scss create mode 100644 ui/src/app/alarm/alarm-details-dialog.tpl.html create mode 100644 ui/src/app/alarm/alarm-header.directive.js rename ui/src/app/{event/event-header-alarm.tpl.html => alarm/alarm-header.tpl.html} (62%) create mode 100644 ui/src/app/alarm/alarm-row.directive.js rename ui/src/app/{event/event-row-alarm.tpl.html => alarm/alarm-row.tpl.html} (59%) create mode 100644 ui/src/app/alarm/alarm-table.directive.js create mode 100644 ui/src/app/alarm/alarm-table.tpl.html create mode 100644 ui/src/app/alarm/alarm.scss create mode 100644 ui/src/app/alarm/index.js diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 19774bbf42..19c75e7b9b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -57,6 +57,19 @@ public class AlarmController extends BaseController { } } + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/alarm/info/{alarmId}", method = RequestMethod.GET) + @ResponseBody + public AlarmInfo getAlarmInfoById(@PathVariable("alarmId") String strAlarmId) throws ThingsboardException { + checkParameter("alarmId", strAlarmId); + try { + AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); + return checkAlarmInfoId(alarmId); + } catch (Exception e) { + throw handleException(e); + } + } + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm", method = RequestMethod.POST) @ResponseBody diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index aba4b6e162..c63dda7b3a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -27,6 +27,7 @@ import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.common.data.*; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmId; +import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.page.TextPageLink; @@ -351,6 +352,17 @@ public abstract class BaseController { } } + AlarmInfo checkAlarmInfoId(AlarmId alarmId) throws ThingsboardException { + try { + validateId(alarmId, "Incorrect alarmId " + alarmId); + AlarmInfo alarmInfo = alarmService.findAlarmInfoByIdAsync(alarmId).get(); + checkAlarm(alarmInfo); + return alarmInfo; + } catch (Exception e) { + throw handleException(e, false); + } + } + protected void checkAlarm(Alarm alarm) throws ThingsboardException { checkNotNull(alarm); checkTenantId(alarm.getTenantId()); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java index 9c6f998a3f..e48cf5b7a8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java @@ -55,6 +55,7 @@ public class Alarm extends BaseData implements HasName { public Alarm(Alarm alarm) { super(alarm.getId()); + this.createdTime = alarm.getCreatedTime(); this.tenantId = alarm.getTenantId(); this.type = alarm.getType(); this.originator = alarm.getOriginator(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java index fb8a80d611..3556d51f09 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java @@ -35,6 +35,8 @@ public interface AlarmService { ListenableFuture findAlarmByIdAsync(AlarmId alarmId); + ListenableFuture findAlarmInfoByIdAsync(AlarmId alarmId); + ListenableFuture> findAlarms(AlarmQuery query); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index d152d96767..58f7316a75 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -198,6 +198,23 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ return alarmDao.findAlarmByIdAsync(alarmId.getId()); } + @Override + public ListenableFuture findAlarmInfoByIdAsync(AlarmId alarmId) { + log.trace("Executing findAlarmInfoByIdAsync [{}]", alarmId); + validateId(alarmId, "Incorrect alarmId " + alarmId); + return Futures.transform(alarmDao.findAlarmByIdAsync(alarmId.getId()), + (AsyncFunction) alarm1 -> { + AlarmInfo alarmInfo = new AlarmInfo(alarm1); + return Futures.transform( + entityService.fetchEntityNameAsync(alarmInfo.getOriginator()), (Function) + originatorName -> { + alarmInfo.setOriginatorName(originatorName); + return alarmInfo; + } + ); + }); + } + @Override public ListenableFuture> findAlarms(AlarmQuery query) { ListenableFuture> alarms = alarmDao.findAlarms(query); diff --git a/ui/src/app/alarm/alarm-details-dialog.controller.js b/ui/src/app/alarm/alarm-details-dialog.controller.js new file mode 100644 index 0000000000..0cc05ef295 --- /dev/null +++ b/ui/src/app/alarm/alarm-details-dialog.controller.js @@ -0,0 +1,134 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import 'brace/ext/language_tools'; +import 'brace/mode/json'; +import 'brace/theme/github'; +import beautify from 'js-beautify'; + +import './alarm-details-dialog.scss'; + +const js_beautify = beautify.js; + +/*@ngInject*/ +export default function AlarmDetailsDialogController($mdDialog, $filter, $translate, types, alarmService, alarmId, showingCallback) { + + var vm = this; + + vm.alarmId = alarmId; + vm.types = types; + vm.alarm = null; + + vm.alarmUpdated = false; + + showingCallback.onShowing = function(scope, element) { + updateEditorSize(element); + } + + vm.alarmDetailsOptions = { + useWrapMode: false, + mode: 'json', + showGutter: false, + showPrintMargin: false, + theme: 'github', + advanced: { + enableSnippets: false, + enableBasicAutocompletion: false, + enableLiveAutocompletion: false + }, + onLoad: function (_ace) { + vm.editor = _ace; + } + }; + + vm.close = close; + vm.acknowledge = acknowledge; + vm.clear = clear; + + loadAlarm(); + + function updateEditorSize(element) { + var newWidth = 600; + var newHeight = 200; + angular.element('#tb-alarm-details', element).height(newHeight.toString() + "px") + .width(newWidth.toString() + "px"); + vm.editor.resize(); + } + + function loadAlarm() { + alarmService.getAlarmInfo(vm.alarmId).then( + function success(alarm) { + vm.alarm = alarm; + loadAlarmFields(); + }, + function fail() { + vm.alarm = null; + } + ); + } + + function loadAlarmFields() { + vm.createdTime = $filter('date')(vm.alarm.createdTime, 'yyyy-MM-dd HH:mm:ss'); + vm.startTime = null; + if (vm.alarm.startTs) { + vm.startTime = $filter('date')(vm.alarm.startTs, 'yyyy-MM-dd HH:mm:ss'); + } + vm.endTime = null; + if (vm.alarm.endTs) { + vm.endTime = $filter('date')(vm.alarm.endTs, 'yyyy-MM-dd HH:mm:ss'); + } + vm.ackTime = null; + if (vm.alarm.ackTs) { + vm.ackTime = $filter('date')(vm.alarm.ackTs, 'yyyy-MM-dd HH:mm:ss') + } + vm.clearTime = null; + if (vm.alarm.clearTs) { + vm.clearTime = $filter('date')(vm.alarm.clearTs, 'yyyy-MM-dd HH:mm:ss'); + } + + vm.alarmSeverity = $translate.instant(types.alarmSeverity[vm.alarm.severity].name); + + vm.alarmStatus = $translate.instant('alarm.display-status.' + vm.alarm.status); + + vm.alarmDetails = null; + if (vm.alarm.details) { + vm.alarmDetails = angular.toJson(vm.alarm.details); + vm.alarmDetails = js_beautify(vm.alarmDetails, {indent_size: 4}); + } + } + + function acknowledge () { + alarmService.ackAlarm(vm.alarmId).then( + function success() { + vm.alarmUpdated = true; + loadAlarm(); + } + ); + } + + function clear () { + alarmService.clearAlarm(vm.alarmId).then( + function success() { + vm.alarmUpdated = true; + loadAlarm(); + } + ); + } + + function close () { + $mdDialog.hide(vm.alarmUpdated ? vm.alarm : null); + } + +} diff --git a/ui/src/app/alarm/alarm-details-dialog.scss b/ui/src/app/alarm/alarm-details-dialog.scss new file mode 100644 index 0000000000..9b923d076c --- /dev/null +++ b/ui/src/app/alarm/alarm-details-dialog.scss @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.tb-alarm-details-panel { + margin-left: 15px; + border: 1px solid #C0C0C0; + height: 100%; + #tb-alarm-details { + min-width: 600px; + min-height: 200px; + width: 100%; + height: 100%; + } +} diff --git a/ui/src/app/alarm/alarm-details-dialog.tpl.html b/ui/src/app/alarm/alarm-details-dialog.tpl.html new file mode 100644 index 0000000000..c958201ea9 --- /dev/null +++ b/ui/src/app/alarm/alarm-details-dialog.tpl.html @@ -0,0 +1,107 @@ + + + +
    +

    alarm.alarm-details

    + + + + +
    +
    + +
    +
    + + + + + + + + +
    +
    + + + + + + + + + +
    +
    + + + + + + + + + +
    +
    + + + + + + + + + + + + +
    +
    alarm.details
    +
    +
    +
    +
    +
    +
    + + {{ 'alarm.acknowledge' | + translate }} + + {{ 'alarm.clear' | + translate }} + + + {{ 'action.close' | + translate }} + + +
    diff --git a/ui/src/app/alarm/alarm-header.directive.js b/ui/src/app/alarm/alarm-header.directive.js new file mode 100644 index 0000000000..b66a9729a1 --- /dev/null +++ b/ui/src/app/alarm/alarm-header.directive.js @@ -0,0 +1,39 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* eslint-disable import/no-unresolved, import/default */ + +import alarmHeaderTemplate from './alarm-header.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function AlarmHeaderDirective($compile, $templateCache) { + + var linker = function (scope, element) { + + var template = $templateCache.get(alarmHeaderTemplate); + element.html(template); + $compile(element.contents())(scope); + + } + + return { + restrict: "A", + replace: false, + link: linker, + scope: false + }; +} diff --git a/ui/src/app/event/event-header-alarm.tpl.html b/ui/src/app/alarm/alarm-header.tpl.html similarity index 62% rename from ui/src/app/event/event-header-alarm.tpl.html rename to ui/src/app/alarm/alarm-header.tpl.html index be1b0752b3..b67ac8f8e1 100644 --- a/ui/src/app/event/event-header-alarm.tpl.html +++ b/ui/src/app/alarm/alarm-header.tpl.html @@ -15,6 +15,9 @@ limitations under the License. --> -
    event.event-time
    -
    event.server
    -
    event.alarm
    +
    alarm.created-time
    +
    alarm.originator
    +
    alarm.type
    +
    alarm.severity
    +
    alarm.status
    +
    alarm.details
    diff --git a/ui/src/app/alarm/alarm-row.directive.js b/ui/src/app/alarm/alarm-row.directive.js new file mode 100644 index 0000000000..9cb9bedd68 --- /dev/null +++ b/ui/src/app/alarm/alarm-row.directive.js @@ -0,0 +1,67 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* eslint-disable import/no-unresolved, import/default */ + +import alarmDetailsDialogTemplate from './alarm-details-dialog.tpl.html'; + +import alarmRowTemplate from './alarm-row.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function AlarmRowDirective($compile, $templateCache, types, $mdDialog, $document) { + + var linker = function (scope, element, attrs) { + + var template = $templateCache.get(alarmRowTemplate); + element.html(template); + + scope.alarm = attrs.alarm; + scope.types = types; + + scope.showAlarmDetails = function($event) { + var onShowingCallback = { + onShowing: function(){} + } + $mdDialog.show({ + controller: 'AlarmDetailsDialogController', + controllerAs: 'vm', + templateUrl: alarmDetailsDialogTemplate, + locals: {alarmId: scope.alarm.id.id, showingCallback: onShowingCallback}, + parent: angular.element($document[0].body), + targetEvent: $event, + fullscreen: true, + skipHide: true, + onShowing: function(scope, element) { + onShowingCallback.onShowing(scope, element); + } + }).then(function (alarm) { + if (alarm) { + scope.alarm = alarm; + } + }); + } + + $compile(element.contents())(scope); + } + + return { + restrict: "A", + replace: false, + link: linker, + scope: false + }; +} diff --git a/ui/src/app/event/event-row-alarm.tpl.html b/ui/src/app/alarm/alarm-row.tpl.html similarity index 59% rename from ui/src/app/event/event-row-alarm.tpl.html rename to ui/src/app/alarm/alarm-row.tpl.html index fa6a2148ab..00c83241f7 100644 --- a/ui/src/app/event/event-row-alarm.tpl.html +++ b/ui/src/app/alarm/alarm-row.tpl.html @@ -15,14 +15,19 @@ limitations under the License. --> -
    {{event.createdTime | date : 'yyyy-MM-dd HH:mm:ss'}}
    -
    {{event.body.server}}
    -
    - {{alarm.createdTime | date : 'yyyy-MM-dd HH:mm:ss'}}
    +
    {{alarm.originatorName}}
    +
    {{alarm.type}}
    +
    + {{ alarm ? (types.alarmSeverity[alarm.severity].name | translate) : '' }} +
    +
    {{ alarm ? (('alarm.display-status.' + alarm.status) | translate) : '' }}
    +
    + - {{ 'action.view' | translate }} + {{ 'alarm.details' | translate }} diff --git a/ui/src/app/alarm/alarm-table.directive.js b/ui/src/app/alarm/alarm-table.directive.js new file mode 100644 index 0000000000..c93ea03981 --- /dev/null +++ b/ui/src/app/alarm/alarm-table.directive.js @@ -0,0 +1,211 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import './alarm.scss'; + +/* eslint-disable import/no-unresolved, import/default */ + +import alarmTableTemplate from './alarm-table.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function AlarmTableDirective($compile, $templateCache, $rootScope, types, alarmService) { + + var linker = function (scope, element) { + + var template = $templateCache.get(alarmTableTemplate); + + element.html(template); + + scope.types = types; + + scope.alarmSearchStatus = types.alarmSearchStatus.any; + + var pageSize = 20; + var startTime = 0; + var endTime = 0; + + scope.timewindow = { + history: { + timewindowMs: 24 * 60 * 60 * 1000 // 1 day + } + } + + scope.topIndex = 0; + + scope.theAlarms = { + getItemAtIndex: function (index) { + if (index > scope.alarms.data.length) { + scope.theAlarms.fetchMoreItems_(index); + return null; + } + var item = scope.alarms.data[index]; + if (item) { + item.indexNumber = index + 1; + } + return item; + }, + + getLength: function () { + if (scope.alarms.hasNext) { + return scope.alarms.data.length + scope.alarms.nextPageLink.limit; + } else { + return scope.alarms.data.length; + } + }, + + fetchMoreItems_: function () { + if (scope.alarms.hasNext && !scope.alarms.pending) { + if (scope.entityType && scope.entityId && scope.alarmSearchStatus) { + var promise = alarmService.getAlarms(scope.entityType, scope.entityId, + scope.alarms.nextPageLink, scope.alarmSearchStatus, null, true, false); + if (promise) { + scope.alarms.pending = true; + promise.then( + function success(alarms) { + scope.alarms.data = scope.alarms.data.concat(alarms.data); + scope.alarms.nextPageLink = alarms.nextPageLink; + scope.alarms.hasNext = alarms.hasNext; + if (scope.alarms.hasNext) { + scope.alarms.nextPageLink.limit = pageSize; + } + scope.alarms.pending = false; + }, + function fail() { + scope.alarms.hasNext = false; + scope.alarms.pending = false; + }); + } else { + scope.alarms.hasNext = false; + } + } else { + scope.alarms.hasNext = false; + } + } + } + }; + + scope.$watch("entityId", function(newVal, prevVal) { + if (newVal && !angular.equals(newVal, prevVal)) { + resetFilter(); + reload(); + } + }); + + + + function destroyWatchers() { + if (scope.alarmSearchStatusWatchHandle) { + scope.alarmSearchStatusWatchHandle(); + scope.alarmSearchStatusWatchHandle = null; + } + if (scope.timewindowWatchHandle) { + scope.timewindowWatchHandle(); + scope.timewindowWatchHandle = null; + } + } + + function initWatchers() { + scope.alarmSearchStatusWatchHandle = scope.$watch("alarmSearchStatus", function(newVal, prevVal) { + if (newVal && !angular.equals(newVal, prevVal)) { + reload(); + } + }); + scope.timewindowWatchHandle = scope.$watch("timewindow", function(newVal, prevVal) { + if (newVal && !angular.equals(newVal, prevVal)) { + reload(); + } + }, true); + } + + function resetFilter() { + destroyWatchers(); + scope.timewindow = { + history: { + timewindowMs: 24 * 60 * 60 * 1000 // 1 day + } + }; + scope.alarmSearchStatus = types.alarmSearchStatus.any; + initWatchers(); + } + + function updateTimeWindowRange () { + if (scope.timewindow.history.timewindowMs) { + var currentTime = (new Date).getTime(); + startTime = currentTime - scope.timewindow.history.timewindowMs; + endTime = currentTime; + } else { + startTime = scope.timewindow.history.fixedTimewindow.startTimeMs; + endTime = scope.timewindow.history.fixedTimewindow.endTimeMs; + } + } + + function reload () { + scope.topIndex = 0; + scope.selected = []; + updateTimeWindowRange(); + scope.alarms = { + data: [], + nextPageLink: { + limit: pageSize, + startTime: startTime, + endTime: endTime + }, + hasNext: true, + pending: false + }; + scope.theAlarms.getItemAtIndex(pageSize); + } + + scope.noData = function() { + return scope.alarms.data.length == 0 && !scope.alarms.hasNext; + } + + scope.hasData = function() { + return scope.alarms.data.length > 0; + } + + scope.loading = function() { + return $rootScope.loading; + } + + scope.hasScroll = function() { + var repeatContainer = scope.repeatContainer[0]; + if (repeatContainer) { + var scrollElement = repeatContainer.children[0]; + if (scrollElement) { + return scrollElement.scrollHeight > scrollElement.clientHeight; + } + } + return false; + } + + reload(); + + initWatchers(); + + $compile(element.contents())(scope); + } + + return { + restrict: "E", + link: linker, + scope: { + entityType: '=', + entityId: '=' + } + }; +} diff --git a/ui/src/app/alarm/alarm-table.tpl.html b/ui/src/app/alarm/alarm-table.tpl.html new file mode 100644 index 0000000000..c32e39a2d2 --- /dev/null +++ b/ui/src/app/alarm/alarm-table.tpl.html @@ -0,0 +1,49 @@ + + +
    + + + + + {{ ('alarm.search-status.' + searchStatus) | translate }} + + + + +
    +
    + + + + + + alarm.no-alarms-prompt + + + + + + + + +
    +
    diff --git a/ui/src/app/alarm/alarm.scss b/ui/src/app/alarm/alarm.scss new file mode 100644 index 0000000000..aea5225445 --- /dev/null +++ b/ui/src/app/alarm/alarm.scss @@ -0,0 +1,78 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.tb-alarm-container { + overflow-x: auto; +} + +md-list.tb-alarm-table { + padding: 0px; + min-width: 700px; + + md-list-item { + padding: 0px; + } + + .tb-row { + height: 48px; + padding: 0px; + overflow: hidden; + } + + .tb-row:hover { + background-color: #EEEEEE; + } + + .tb-header:hover { + background: none; + } + + .tb-header { + .tb-cell { + color: rgba(0,0,0,.54); + font-size: 12px; + font-weight: 700; + white-space: nowrap; + background: none; + } + } + + .tb-cell { + padding: 0 24px; + margin: auto 0; + color: rgba(0,0,0,.87); + font-size: 13px; + vertical-align: middle; + text-align: left; + overflow: hidden; + .md-button { + padding: 0; + margin: 0; + } + } + + .tb-cell.tb-number { + text-align: right; + } + +} + +#tb-alarm-content { + min-width: 400px; + min-height: 50px; + width: 100%; + height: 100%; +} diff --git a/ui/src/app/alarm/index.js b/ui/src/app/alarm/index.js new file mode 100644 index 0000000000..0ea4610c3d --- /dev/null +++ b/ui/src/app/alarm/index.js @@ -0,0 +1,27 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import AlarmDetailsDialogController from './alarm-details-dialog.controller'; +import AlarmHeaderDirective from './alarm-header.directive'; +import AlarmRowDirective from './alarm-row.directive'; +import AlarmTableDirective from './alarm-table.directive'; + +export default angular.module('thingsboard.alarm', []) + .controller('AlarmDetailsDialogController', AlarmDetailsDialogController) + .directive('tbAlarmHeader', AlarmHeaderDirective) + .directive('tbAlarmRow', AlarmRowDirective) + .directive('tbAlarmTable', AlarmTableDirective) + .name; diff --git a/ui/src/app/api/alarm.service.js b/ui/src/app/api/alarm.service.js index ca892f292b..34e6b59e20 100644 --- a/ui/src/app/api/alarm.service.js +++ b/ui/src/app/api/alarm.service.js @@ -21,6 +21,7 @@ export default angular.module('thingsboard.api.alarm', []) function AlarmService($http, $q, $interval, $filter) { var service = { getAlarm: getAlarm, + getAlarmInfo: getAlarmInfo, saveAlarm: saveAlarm, ackAlarm: ackAlarm, clearAlarm: clearAlarm, @@ -46,6 +47,21 @@ function AlarmService($http, $q, $interval, $filter) { return deferred.promise; } + function getAlarmInfo(alarmId, ignoreErrors, config) { + var deferred = $q.defer(); + var url = '/api/alarm/info/' + alarmId; + if (!config) { + config = {}; + } + config = Object.assign(config, { ignoreErrors: ignoreErrors }); + $http.get(url, config).then(function success(response) { + deferred.resolve(response.data); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + function saveAlarm(alarm, ignoreErrors, config) { var deferred = $q.defer(); var url = '/api/alarm'; diff --git a/ui/src/app/asset/assets.tpl.html b/ui/src/app/asset/assets.tpl.html index 11a118f5e4..8370d3dbac 100644 --- a/ui/src/app/asset/assets.tpl.html +++ b/ui/src/app/asset/assets.tpl.html @@ -48,11 +48,16 @@ disable-attribute-scope-selection="true"> + + + + + default-event-type="{{vm.types.eventType.error.value}}"> diff --git a/ui/src/app/asset/index.js b/ui/src/app/asset/index.js index 62ab201b69..2426e85b3c 100644 --- a/ui/src/app/asset/index.js +++ b/ui/src/app/asset/index.js @@ -15,7 +15,6 @@ */ import uiRouter from 'angular-ui-router'; import thingsboardGrid from '../components/grid.directive'; -import thingsboardEvent from '../event'; import thingsboardApiUser from '../api/user.service'; import thingsboardApiAsset from '../api/asset.service'; import thingsboardApiCustomer from '../api/customer.service'; @@ -29,7 +28,6 @@ import AssetDirective from './asset.directive'; export default angular.module('thingsboard.asset', [ uiRouter, thingsboardGrid, - thingsboardEvent, thingsboardApiUser, thingsboardApiAsset, thingsboardApiCustomer diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 20760937a3..b281ad1043 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -72,6 +72,28 @@ export default angular.module('thingsboard.types', []) ack: "ACK", unack: "UNACK" }, + alarmSeverity: { + "CRITICAL": { + name: "alarm.severity-critical", + class: "tb-critical" + }, + "MAJOR": { + name: "alarm.severity-major", + class: "tb-major" + }, + "MINOR": { + name: "alarm.severity-minor", + class: "tb-minor" + }, + "WARNING": { + name: "alarm.severity-warning", + class: "tb-warning" + }, + "INDETERMINATE": { + name: "alarm.severity-indeterminate", + class: "tb-indeterminate" + } + }, aliasFilterType: { entityList: { value: 'entityList', @@ -215,10 +237,6 @@ export default angular.module('thingsboard.types', []) manages: "Manages" }, eventType: { - alarm: { - value: "ALARM", - name: "event.type-alarm" - }, error: { value: "ERROR", name: "event.type-error" diff --git a/ui/src/app/customer/customers.tpl.html b/ui/src/app/customer/customers.tpl.html index 6c70a70dad..a6521f066d 100644 --- a/ui/src/app/customer/customers.tpl.html +++ b/ui/src/app/customer/customers.tpl.html @@ -48,11 +48,16 @@ disable-attribute-scope-selection="true"> + + + + + default-event-type="{{vm.types.eventType.error.value}}"> diff --git a/ui/src/app/device/devices.tpl.html b/ui/src/app/device/devices.tpl.html index 3ff6c1ce39..1e467be124 100644 --- a/ui/src/app/device/devices.tpl.html +++ b/ui/src/app/device/devices.tpl.html @@ -49,11 +49,16 @@ disable-attribute-scope-selection="true"> + + + + + default-event-type="{{vm.types.eventType.error.value}}"> diff --git a/ui/src/app/device/index.js b/ui/src/app/device/index.js index ea42ee8e95..5a8adaf9bb 100644 --- a/ui/src/app/device/index.js +++ b/ui/src/app/device/index.js @@ -15,7 +15,6 @@ */ import uiRouter from 'angular-ui-router'; import thingsboardGrid from '../components/grid.directive'; -import thingsboardEvent from '../event'; import thingsboardApiUser from '../api/user.service'; import thingsboardApiDevice from '../api/device.service'; import thingsboardApiCustomer from '../api/customer.service'; @@ -30,7 +29,6 @@ import DeviceDirective from './device.directive'; export default angular.module('thingsboard.device', [ uiRouter, thingsboardGrid, - thingsboardEvent, thingsboardApiUser, thingsboardApiDevice, thingsboardApiCustomer diff --git a/ui/src/app/event/event-content-dialog.controller.js b/ui/src/app/event/event-content-dialog.controller.js index 19ddb8c4d5..235cfcff32 100644 --- a/ui/src/app/event/event-content-dialog.controller.js +++ b/ui/src/app/event/event-content-dialog.controller.js @@ -62,7 +62,7 @@ export default function EventContentDialogController($mdDialog, content, title, } newWidth = 8 * maxLineLength + 16; } - $('#tb-content', element).height(newHeight.toString() + "px") + $('#tb-event-content', element).height(newHeight.toString() + "px") .width(newWidth.toString() + "px"); vm.editor.resize(); } diff --git a/ui/src/app/event/event-content-dialog.tpl.html b/ui/src/app/event/event-content-dialog.tpl.html index 4cf046b080..7b4184c5f4 100644 --- a/ui/src/app/event/event-content-dialog.tpl.html +++ b/ui/src/app/event/event-content-dialog.tpl.html @@ -27,7 +27,7 @@
    -
    diff --git a/ui/src/app/event/event-header.directive.js b/ui/src/app/event/event-header.directive.js index 5e8cc7c6e7..c43894ef85 100644 --- a/ui/src/app/event/event-header.directive.js +++ b/ui/src/app/event/event-header.directive.js @@ -18,7 +18,6 @@ import eventHeaderLcEventTemplate from './event-header-lc-event.tpl.html'; import eventHeaderStatsTemplate from './event-header-stats.tpl.html'; import eventHeaderErrorTemplate from './event-header-error.tpl.html'; -import eventHeaderAlarmTemplate from './event-header-alarm.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ @@ -39,9 +38,6 @@ export default function EventHeaderDirective($compile, $templateCache, types) { case types.eventType.error.value: template = eventHeaderErrorTemplate; break; - case types.eventType.alarm.value: - template = eventHeaderAlarmTemplate; - break; } return $templateCache.get(template); } diff --git a/ui/src/app/event/event-row.directive.js b/ui/src/app/event/event-row.directive.js index 9bff1921a6..d1feb60760 100644 --- a/ui/src/app/event/event-row.directive.js +++ b/ui/src/app/event/event-row.directive.js @@ -20,7 +20,6 @@ import eventErrorDialogTemplate from './event-content-dialog.tpl.html'; import eventRowLcEventTemplate from './event-row-lc-event.tpl.html'; import eventRowStatsTemplate from './event-row-stats.tpl.html'; import eventRowErrorTemplate from './event-row-error.tpl.html'; -import eventRowAlarmTemplate from './event-row-alarm.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ @@ -41,9 +40,6 @@ export default function EventRowDirective($compile, $templateCache, $mdDialog, $ case types.eventType.error.value: template = eventRowErrorTemplate; break; - case types.eventType.alarm.value: - template = eventRowAlarmTemplate; - break; } return $templateCache.get(template); } diff --git a/ui/src/app/event/event-table.tpl.html b/ui/src/app/event/event-table.tpl.html index f44aeb32eb..82a921fedd 100644 --- a/ui/src/app/event/event-table.tpl.html +++ b/ui/src/app/event/event-table.tpl.html @@ -27,7 +27,7 @@
    - + + + + + + default-event-type="{{vm.types.eventType.lcEvent.value}}"> diff --git a/ui/src/app/rule/index.js b/ui/src/app/rule/index.js index 64819201cf..2700ec71cf 100644 --- a/ui/src/app/rule/index.js +++ b/ui/src/app/rule/index.js @@ -17,7 +17,6 @@ import uiRouter from 'angular-ui-router'; import thingsboardGrid from '../components/grid.directive'; import thingsboardPluginSelect from '../components/plugin-select.directive'; import thingsboardComponent from '../component'; -import thingsboardEvent from '../event'; import thingsboardApiRule from '../api/rule.service'; import thingsboardApiPlugin from '../api/plugin.service'; import thingsboardApiComponentDescriptor from '../api/component-descriptor.service'; @@ -31,7 +30,6 @@ export default angular.module('thingsboard.rule', [ thingsboardGrid, thingsboardPluginSelect, thingsboardComponent, - thingsboardEvent, thingsboardApiRule, thingsboardApiPlugin, thingsboardApiComponentDescriptor diff --git a/ui/src/app/rule/rules.tpl.html b/ui/src/app/rule/rules.tpl.html index 098bbee7fb..336fe63ce9 100644 --- a/ui/src/app/rule/rules.tpl.html +++ b/ui/src/app/rule/rules.tpl.html @@ -48,12 +48,16 @@ disable-attribute-scope-selection="true"> + + + + + default-event-type="{{vm.types.eventType.lcEvent.value}}"> diff --git a/ui/src/app/tenant/tenants.tpl.html b/ui/src/app/tenant/tenants.tpl.html index 00350a4e02..e407291757 100644 --- a/ui/src/app/tenant/tenants.tpl.html +++ b/ui/src/app/tenant/tenants.tpl.html @@ -46,11 +46,16 @@ disable-attribute-scope-selection="true"> + + + + + default-event-type="{{vm.types.eventType.error.value}}"> diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss index bf444937a4..ab720c5e5a 100644 --- a/ui/src/scss/main.scss +++ b/ui/src/scss/main.scss @@ -309,6 +309,24 @@ pre.tb-highlight { } } +.tb-severity { + font-weight: bold; + &.tb-critical { + color: red !important; + } + &.tb-major { + color: orange !important; + } + &.tb-minor { + color: #ffca3d !important; + } + &.tb-warning { + color: #abab00 !important; + } + &.tb-indeterminate { + color: green !important; + } +} /*********************** * Flow