Commit 2cb71c15b216f27c0b4371c5c16ab4a2031190fd
Committed by
GitHub
Merge pull request #164 from thingsboard/feature/TB-61
TB-61: Improve Alias Filter
Showing
52 changed files
with
2617 additions
and
1097 deletions
Too many changes to show.
To preserve performance only 52 of 74 files are displayed.
... | ... | @@ -250,6 +250,21 @@ public class EntityRelationController extends BaseController { |
250 | 250 | } |
251 | 251 | } |
252 | 252 | |
253 | + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") | |
254 | + @RequestMapping(value = "/relations/info", method = RequestMethod.POST) | |
255 | + @ResponseBody | |
256 | + public List<EntityRelationInfo> findInfoByQuery(@RequestBody EntityRelationsQuery query) throws ThingsboardException { | |
257 | + checkNotNull(query); | |
258 | + checkNotNull(query.getParameters()); | |
259 | + checkNotNull(query.getFilters()); | |
260 | + checkEntityId(query.getParameters().getEntityId()); | |
261 | + try { | |
262 | + return checkNotNull(relationService.findInfoByQuery(query).get()); | |
263 | + } catch (Exception e) { | |
264 | + throw handleException(e); | |
265 | + } | |
266 | + } | |
267 | + | |
253 | 268 | private RelationTypeGroup parseRelationTypeGroup(String strRelationTypeGroup, RelationTypeGroup defaultValue) { |
254 | 269 | RelationTypeGroup result = defaultValue; |
255 | 270 | if (strRelationTypeGroup != null && strRelationTypeGroup.trim().length()>0) { | ... | ... |
... | ... | @@ -191,7 +191,7 @@ public class BaseRelationService implements RelationService { |
191 | 191 | |
192 | 192 | @Override |
193 | 193 | public ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query) { |
194 | - log.trace("Executing findByQuery [{}][{}]", query); | |
194 | + log.trace("Executing findByQuery [{}]", query); | |
195 | 195 | RelationsSearchParameters params = query.getParameters(); |
196 | 196 | final List<EntityTypeFilter> filters = query.getFilters(); |
197 | 197 | if (filters == null || filters.isEmpty()) { |
... | ... | @@ -224,6 +224,30 @@ public class BaseRelationService implements RelationService { |
224 | 224 | } |
225 | 225 | } |
226 | 226 | |
227 | + @Override | |
228 | + public ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(EntityRelationsQuery query) { | |
229 | + log.trace("Executing findInfoByQuery [{}]", query); | |
230 | + ListenableFuture<List<EntityRelation>> relations = findByQuery(query); | |
231 | + EntitySearchDirection direction = query.getParameters().getDirection(); | |
232 | + ListenableFuture<List<EntityRelationInfo>> relationsInfo = Futures.transform(relations, | |
233 | + (AsyncFunction<List<EntityRelation>, List<EntityRelationInfo>>) relations1 -> { | |
234 | + List<ListenableFuture<EntityRelationInfo>> futures = new ArrayList<>(); | |
235 | + relations1.stream().forEach(relation -> | |
236 | + futures.add(fetchRelationInfoAsync(relation, | |
237 | + relation2 -> direction == EntitySearchDirection.FROM ? relation2.getTo() : relation2.getFrom(), | |
238 | + (EntityRelationInfo relationInfo, String entityName) -> { | |
239 | + if (direction == EntitySearchDirection.FROM) { | |
240 | + relationInfo.setToName(entityName); | |
241 | + } else { | |
242 | + relationInfo.setFromName(entityName); | |
243 | + } | |
244 | + })) | |
245 | + ); | |
246 | + return Futures.successfulAsList(futures); | |
247 | + }); | |
248 | + return relationsInfo; | |
249 | + } | |
250 | + | |
227 | 251 | protected void validate(EntityRelation relation) { |
228 | 252 | if (relation == null) { |
229 | 253 | throw new DataValidationException("Relation type should be specified!"); | ... | ... |
... | ... | @@ -52,6 +52,8 @@ public interface RelationService { |
52 | 52 | |
53 | 53 | ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query); |
54 | 54 | |
55 | + ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(EntityRelationsQuery query); | |
56 | + | |
55 | 57 | // TODO: This method may be useful for some validations in the future |
56 | 58 | // ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to); |
57 | 59 | ... | ... |
ui/src/app/api/alias-controller.js
0 → 100644
1 | +/* | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +const varsRegex = /\$\{([^\}]*)\}/g; | |
18 | + | |
19 | +export default class AliasController { | |
20 | + | |
21 | + constructor($scope, $q, $filter, utils, types, entityService, stateController, entityAliases) { | |
22 | + this.$scope = $scope; | |
23 | + this.$q = $q; | |
24 | + this.$filter = $filter; | |
25 | + this.utils = utils; | |
26 | + this.types = types; | |
27 | + this.entityService = entityService; | |
28 | + this.stateController = stateController; | |
29 | + this.entityAliases = angular.copy(entityAliases); | |
30 | + this.resolvedAliases = {}; | |
31 | + this.resolvedAliasesPromise = {}; | |
32 | + this.resolvedAliasesToStateEntities = {}; | |
33 | + } | |
34 | + | |
35 | + updateEntityAliases(newEntityAliases) { | |
36 | + var changedAliasIds = []; | |
37 | + for (var aliasId in newEntityAliases) { | |
38 | + var newEntityAlias = newEntityAliases[aliasId]; | |
39 | + var prevEntityAlias = this.entityAliases[aliasId]; | |
40 | + if (!angular.equals(newEntityAlias, prevEntityAlias)) { | |
41 | + changedAliasIds.push(aliasId); | |
42 | + this.setAliasUnresolved(aliasId); | |
43 | + } | |
44 | + } | |
45 | + for (aliasId in this.entityAliases) { | |
46 | + if (!newEntityAliases[aliasId]) { | |
47 | + changedAliasIds.push(aliasId); | |
48 | + this.setAliasUnresolved(aliasId); | |
49 | + } | |
50 | + } | |
51 | + this.entityAliases = angular.copy(newEntityAliases); | |
52 | + if (changedAliasIds.length) { | |
53 | + this.$scope.$broadcast('entityAliasesChanged', changedAliasIds); | |
54 | + } | |
55 | + } | |
56 | + | |
57 | + dashboardStateChanged() { | |
58 | + var newEntityId = this.stateController.getStateParams().entityId; | |
59 | + var changedAliasIds = []; | |
60 | + for (var aliasId in this.resolvedAliasesToStateEntities) { | |
61 | + var prevEntityId = this.resolvedAliasesToStateEntities[aliasId]; | |
62 | + if (!angular.equals(newEntityId, prevEntityId)) { | |
63 | + changedAliasIds.push(aliasId); | |
64 | + this.setAliasUnresolved(aliasId); | |
65 | + } | |
66 | + } | |
67 | + if (changedAliasIds.length) { | |
68 | + this.$scope.$broadcast('entityAliasesChanged', changedAliasIds); | |
69 | + } | |
70 | + } | |
71 | + | |
72 | + setAliasUnresolved(aliasId) { | |
73 | + delete this.resolvedAliases[aliasId]; | |
74 | + delete this.resolvedAliasesPromise[aliasId]; | |
75 | + delete this.resolvedAliasesToStateEntities[aliasId]; | |
76 | + } | |
77 | + | |
78 | + getEntityAliases() { | |
79 | + return this.entityAliases; | |
80 | + } | |
81 | + | |
82 | + getAliasInfo(aliasId) { | |
83 | + var deferred = this.$q.defer(); | |
84 | + var aliasInfo = this.resolvedAliases[aliasId]; | |
85 | + if (aliasInfo) { | |
86 | + deferred.resolve(aliasInfo); | |
87 | + return deferred.promise; | |
88 | + } else if (this.resolvedAliasesPromise[aliasId]) { | |
89 | + return this.resolvedAliasesPromise[aliasId]; | |
90 | + } else { | |
91 | + this.resolvedAliasesPromise[aliasId] = deferred.promise; | |
92 | + var aliasCtrl = this; | |
93 | + var entityAlias = this.entityAliases[aliasId]; | |
94 | + if (entityAlias) { | |
95 | + this.entityService.resolveAlias(entityAlias, this.stateController.getStateParams()).then( | |
96 | + function success(aliasInfo) { | |
97 | + aliasCtrl.resolvedAliases[aliasId] = aliasInfo; | |
98 | + if (aliasInfo.stateEntity) { | |
99 | + aliasCtrl.resolvedAliasesToStateEntities[aliasId] = | |
100 | + aliasCtrl.stateController.getStateParams().entityId; | |
101 | + } | |
102 | + aliasCtrl.$scope.$broadcast('entityAliasResolved', aliasId); | |
103 | + deferred.resolve(aliasInfo); | |
104 | + }, | |
105 | + function fail() { | |
106 | + deferred.reject(); | |
107 | + } | |
108 | + ); | |
109 | + } else { | |
110 | + deferred.reject(); | |
111 | + } | |
112 | + return this.resolvedAliasesPromise[aliasId]; | |
113 | + } | |
114 | + } | |
115 | + | |
116 | + resolveDatasource(datasource) { | |
117 | + var deferred = this.$q.defer(); | |
118 | + if (datasource.type === this.types.datasourceType.entity) { | |
119 | + if (datasource.entityAliasId) { | |
120 | + this.getAliasInfo(datasource.entityAliasId).then( | |
121 | + function success(aliasInfo) { | |
122 | + datasource.aliasName = aliasInfo.alias; | |
123 | + if (aliasInfo.resolveMultiple) { | |
124 | + var newDatasource; | |
125 | + var resolvedEntities = aliasInfo.resolvedEntities; | |
126 | + if (resolvedEntities && resolvedEntities.length) { | |
127 | + var datasources = []; | |
128 | + for (var i=0;i<resolvedEntities.length;i++) { | |
129 | + var resolvedEntity = resolvedEntities[i]; | |
130 | + newDatasource = angular.copy(datasource); | |
131 | + newDatasource.entityId = resolvedEntity.id; | |
132 | + newDatasource.entityType = resolvedEntity.entityType; | |
133 | + newDatasource.entityName = resolvedEntity.name; | |
134 | + newDatasource.name = resolvedEntity.name; | |
135 | + newDatasource.generated = i > 0 ? true : false; | |
136 | + datasources.push(newDatasource); | |
137 | + } | |
138 | + deferred.resolve(datasources); | |
139 | + } else { | |
140 | + if (aliasInfo.stateEntity) { | |
141 | + newDatasource = angular.copy(datasource); | |
142 | + newDatasource.unresolvedStateEntity = true; | |
143 | + deferred.resolve([newDatasource]); | |
144 | + } else { | |
145 | + deferred.reject(); | |
146 | + } | |
147 | + } | |
148 | + } else { | |
149 | + var entity = aliasInfo.currentEntity; | |
150 | + if (entity) { | |
151 | + datasource.entityId = entity.id; | |
152 | + datasource.entityType = entity.entityType; | |
153 | + datasource.entityName = entity.name; | |
154 | + datasource.name = entity.name; | |
155 | + deferred.resolve([datasource]); | |
156 | + } else { | |
157 | + if (aliasInfo.stateEntity) { | |
158 | + datasource.unresolvedStateEntity = true; | |
159 | + deferred.resolve([datasource]); | |
160 | + } else { | |
161 | + deferred.reject(); | |
162 | + } | |
163 | + } | |
164 | + } | |
165 | + }, | |
166 | + function fail() { | |
167 | + deferred.reject(); | |
168 | + } | |
169 | + ); | |
170 | + } else { // entityId | |
171 | + datasource.aliasName = datasource.entityName; | |
172 | + datasource.name = datasource.entityName; | |
173 | + deferred.resolve([datasource]); | |
174 | + } | |
175 | + } else { // function | |
176 | + deferred.resolve([datasource]); | |
177 | + } | |
178 | + return deferred.promise; | |
179 | + } | |
180 | + | |
181 | + resolveDatasources(datasources) { | |
182 | + | |
183 | + function updateDataKeyLabel(dataKey, datasource) { | |
184 | + if (!dataKey.pattern) { | |
185 | + dataKey.pattern = angular.copy(dataKey.label); | |
186 | + } | |
187 | + var pattern = dataKey.pattern; | |
188 | + var label = dataKey.pattern; | |
189 | + var match = varsRegex.exec(pattern); | |
190 | + while (match !== null) { | |
191 | + var variable = match[0]; | |
192 | + var variableName = match[1]; | |
193 | + if (variableName === 'dsName') { | |
194 | + label = label.split(variable).join(datasource.name); | |
195 | + } else if (variableName === 'entityName') { | |
196 | + label = label.split(variable).join(datasource.entityName); | |
197 | + } else if (variableName === 'deviceName') { | |
198 | + label = label.split(variable).join(datasource.entityName); | |
199 | + } else if (variableName === 'aliasName') { | |
200 | + label = label.split(variable).join(datasource.aliasName); | |
201 | + } | |
202 | + match = varsRegex.exec(pattern); | |
203 | + } | |
204 | + dataKey.label = label; | |
205 | + } | |
206 | + | |
207 | + function updateDatasourceKeyLabels(datasource) { | |
208 | + for (var dk = 0; dk < datasource.dataKeys.length; dk++) { | |
209 | + updateDataKeyLabel(datasource.dataKeys[dk], datasource); | |
210 | + } | |
211 | + } | |
212 | + | |
213 | + var deferred = this.$q.defer(); | |
214 | + var newDatasources = angular.copy(datasources); | |
215 | + var datasorceResolveTasks = []; | |
216 | + var aliasCtrl = this; | |
217 | + newDatasources.forEach(function (datasource) { | |
218 | + var resolveDatasourceTask = aliasCtrl.resolveDatasource(datasource); | |
219 | + datasorceResolveTasks.push(resolveDatasourceTask); | |
220 | + }); | |
221 | + this.$q.all(datasorceResolveTasks).then( | |
222 | + function success(datasourcesArrays) { | |
223 | + var datasources = [].concat.apply([], datasourcesArrays); | |
224 | + datasources = aliasCtrl.$filter('orderBy')(datasources, '+generated'); | |
225 | + var index = 0; | |
226 | + var functionIndex = 0; | |
227 | + datasources.forEach(function(datasource) { | |
228 | + if (datasource.type === aliasCtrl.types.datasourceType.function) { | |
229 | + var name; | |
230 | + if (datasource.name && datasource.name.length) { | |
231 | + name = datasource.name; | |
232 | + } else { | |
233 | + functionIndex++; | |
234 | + name = aliasCtrl.types.datasourceType.function; | |
235 | + if (functionIndex > 1) { | |
236 | + name += ' ' + functionIndex; | |
237 | + } | |
238 | + } | |
239 | + datasource.name = name; | |
240 | + datasource.aliasName = name; | |
241 | + datasource.entityName = name; | |
242 | + } else if (datasource.unresolvedStateEntity) { | |
243 | + datasource.name = "Unresolved"; | |
244 | + datasource.entityName = "Unresolved"; | |
245 | + } | |
246 | + datasource.dataKeys.forEach(function(dataKey) { | |
247 | + if (datasource.generated) { | |
248 | + dataKey._hash = Math.random(); | |
249 | + dataKey.color = aliasCtrl.utils.getMaterialColor(index); | |
250 | + } | |
251 | + index++; | |
252 | + }); | |
253 | + updateDatasourceKeyLabels(datasource); | |
254 | + }); | |
255 | + deferred.resolve(datasources); | |
256 | + }, | |
257 | + function fail() { | |
258 | + deferred.reject(); | |
259 | + } | |
260 | + ); | |
261 | + return deferred.promise; | |
262 | + } | |
263 | + | |
264 | + getInstantAliasInfo(aliasId) { | |
265 | + return this.resolvedAliases[aliasId]; | |
266 | + } | |
267 | + | |
268 | + updateCurrentAliasEntity(aliasId, currentEntity) { | |
269 | + var aliasInfo = this.resolvedAliases[aliasId]; | |
270 | + if (aliasInfo) { | |
271 | + var prevCurrentEntity = aliasInfo.currentEntity; | |
272 | + if (!angular.equals(currentEntity, prevCurrentEntity)) { | |
273 | + aliasInfo.currentEntity = currentEntity; | |
274 | + this.$scope.$broadcast('entityAliasesChanged', [aliasId]); | |
275 | + } | |
276 | + } | |
277 | + } | |
278 | + | |
279 | +} | |
\ No newline at end of file | ... | ... |
... | ... | @@ -30,7 +30,8 @@ function EntityRelationService($http, $q) { |
30 | 30 | findByTo: findByTo, |
31 | 31 | findInfoByTo: findInfoByTo, |
32 | 32 | findByToAndType: findByToAndType, |
33 | - findByQuery: findByQuery | |
33 | + findByQuery: findByQuery, | |
34 | + findInfoByQuery: findInfoByQuery | |
34 | 35 | } |
35 | 36 | |
36 | 37 | return service; |
... | ... | @@ -159,4 +160,15 @@ function EntityRelationService($http, $q) { |
159 | 160 | return deferred.promise; |
160 | 161 | } |
161 | 162 | |
163 | + function findInfoByQuery(query) { | |
164 | + var deferred = $q.defer(); | |
165 | + var url = '/api/relations/info'; | |
166 | + $http.post(url, query).then(function success(response) { | |
167 | + deferred.resolve(response.data); | |
168 | + }, function fail() { | |
169 | + deferred.reject(); | |
170 | + }); | |
171 | + return deferred.promise; | |
172 | + } | |
173 | + | |
162 | 174 | } | ... | ... |
... | ... | @@ -27,10 +27,14 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
27 | 27 | getEntity: getEntity, |
28 | 28 | getEntities: getEntities, |
29 | 29 | getEntitiesByNameFilter: getEntitiesByNameFilter, |
30 | - processEntityAliases: processEntityAliases, | |
31 | - getEntityKeys: getEntityKeys, | |
30 | + resolveAlias: resolveAlias, | |
31 | + resolveAliasFilter: resolveAliasFilter, | |
32 | 32 | checkEntityAlias: checkEntityAlias, |
33 | - createDatasoucesFromSubscriptionsInfo: createDatasoucesFromSubscriptionsInfo, | |
33 | + filterAliasByEntityTypes: filterAliasByEntityTypes, | |
34 | + getAliasFilterTypesByEntityTypes: getAliasFilterTypesByEntityTypes, | |
35 | + prepareAllowedEntityTypesList: prepareAllowedEntityTypesList, | |
36 | + getEntityKeys: getEntityKeys, | |
37 | + createDatasourcesFromSubscriptionsInfo: createDatasourcesFromSubscriptionsInfo, | |
34 | 38 | getRelatedEntities: getRelatedEntities, |
35 | 39 | saveRelatedEntity: saveRelatedEntity, |
36 | 40 | getRelatedEntity: getRelatedEntity, |
... | ... | @@ -173,6 +177,54 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
173 | 177 | return deferred.promise; |
174 | 178 | } |
175 | 179 | |
180 | + function getSingleTenantByPageLinkPromise(pageLink) { | |
181 | + var user = userService.getCurrentUser(); | |
182 | + var tenantId = user.tenantId; | |
183 | + var deferred = $q.defer(); | |
184 | + tenantService.getTenant(tenantId).then( | |
185 | + function success(tenant) { | |
186 | + var tenantName = tenant.name; | |
187 | + var result = { | |
188 | + data: [], | |
189 | + nextPageLink: pageLink, | |
190 | + hasNext: false | |
191 | + }; | |
192 | + if (tenantName.toLowerCase().startsWith(pageLink.textSearch)) { | |
193 | + result.data.push(tenant); | |
194 | + } | |
195 | + deferred.resolve(result); | |
196 | + }, | |
197 | + function fail() { | |
198 | + deferred.reject(); | |
199 | + } | |
200 | + ); | |
201 | + return deferred.promise; | |
202 | + } | |
203 | + | |
204 | + function getSingleCustomerByPageLinkPromise(pageLink) { | |
205 | + var user = userService.getCurrentUser(); | |
206 | + var customerId = user.customerId; | |
207 | + var deferred = $q.defer(); | |
208 | + customerService.getCustomer(customerId).then( | |
209 | + function success(customer) { | |
210 | + var customerName = customer.name; | |
211 | + var result = { | |
212 | + data: [], | |
213 | + nextPageLink: pageLink, | |
214 | + hasNext: false | |
215 | + }; | |
216 | + if (customerName.toLowerCase().startsWith(pageLink.textSearch)) { | |
217 | + result.data.push(customer); | |
218 | + } | |
219 | + deferred.resolve(result); | |
220 | + }, | |
221 | + function fail() { | |
222 | + deferred.reject(); | |
223 | + } | |
224 | + ); | |
225 | + return deferred.promise; | |
226 | + } | |
227 | + | |
176 | 228 | function getEntitiesByPageLinkPromise(entityType, pageLink, config, subType) { |
177 | 229 | var promise; |
178 | 230 | var user = userService.getCurrentUser(); |
... | ... | @@ -193,10 +245,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
193 | 245 | } |
194 | 246 | break; |
195 | 247 | case types.entityType.tenant: |
196 | - promise = tenantService.getTenants(pageLink); | |
248 | + if (user.authority === 'TENANT_ADMIN') { | |
249 | + promise = getSingleTenantByPageLinkPromise(pageLink); | |
250 | + } else { | |
251 | + promise = tenantService.getTenants(pageLink); | |
252 | + } | |
197 | 253 | break; |
198 | 254 | case types.entityType.customer: |
199 | - promise = customerService.getCustomers(pageLink); | |
255 | + if (user.authority === 'CUSTOMER_USER') { | |
256 | + promise = getSingleCustomerByPageLinkPromise(pageLink); | |
257 | + } else { | |
258 | + promise = customerService.getCustomers(pageLink); | |
259 | + } | |
200 | 260 | break; |
201 | 261 | case types.entityType.rule: |
202 | 262 | promise = ruleService.getAllRules(pageLink); |
... | ... | @@ -221,17 +281,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
221 | 281 | return promise; |
222 | 282 | } |
223 | 283 | |
224 | - function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config, subType) { | |
225 | - var deferred = $q.defer(); | |
226 | - var pageLink = {limit: limit, textSearch: entityNameFilter}; | |
284 | + function getEntitiesByPageLink(entityType, pageLink, config, subType, data, deferred) { | |
227 | 285 | var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config, subType); |
228 | 286 | if (promise) { |
229 | 287 | promise.then( |
230 | 288 | function success(result) { |
231 | - if (result.data && result.data.length > 0) { | |
232 | - deferred.resolve(result.data); | |
289 | + data = data.concat(result.data); | |
290 | + if (result.hasNext) { | |
291 | + pageLink = result.nextPageLink; | |
292 | + getEntitiesByPageLink(entityType, pageLink, config, subType, data, deferred); | |
233 | 293 | } else { |
234 | - deferred.resolve(null); | |
294 | + if (data && data.length > 0) { | |
295 | + deferred.resolve(data); | |
296 | + } else { | |
297 | + deferred.resolve(null); | |
298 | + } | |
235 | 299 | } |
236 | 300 | }, |
237 | 301 | function fail() { |
... | ... | @@ -241,92 +305,418 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
241 | 305 | } else { |
242 | 306 | deferred.resolve(null); |
243 | 307 | } |
308 | + } | |
309 | + | |
310 | + function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config, subType) { | |
311 | + var deferred = $q.defer(); | |
312 | + var pageLink = {limit: limit, textSearch: entityNameFilter}; | |
313 | + if (limit == -1) { // all | |
314 | + var data = []; | |
315 | + pageLink.limit = 100; | |
316 | + getEntitiesByPageLink(entityType, pageLink, config, subType, data, deferred); | |
317 | + } else { | |
318 | + var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config, subType); | |
319 | + if (promise) { | |
320 | + promise.then( | |
321 | + function success(result) { | |
322 | + if (result.data && result.data.length > 0) { | |
323 | + deferred.resolve(result.data); | |
324 | + } else { | |
325 | + deferred.resolve(null); | |
326 | + } | |
327 | + }, | |
328 | + function fail() { | |
329 | + deferred.resolve(null); | |
330 | + } | |
331 | + ); | |
332 | + } else { | |
333 | + deferred.resolve(null); | |
334 | + } | |
335 | + } | |
244 | 336 | return deferred.promise; |
245 | 337 | } |
246 | 338 | |
247 | - function entityToEntityInfo(entityType, entity) { | |
248 | - return { name: entity.name, entityType: entityType, id: entity.id.id }; | |
339 | + function entityToEntityInfo(entity) { | |
340 | + return { name: entity.name, entityType: entity.id.entityType, id: entity.id.id }; | |
249 | 341 | } |
250 | 342 | |
251 | - function entitiesToEntitiesInfo(entityType, entities) { | |
343 | + function entityRelationInfoToEntityInfo(entityRelationInfo, direction) { | |
344 | + var entityId = direction == types.entitySearchDirection.from ? entityRelationInfo.to : entityRelationInfo.from; | |
345 | + var name = direction == types.entitySearchDirection.from ? entityRelationInfo.toName : entityRelationInfo.fromName; | |
346 | + return { | |
347 | + name: name, | |
348 | + entityType: entityId.entityType, | |
349 | + id: entityId.id | |
350 | + }; | |
351 | + } | |
352 | + | |
353 | + function entitiesToEntitiesInfo(entities) { | |
252 | 354 | var entitiesInfo = []; |
253 | 355 | for (var d = 0; d < entities.length; d++) { |
254 | - entitiesInfo.push(entityToEntityInfo(entityType, entities[d])); | |
356 | + entitiesInfo.push(entityToEntityInfo(entities[d])); | |
255 | 357 | } |
256 | 358 | return entitiesInfo; |
257 | 359 | } |
258 | 360 | |
259 | - function processEntityAlias(index, aliasIds, entityAliases, resolution, deferred) { | |
260 | - if (index < aliasIds.length) { | |
261 | - var aliasId = aliasIds[index]; | |
262 | - var entityAlias = entityAliases[aliasId]; | |
263 | - var alias = entityAlias.alias; | |
264 | - var entityFilter = entityAlias.entityFilter; | |
265 | - if (entityFilter.useFilter) { | |
266 | - var entityNameFilter = entityFilter.entityNameFilter; | |
267 | - getEntitiesByNameFilter(entityAlias.entityType, entityNameFilter, 100).then( | |
268 | - function(entities) { | |
269 | - if (entities && entities != null) { | |
270 | - var resolvedAlias = {alias: alias, entityType: entityAlias.entityType, entityId: entities[0].id.id}; | |
271 | - resolution.aliasesInfo.entityAliases[aliasId] = resolvedAlias; | |
272 | - resolution.aliasesInfo.entityAliasesInfo[aliasId] = entitiesToEntitiesInfo(entityAlias.entityType, entities); | |
273 | - index++; | |
274 | - processEntityAlias(index, aliasIds, entityAliases, resolution, deferred); | |
361 | + function entityRelationInfosToEntitiesInfo(entityRelations, direction) { | |
362 | + var entitiesInfo = []; | |
363 | + for (var d = 0; d < entityRelations.length; d++) { | |
364 | + entitiesInfo.push(entityRelationInfoToEntityInfo(entityRelations[d], direction)); | |
365 | + } | |
366 | + return entitiesInfo; | |
367 | + } | |
368 | + | |
369 | + | |
370 | + function resolveAlias(entityAlias, stateParams) { | |
371 | + var deferred = $q.defer(); | |
372 | + var filter = entityAlias.filter; | |
373 | + resolveAliasFilter(filter, stateParams, -1).then( | |
374 | + function (result) { | |
375 | + var aliasInfo = { | |
376 | + alias: entityAlias.alias, | |
377 | + stateEntity: result.stateEntity, | |
378 | + resolveMultiple: filter.resolveMultiple | |
379 | + }; | |
380 | + aliasInfo.resolvedEntities = result.entities; | |
381 | + aliasInfo.currentEntity = null; | |
382 | + if (aliasInfo.resolvedEntities.length) { | |
383 | + aliasInfo.currentEntity = aliasInfo.resolvedEntities[0]; | |
384 | + } | |
385 | + deferred.resolve(aliasInfo); | |
386 | + }, | |
387 | + function fail() { | |
388 | + deferred.reject(); | |
389 | + } | |
390 | + ); | |
391 | + return deferred.promise; | |
392 | + } | |
393 | + | |
394 | + function resolveAliasFilter(filter, stateParams, maxItems) { | |
395 | + var deferred = $q.defer(); | |
396 | + var result = { | |
397 | + entities: [], | |
398 | + stateEntity: false | |
399 | + }; | |
400 | + switch (filter.type) { | |
401 | + case types.aliasFilterType.entityList.value: | |
402 | + getEntities(filter.entityType, filter.entityList).then( | |
403 | + function success(entities) { | |
404 | + if (entities && entities.length) { | |
405 | + result.entities = entitiesToEntitiesInfo(entities); | |
406 | + deferred.resolve(result); | |
275 | 407 | } else { |
276 | - if (!resolution.error) { | |
277 | - resolution.error = 'dashboard.invalid-aliases-config'; | |
278 | - } | |
279 | - index++; | |
280 | - processEntityAlias(index, aliasIds, entityAliases, resolution, deferred); | |
408 | + deferred.reject(); | |
281 | 409 | } |
282 | - }); | |
283 | - } else { | |
284 | - var entityList = entityFilter.entityList; | |
285 | - getEntities(entityAlias.entityType, entityList).then( | |
410 | + }, | |
411 | + function fail() { | |
412 | + deferred.reject(); | |
413 | + } | |
414 | + ); | |
415 | + break; | |
416 | + case types.aliasFilterType.entityName.value: | |
417 | + getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems).then( | |
286 | 418 | function success(entities) { |
287 | - if (entities && entities.length > 0) { | |
288 | - var resolvedAlias = {alias: alias, entityType: entityAlias.entityType, entityId: entities[0].id.id}; | |
289 | - resolution.aliasesInfo.entityAliases[aliasId] = resolvedAlias; | |
290 | - resolution.aliasesInfo.entityAliasesInfo[aliasId] = entitiesToEntitiesInfo(entityAlias.entityType, entities); | |
291 | - index++; | |
292 | - processEntityAlias(index, aliasIds, entityAliases, resolution, deferred); | |
419 | + if (entities && entities.length) { | |
420 | + result.entities = entitiesToEntitiesInfo(entities); | |
421 | + deferred.resolve(result); | |
293 | 422 | } else { |
294 | - if (!resolution.error) { | |
295 | - resolution.error = 'dashboard.invalid-aliases-config'; | |
296 | - } | |
297 | - index++; | |
298 | - processEntityAlias(index, aliasIds, entityAliases, resolution, deferred); | |
423 | + deferred.reject(); | |
424 | + } | |
425 | + }, | |
426 | + function fail() { | |
427 | + deferred.reject(); | |
428 | + } | |
429 | + ); | |
430 | + break; | |
431 | + case types.aliasFilterType.stateEntity.value: | |
432 | + result.stateEntity = true; | |
433 | + if (stateParams && stateParams.entityId) { | |
434 | + getEntity(stateParams.entityId.entityType, stateParams.entityId.id).then( | |
435 | + function success(entity) { | |
436 | + result.entities = entitiesToEntitiesInfo([entity]); | |
437 | + deferred.resolve(result); | |
438 | + }, | |
439 | + function fail() { | |
440 | + deferred.reject(); | |
441 | + } | |
442 | + ); | |
443 | + } else { | |
444 | + deferred.resolve(result); | |
445 | + } | |
446 | + break; | |
447 | + case types.aliasFilterType.assetType.value: | |
448 | + getEntitiesByNameFilter(types.entityType.asset, filter.assetNameFilter, maxItems, null, filter.assetType).then( | |
449 | + function success(entities) { | |
450 | + if (entities && entities.length) { | |
451 | + result.entities = entitiesToEntitiesInfo(entities); | |
452 | + deferred.resolve(result); | |
453 | + } else { | |
454 | + deferred.reject(); | |
299 | 455 | } |
300 | 456 | }, |
301 | 457 | function fail() { |
302 | - if (!resolution.error) { | |
303 | - resolution.error = 'dashboard.invalid-aliases-config'; | |
458 | + deferred.reject(); | |
459 | + } | |
460 | + ); | |
461 | + break; | |
462 | + case types.aliasFilterType.deviceType.value: | |
463 | + getEntitiesByNameFilter(types.entityType.device, filter.deviceNameFilter, maxItems, null, filter.deviceType).then( | |
464 | + function success(entities) { | |
465 | + if (entities && entities.length) { | |
466 | + result.entities = entitiesToEntitiesInfo(entities); | |
467 | + deferred.resolve(result); | |
468 | + } else { | |
469 | + deferred.reject(); | |
304 | 470 | } |
305 | - index++; | |
306 | - processEntityAlias(index, aliasIds, entityAliases, resolution, deferred); | |
471 | + }, | |
472 | + function fail() { | |
473 | + deferred.reject(); | |
307 | 474 | } |
308 | 475 | ); |
476 | + break; | |
477 | + case types.aliasFilterType.relationsQuery.value: | |
478 | + result.stateEntity = filter.rootStateEntity; | |
479 | + var rootEntityType; | |
480 | + var rootEntityId; | |
481 | + if (result.stateEntity && stateParams && stateParams.entityId) { | |
482 | + rootEntityType = stateParams.entityId.entityType; | |
483 | + rootEntityId = stateParams.entityId.id; | |
484 | + } else if (!result.stateEntity) { | |
485 | + rootEntityType = filter.rootEntity.entityType; | |
486 | + rootEntityId = filter.rootEntity.id; | |
487 | + } | |
488 | + if (rootEntityType && rootEntityId) { | |
489 | + var searchQuery = { | |
490 | + parameters: { | |
491 | + rootId: rootEntityId, | |
492 | + rootType: rootEntityType, | |
493 | + direction: filter.direction | |
494 | + }, | |
495 | + filters: filter.filters | |
496 | + }; | |
497 | + searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1; | |
498 | + entityRelationService.findInfoByQuery(searchQuery).then( | |
499 | + function success(allRelations) { | |
500 | + if (allRelations && allRelations.length) { | |
501 | + if (angular.isDefined(maxItems) && maxItems > 0) { | |
502 | + var limit = Math.min(allRelations.length, maxItems); | |
503 | + allRelations.length = limit; | |
504 | + } | |
505 | + result.entities = entityRelationInfosToEntitiesInfo(allRelations, filter.direction); | |
506 | + deferred.resolve(result); | |
507 | + } else { | |
508 | + deferred.reject(); | |
509 | + } | |
510 | + }, | |
511 | + function fail() { | |
512 | + deferred.reject(); | |
513 | + } | |
514 | + ); | |
515 | + } else { | |
516 | + deferred.resolve(result); | |
517 | + } | |
518 | + break; | |
519 | + case types.aliasFilterType.assetSearchQuery.value: | |
520 | + case types.aliasFilterType.deviceSearchQuery.value: | |
521 | + result.stateEntity = filter.rootStateEntity; | |
522 | + if (result.stateEntity && stateParams && stateParams.entityId) { | |
523 | + rootEntityType = stateParams.entityId.entityType; | |
524 | + rootEntityId = stateParams.entityId.id; | |
525 | + } else if (!result.stateEntity) { | |
526 | + rootEntityType = filter.rootEntity.entityType; | |
527 | + rootEntityId = filter.rootEntity.id; | |
528 | + } | |
529 | + if (rootEntityType && rootEntityId) { | |
530 | + searchQuery = { | |
531 | + parameters: { | |
532 | + rootId: rootEntityId, | |
533 | + rootType: rootEntityType, | |
534 | + direction: filter.direction | |
535 | + }, | |
536 | + relationType: filter.relationType | |
537 | + }; | |
538 | + searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1; | |
539 | + var findByQueryPromise; | |
540 | + if (filter.type == types.aliasFilterType.assetSearchQuery.value) { | |
541 | + searchQuery.assetTypes = filter.assetTypes; | |
542 | + findByQueryPromise = assetService.findByQuery(searchQuery, false); | |
543 | + } else if (filter.type == types.aliasFilterType.deviceSearchQuery.value) { | |
544 | + searchQuery.deviceTypes = filter.deviceTypes; | |
545 | + findByQueryPromise = deviceService.findByQuery(searchQuery, false); | |
546 | + } | |
547 | + findByQueryPromise.then( | |
548 | + function success(entities) { | |
549 | + if (entities && entities.length) { | |
550 | + if (angular.isDefined(maxItems) && maxItems > 0) { | |
551 | + var limit = Math.min(entities.length, maxItems); | |
552 | + entities.length = limit; | |
553 | + } | |
554 | + result.entities = entitiesToEntitiesInfo(entities); | |
555 | + deferred.resolve(result); | |
556 | + } else { | |
557 | + deferred.reject(); | |
558 | + } | |
559 | + }, | |
560 | + function fail() { | |
561 | + deferred.reject(); | |
562 | + } | |
563 | + ); | |
564 | + } else { | |
565 | + deferred.resolve(result); | |
566 | + } | |
567 | + break; | |
568 | + } | |
569 | + return deferred.promise; | |
570 | + } | |
571 | + | |
572 | + function filterAliasByEntityTypes(entityAlias, entityTypes) { | |
573 | + var filter = entityAlias.filter; | |
574 | + if (filterAliasFilterTypeByEntityTypes(filter.type, entityTypes)) { | |
575 | + switch (filter.type) { | |
576 | + case types.aliasFilterType.entityList.value: | |
577 | + return entityTypes.indexOf(filter.entityType) > -1 ? true : false; | |
578 | + case types.aliasFilterType.entityName.value: | |
579 | + return entityTypes.indexOf(filter.entityType) > -1 ? true : false; | |
580 | + case types.aliasFilterType.stateEntity.value: | |
581 | + return true; | |
582 | + case types.aliasFilterType.assetType.value: | |
583 | + return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false; | |
584 | + case types.aliasFilterType.deviceType.value: | |
585 | + return entityTypes.indexOf(types.entityType.device) > -1 ? true : false; | |
586 | + case types.aliasFilterType.relationsQuery.value: | |
587 | + if (filter.filters && filter.filters.length) { | |
588 | + var match = false; | |
589 | + for (var f=0;f<filter.filters.length;f++) { | |
590 | + var relationFilter = filter.filters[f]; | |
591 | + if (relationFilter.entityTypes && relationFilter.entityTypes.length) { | |
592 | + for (var et=0;et<relationFilter.entityTypes.length;et++) { | |
593 | + if (entityTypes.indexOf(relationFilter.entityTypes[et]) > -1) { | |
594 | + match = true; | |
595 | + break; | |
596 | + } | |
597 | + } | |
598 | + } else { | |
599 | + match = true; | |
600 | + break; | |
601 | + } | |
602 | + } | |
603 | + return match; | |
604 | + } else { | |
605 | + return true; | |
606 | + } | |
607 | + case types.aliasFilterType.assetSearchQuery.value: | |
608 | + return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false; | |
609 | + case types.aliasFilterType.deviceSearchQuery.value: | |
610 | + return entityTypes.indexOf(types.entityType.device) > -1 ? true : false; | |
309 | 611 | } |
310 | - } else { | |
311 | - deferred.resolve(resolution); | |
312 | 612 | } |
613 | + return false; | |
313 | 614 | } |
314 | 615 | |
315 | - function processEntityAliases(entityAliases) { | |
316 | - var deferred = $q.defer(); | |
317 | - var resolution = { | |
318 | - aliasesInfo: { | |
319 | - entityAliases: {}, | |
320 | - entityAliasesInfo: {} | |
616 | + function filterAliasFilterTypeByEntityType(aliasFilterType, entityType) { | |
617 | + switch (aliasFilterType) { | |
618 | + case types.aliasFilterType.entityList.value: | |
619 | + return true; | |
620 | + case types.aliasFilterType.entityName.value: | |
621 | + return true; | |
622 | + case types.aliasFilterType.stateEntity.value: | |
623 | + return true; | |
624 | + case types.aliasFilterType.assetType.value: | |
625 | + return entityType === types.entityType.asset; | |
626 | + case types.aliasFilterType.deviceType.value: | |
627 | + return entityType === types.entityType.device; | |
628 | + case types.aliasFilterType.relationsQuery.value: | |
629 | + return true; | |
630 | + case types.aliasFilterType.assetSearchQuery.value: | |
631 | + return entityType === types.entityType.asset; | |
632 | + case types.aliasFilterType.deviceSearchQuery.value: | |
633 | + return entityType === types.entityType.device; | |
634 | + } | |
635 | + return false; | |
636 | + } | |
637 | + | |
638 | + function filterAliasFilterTypeByEntityTypes(aliasFilterType, entityTypes) { | |
639 | + if (!entityTypes || !entityTypes.length) { | |
640 | + return true; | |
641 | + } | |
642 | + var valid = false; | |
643 | + entityTypes.forEach(function(entityType) { | |
644 | + valid = valid || filterAliasFilterTypeByEntityType(aliasFilterType, entityType); | |
645 | + }); | |
646 | + return valid; | |
647 | + } | |
648 | + | |
649 | + function getAliasFilterTypesByEntityTypes(entityTypes) { | |
650 | + var allAliasFilterTypes = types.aliasFilterType; | |
651 | + if (!entityTypes || !entityTypes.length) { | |
652 | + return allAliasFilterTypes; | |
653 | + } | |
654 | + var result = {}; | |
655 | + for (var type in allAliasFilterTypes) { | |
656 | + var aliasFilterType = allAliasFilterTypes[type]; | |
657 | + if (filterAliasFilterTypeByEntityTypes(aliasFilterType.value, entityTypes)) { | |
658 | + result[type] = aliasFilterType; | |
321 | 659 | } |
322 | - }; | |
323 | - var aliasIds = []; | |
324 | - if (entityAliases) { | |
325 | - for (var aliasId in entityAliases) { | |
326 | - aliasIds.push(aliasId); | |
660 | + } | |
661 | + return result; | |
662 | + } | |
663 | + | |
664 | + function prepareAllowedEntityTypesList(allowedEntityTypes) { | |
665 | + var authority = userService.getAuthority(); | |
666 | + var entityTypes = {}; | |
667 | + switch(authority) { | |
668 | + case 'SYS_ADMIN': | |
669 | + entityTypes.tenant = types.entityType.tenant; | |
670 | + entityTypes.rule = types.entityType.rule; | |
671 | + entityTypes.plugin = types.entityType.plugin; | |
672 | + break; | |
673 | + case 'TENANT_ADMIN': | |
674 | + entityTypes.device = types.entityType.device; | |
675 | + entityTypes.asset = types.entityType.asset; | |
676 | + entityTypes.tenant = types.entityType.tenant; | |
677 | + entityTypes.customer = types.entityType.customer; | |
678 | + entityTypes.rule = types.entityType.rule; | |
679 | + entityTypes.plugin = types.entityType.plugin; | |
680 | + entityTypes.dashboard = types.entityType.dashboard; | |
681 | + break; | |
682 | + case 'CUSTOMER_USER': | |
683 | + entityTypes.device = types.entityType.device; | |
684 | + entityTypes.asset = types.entityType.asset; | |
685 | + entityTypes.customer = types.entityType.customer; | |
686 | + entityTypes.dashboard = types.entityType.dashboard; | |
687 | + break; | |
688 | + } | |
689 | + | |
690 | + if (allowedEntityTypes) { | |
691 | + for (var entityType in entityTypes) { | |
692 | + if (allowedEntityTypes.indexOf(entityTypes[entityType]) === -1) { | |
693 | + delete entityTypes[entityType]; | |
694 | + } | |
327 | 695 | } |
328 | 696 | } |
329 | - processEntityAlias(0, aliasIds, entityAliases, resolution, deferred); | |
697 | + return entityTypes; | |
698 | + } | |
699 | + | |
700 | + | |
701 | + function checkEntityAlias(entityAlias) { | |
702 | + var deferred = $q.defer(); | |
703 | + resolveAliasFilter(entityAlias.filter, null, 1).then( | |
704 | + function success(result) { | |
705 | + if (result.stateEntity) { | |
706 | + deferred.resolve(true); | |
707 | + } else { | |
708 | + var entities = result.entities; | |
709 | + if (entities && entities.length) { | |
710 | + deferred.resolve(true); | |
711 | + } else { | |
712 | + deferred.resolve(false); | |
713 | + } | |
714 | + } | |
715 | + }, | |
716 | + function fail() { | |
717 | + deferred.resolve(false); | |
718 | + } | |
719 | + ); | |
330 | 720 | return deferred.promise; |
331 | 721 | } |
332 | 722 | |
... | ... | @@ -354,40 +744,13 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
354 | 744 | } |
355 | 745 | } |
356 | 746 | deferred.resolve(result); |
357 | - }, function fail(response) { | |
358 | - deferred.reject(response.data); | |
747 | + }, function fail() { | |
748 | + deferred.reject(); | |
359 | 749 | }); |
360 | 750 | return deferred.promise; |
361 | 751 | } |
362 | 752 | |
363 | - function checkEntityAlias(entityAlias) { | |
364 | - var deferred = $q.defer(); | |
365 | - var entityType = entityAlias.entityType; | |
366 | - var entityFilter = entityAlias.entityFilter; | |
367 | - var promise; | |
368 | - if (entityFilter.useFilter) { | |
369 | - var entityNameFilter = entityFilter.entityNameFilter; | |
370 | - promise = getEntitiesByNameFilter(entityType, entityNameFilter, 1); | |
371 | - } else { | |
372 | - var entityList = entityFilter.entityList; | |
373 | - promise = getEntities(entityType, entityList); | |
374 | - } | |
375 | - promise.then( | |
376 | - function success(entities) { | |
377 | - if (entities && entities.length > 0) { | |
378 | - deferred.resolve(true); | |
379 | - } else { | |
380 | - deferred.resolve(false); | |
381 | - } | |
382 | - }, | |
383 | - function fail() { | |
384 | - deferred.resolve(false); | |
385 | - } | |
386 | - ); | |
387 | - return deferred.promise; | |
388 | - } | |
389 | - | |
390 | - function createDatasoucesFromSubscriptionsInfo(subscriptionsInfo) { | |
753 | + function createDatasourcesFromSubscriptionsInfo(subscriptionsInfo) { | |
391 | 754 | var deferred = $q.defer(); |
392 | 755 | var datasources = []; |
393 | 756 | processSubscriptionsInfo(0, subscriptionsInfo, datasources, deferred); |
... | ... | @@ -822,4 +1185,4 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device |
822 | 1185 | } |
823 | 1186 | } |
824 | 1187 | |
825 | -} | |
\ No newline at end of file | ||
1188 | +} | ... | ... |
... | ... | @@ -39,6 +39,9 @@ export default class Subscription { |
39 | 39 | this.cafs = {}; |
40 | 40 | this.registrations = []; |
41 | 41 | |
42 | + var subscription = this; | |
43 | + var deferred = this.ctx.$q.defer(); | |
44 | + | |
42 | 45 | if (this.type === this.ctx.types.widgetType.rpc.value) { |
43 | 46 | this.callbacks.rpcStateChanged = this.callbacks.rpcStateChanged || function(){}; |
44 | 47 | this.callbacks.onRpcSuccess = this.callbacks.onRpcSuccess || function(){}; |
... | ... | @@ -56,7 +59,11 @@ export default class Subscription { |
56 | 59 | this.rpcEnabled = false; |
57 | 60 | this.executingRpcRequest = false; |
58 | 61 | this.executingPromises = []; |
59 | - this.initRpc(); | |
62 | + this.initRpc().then( | |
63 | + function() { | |
64 | + deferred.resolve(subscription); | |
65 | + } | |
66 | + ); | |
60 | 67 | } else { |
61 | 68 | this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || function(){}; |
62 | 69 | this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || function(){}; |
... | ... | @@ -66,6 +73,15 @@ export default class Subscription { |
66 | 73 | |
67 | 74 | this.datasources = this.ctx.utils.validateDatasources(options.datasources); |
68 | 75 | this.datasourceListeners = []; |
76 | + | |
77 | + /* | |
78 | + * data = array of datasourceData | |
79 | + * datasourceData = { | |
80 | + * tbDatasource, | |
81 | + * dataKey, { name, config } | |
82 | + * data = array of [time, value] | |
83 | + * } | |
84 | + */ | |
69 | 85 | this.data = []; |
70 | 86 | this.hiddenData = []; |
71 | 87 | this.originalTimewindow = null; |
... | ... | @@ -103,11 +119,41 @@ export default class Subscription { |
103 | 119 | this.legendConfig.showMax === true || |
104 | 120 | this.legendConfig.showAvg === true || |
105 | 121 | this.legendConfig.showTotal === true); |
106 | - this.initDataSubscription(); | |
122 | + this.initDataSubscription().then( | |
123 | + function success() { | |
124 | + deferred.resolve(subscription); | |
125 | + }, | |
126 | + function fail() { | |
127 | + deferred.reject(); | |
128 | + } | |
129 | + ); | |
107 | 130 | } |
131 | + | |
132 | + return deferred.promise; | |
108 | 133 | } |
109 | 134 | |
110 | 135 | initDataSubscription() { |
136 | + var deferred = this.ctx.$q.defer(); | |
137 | + if (!this.ctx.aliasController) { | |
138 | + this.configureData(); | |
139 | + deferred.resolve(); | |
140 | + } else { | |
141 | + var subscription = this; | |
142 | + this.ctx.aliasController.resolveDatasources(this.datasources).then( | |
143 | + function success(datasources) { | |
144 | + subscription.datasources = datasources; | |
145 | + subscription.configureData(); | |
146 | + deferred.resolve(); | |
147 | + }, | |
148 | + function fail() { | |
149 | + deferred.reject(); | |
150 | + } | |
151 | + ); | |
152 | + } | |
153 | + return deferred.promise; | |
154 | + } | |
155 | + | |
156 | + configureData() { | |
111 | 157 | var dataIndex = 0; |
112 | 158 | for (var i = 0; i < this.datasources.length; i++) { |
113 | 159 | var datasource = this.datasources[i]; |
... | ... | @@ -199,21 +245,46 @@ export default class Subscription { |
199 | 245 | } |
200 | 246 | |
201 | 247 | initRpc() { |
248 | + var deferred = this.ctx.$q.defer(); | |
202 | 249 | if (this.targetDeviceAliasIds && this.targetDeviceAliasIds.length > 0) { |
203 | 250 | this.targetDeviceAliasId = this.targetDeviceAliasIds[0]; |
204 | - if (this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId]) { | |
205 | - this.targetDeviceId = this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId].entityId; | |
251 | + var subscription = this; | |
252 | + this.ctx.aliasController.getAliasInfo(this.targetDeviceAliasId).then( | |
253 | + function success(aliasInfo) { | |
254 | + if (aliasInfo.currentEntity && aliasInfo.currentEntity.entityType == subscription.ctx.types.entityType.device) { | |
255 | + subscription.targetDeviceId = aliasInfo.currentEntity.id; | |
256 | + if (subscription.targetDeviceId) { | |
257 | + subscription.rpcEnabled = true; | |
258 | + } else { | |
259 | + subscription.rpcEnabled = subscription.ctx.$scope.widgetEditMode ? true : false; | |
260 | + } | |
261 | + subscription.callbacks.rpcStateChanged(subscription); | |
262 | + deferred.resolve(); | |
263 | + } else { | |
264 | + subscription.rpcEnabled = false; | |
265 | + subscription.callbacks.rpcStateChanged(subscription); | |
266 | + deferred.resolve(); | |
267 | + } | |
268 | + }, | |
269 | + function fail () { | |
270 | + subscription.rpcEnabled = false; | |
271 | + subscription.callbacks.rpcStateChanged(subscription); | |
272 | + deferred.resolve(); | |
273 | + } | |
274 | + ); | |
275 | + } else { | |
276 | + if (this.targetDeviceIds && this.targetDeviceIds.length > 0) { | |
277 | + this.targetDeviceId = this.targetDeviceIds[0]; | |
206 | 278 | } |
207 | - } else if (this.targetDeviceIds && this.targetDeviceIds.length > 0) { | |
208 | - this.targetDeviceId = this.targetDeviceIds[0]; | |
209 | - } | |
210 | - | |
211 | - if (this.targetDeviceId) { | |
212 | - this.rpcEnabled = true; | |
213 | - } else { | |
214 | - this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false; | |
279 | + if (this.targetDeviceId) { | |
280 | + this.rpcEnabled = true; | |
281 | + } else { | |
282 | + this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false; | |
283 | + } | |
284 | + this.callbacks.rpcStateChanged(this); | |
285 | + deferred.resolve(); | |
215 | 286 | } |
216 | - this.callbacks.rpcStateChanged(this); | |
287 | + return deferred.promise; | |
217 | 288 | } |
218 | 289 | |
219 | 290 | clearRpcError() { |
... | ... | @@ -319,11 +390,11 @@ export default class Subscription { |
319 | 390 | this.onDataUpdated(); |
320 | 391 | } |
321 | 392 | |
322 | - onAliasesChanged() { | |
393 | + onAliasesChanged(aliasIds) { | |
323 | 394 | if (this.type === this.ctx.types.widgetType.rpc.value) { |
324 | - this.checkRpcTarget(); | |
395 | + return this.checkRpcTarget(aliasIds); | |
325 | 396 | } else { |
326 | - this.checkSubscriptions(); | |
397 | + return this.checkSubscriptions(aliasIds); | |
327 | 398 | } |
328 | 399 | } |
329 | 400 | |
... | ... | @@ -481,39 +552,6 @@ export default class Subscription { |
481 | 552 | var datasource = this.datasources[i]; |
482 | 553 | if (angular.isFunction(datasource)) |
483 | 554 | continue; |
484 | - var entityId = null; | |
485 | - var entityType = null; | |
486 | - if (datasource.type === this.ctx.types.datasourceType.entity) { | |
487 | - var aliasName = null; | |
488 | - var entityName = null; | |
489 | - if (datasource.entityId) { | |
490 | - entityId = datasource.entityId; | |
491 | - entityType = datasource.entityType; | |
492 | - datasource.name = datasource.entityName; | |
493 | - aliasName = datasource.entityName; | |
494 | - entityName = datasource.entityName; | |
495 | - } else if (datasource.entityAliasId) { | |
496 | - if (this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId]) { | |
497 | - entityId = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].entityId; | |
498 | - entityType = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].entityType; | |
499 | - datasource.name = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].alias; | |
500 | - aliasName = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].alias; | |
501 | - entityName = ''; | |
502 | - var entitiesInfo = this.ctx.aliasesInfo.entityAliasesInfo[datasource.entityAliasId]; | |
503 | - for (var d = 0; d < entitiesInfo.length; d++) { | |
504 | - if (entitiesInfo[d].id === entityId) { | |
505 | - entityName = entitiesInfo[d].name; | |
506 | - break; | |
507 | - } | |
508 | - } | |
509 | - } | |
510 | - } | |
511 | - } else { | |
512 | - datasource.name = datasource.name || this.ctx.types.datasourceType.function; | |
513 | - } | |
514 | - for (var dk = 0; dk < datasource.dataKeys.length; dk++) { | |
515 | - updateDataKeyLabel(datasource.dataKeys[dk], datasource.name, entityName, aliasName); | |
516 | - } | |
517 | 555 | |
518 | 556 | var subscription = this; |
519 | 557 | |
... | ... | @@ -521,8 +559,8 @@ export default class Subscription { |
521 | 559 | subscriptionType: this.type, |
522 | 560 | subscriptionTimewindow: this.subscriptionTimewindow, |
523 | 561 | datasource: datasource, |
524 | - entityType: entityType, | |
525 | - entityId: entityId, | |
562 | + entityType: datasource.entityType, | |
563 | + entityId: datasource.entityId, | |
526 | 564 | dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) { |
527 | 565 | subscription.dataUpdated(data, datasourceIndex, dataKeyIndex, apply); |
528 | 566 | }, |
... | ... | @@ -544,6 +582,10 @@ export default class Subscription { |
544 | 582 | |
545 | 583 | this.datasourceListeners.push(listener); |
546 | 584 | this.ctx.datasourceService.subscribeToDatasource(listener); |
585 | + if (datasource.unresolvedStateEntity) { | |
586 | + this.notifyDataLoaded(); | |
587 | + this.onDataUpdated(); | |
588 | + } | |
547 | 589 | } |
548 | 590 | } |
549 | 591 | |
... | ... | @@ -557,48 +599,26 @@ export default class Subscription { |
557 | 599 | } |
558 | 600 | } |
559 | 601 | |
560 | - checkRpcTarget() { | |
561 | - var deviceId = null; | |
562 | - if (this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId]) { | |
563 | - deviceId = this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId].entityId; | |
564 | - } | |
565 | - if (!angular.equals(deviceId, this.targetDeviceId)) { | |
566 | - this.targetDeviceId = deviceId; | |
567 | - if (this.targetDeviceId) { | |
568 | - this.rpcEnabled = true; | |
569 | - } else { | |
570 | - this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false; | |
571 | - } | |
572 | - this.callbacks.rpcStateChanged(this); | |
602 | + checkRpcTarget(aliasIds) { | |
603 | + if (aliasIds.indexOf(this.targetDeviceAliasId) > -1) { | |
604 | + return true; | |
605 | + } else { | |
606 | + return false; | |
573 | 607 | } |
574 | 608 | } |
575 | 609 | |
576 | - checkSubscriptions() { | |
610 | + checkSubscriptions(aliasIds) { | |
577 | 611 | var subscriptionsChanged = false; |
578 | 612 | for (var i = 0; i < this.datasourceListeners.length; i++) { |
579 | 613 | var listener = this.datasourceListeners[i]; |
580 | - var entityId = null; | |
581 | - var entityType = null; | |
582 | - var aliasName = null; | |
583 | - if (listener.datasource.type === this.ctx.types.datasourceType.entity) { | |
584 | - if (listener.datasource.entityAliasId && | |
585 | - this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId]) { | |
586 | - entityId = this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId].entityId; | |
587 | - entityType = this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId].entityType; | |
588 | - aliasName = this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId].alias; | |
589 | - } | |
590 | - if (!angular.equals(entityId, listener.entityId) || | |
591 | - !angular.equals(entityType, listener.entityType) || | |
592 | - !angular.equals(aliasName, listener.datasource.name)) { | |
614 | + if (listener.datasource.entityAliasId) { | |
615 | + if (aliasIds.indexOf(listener.datasource.entityAliasId) > -1) { | |
593 | 616 | subscriptionsChanged = true; |
594 | 617 | break; |
595 | 618 | } |
596 | 619 | } |
597 | 620 | } |
598 | - if (subscriptionsChanged) { | |
599 | - this.unsubscribe(); | |
600 | - this.subscribe(); | |
601 | - } | |
621 | + return subscriptionsChanged; | |
602 | 622 | } |
603 | 623 | |
604 | 624 | destroy() { |
... | ... | @@ -617,29 +637,6 @@ export default class Subscription { |
617 | 637 | |
618 | 638 | } |
619 | 639 | |
620 | -const varsRegex = /\$\{([^\}]*)\}/g; | |
621 | - | |
622 | -function updateDataKeyLabel(dataKey, dsName, entityName, aliasName) { | |
623 | - var pattern = dataKey.pattern; | |
624 | - var label = dataKey.pattern; | |
625 | - var match = varsRegex.exec(pattern); | |
626 | - while (match !== null) { | |
627 | - var variable = match[0]; | |
628 | - var variableName = match[1]; | |
629 | - if (variableName === 'dsName') { | |
630 | - label = label.split(variable).join(dsName); | |
631 | - } else if (variableName === 'entityName') { | |
632 | - label = label.split(variable).join(entityName); | |
633 | - } else if (variableName === 'deviceName') { | |
634 | - label = label.split(variable).join(entityName); | |
635 | - } else if (variableName === 'aliasName') { | |
636 | - label = label.split(variable).join(aliasName); | |
637 | - } | |
638 | - match = varsRegex.exec(pattern); | |
639 | - } | |
640 | - dataKey.label = label; | |
641 | -} | |
642 | - | |
643 | 640 | function calculateMin(data) { |
644 | 641 | if (data.length > 0) { |
645 | 642 | var result = Number(data[0][1]); | ... | ... |
... | ... | @@ -23,8 +23,10 @@ function DashboardUtils(types, utils, timeService) { |
23 | 23 | |
24 | 24 | var service = { |
25 | 25 | validateAndUpdateDashboard: validateAndUpdateDashboard, |
26 | + validateAndUpdateWidget: validateAndUpdateWidget, | |
26 | 27 | getRootStateId: getRootStateId, |
27 | 28 | createSingleWidgetDashboard: createSingleWidgetDashboard, |
29 | + createSingleEntityFilter: createSingleEntityFilter, | |
28 | 30 | getStateLayoutsData: getStateLayoutsData, |
29 | 31 | createDefaultState: createDefaultState, |
30 | 32 | createDefaultLayoutData: createDefaultLayoutData, |
... | ... | @@ -39,40 +41,104 @@ function DashboardUtils(types, utils, timeService) { |
39 | 41 | |
40 | 42 | return service; |
41 | 43 | |
42 | - function validateAndUpdateEntityAliases(configuration) { | |
44 | + function validateAndUpdateEntityAliases(configuration, datasourcesByAliasId, targetDevicesByAliasId) { | |
45 | + var aliasId, entityAlias; | |
43 | 46 | if (angular.isUndefined(configuration.entityAliases)) { |
44 | 47 | configuration.entityAliases = {}; |
45 | 48 | if (configuration.deviceAliases) { |
46 | 49 | var deviceAliases = configuration.deviceAliases; |
47 | - for (var aliasId in deviceAliases) { | |
50 | + for (aliasId in deviceAliases) { | |
48 | 51 | var deviceAlias = deviceAliases[aliasId]; |
49 | - var alias = deviceAlias.alias; | |
50 | - var entityFilter = { | |
51 | - useFilter: false, | |
52 | - entityNameFilter: '', | |
53 | - entityList: [] | |
54 | - } | |
55 | - if (deviceAlias.deviceFilter) { | |
56 | - entityFilter.useFilter = deviceAlias.deviceFilter.useFilter; | |
57 | - entityFilter.entityNameFilter = deviceAlias.deviceFilter.deviceNameFilter; | |
58 | - entityFilter.entityList = deviceAlias.deviceFilter.deviceList; | |
59 | - } else if (deviceAlias.deviceId) { | |
60 | - entityFilter.entityList = [deviceAlias.deviceId]; | |
61 | - } | |
62 | - var entityAlias = { | |
63 | - id: aliasId, | |
64 | - alias: alias, | |
65 | - entityType: types.entityType.device, | |
66 | - entityFilter: entityFilter | |
67 | - }; | |
68 | - configuration.entityAliases[aliasId] = entityAlias; | |
52 | + entityAlias = validateAndUpdateDeviceAlias(aliasId, deviceAlias, datasourcesByAliasId, targetDevicesByAliasId); | |
53 | + configuration.entityAliases[entityAlias.id] = entityAlias; | |
69 | 54 | } |
70 | 55 | delete configuration.deviceAliases; |
71 | 56 | } |
57 | + } else { | |
58 | + var entityAliases = configuration.entityAliases; | |
59 | + for (aliasId in entityAliases) { | |
60 | + entityAlias = entityAliases[aliasId]; | |
61 | + entityAlias = validateAndUpdateEntityAlias(aliasId, entityAlias, datasourcesByAliasId, targetDevicesByAliasId); | |
62 | + if (aliasId != entityAlias.id) { | |
63 | + delete entityAliases[aliasId]; | |
64 | + } | |
65 | + entityAliases[entityAlias.id] = entityAlias; | |
66 | + } | |
72 | 67 | } |
73 | 68 | return configuration; |
74 | 69 | } |
75 | 70 | |
71 | + function validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId) { | |
72 | + if (!aliasId || !angular.isString(aliasId) || aliasId.length != 36) { | |
73 | + var newAliasId = utils.guid(); | |
74 | + var aliasDatasources = datasourcesByAliasId[aliasId]; | |
75 | + if (aliasDatasources) { | |
76 | + aliasDatasources.forEach( | |
77 | + function(datasource) { | |
78 | + datasource.entityAliasId = newAliasId; | |
79 | + } | |
80 | + ); | |
81 | + } | |
82 | + var targetDeviceAliasIdsList = targetDevicesByAliasId[aliasId]; | |
83 | + if (targetDeviceAliasIdsList) { | |
84 | + targetDeviceAliasIdsList.forEach( | |
85 | + function(targetDeviceAliasIds) { | |
86 | + targetDeviceAliasIds[0] = newAliasId; | |
87 | + } | |
88 | + ); | |
89 | + } | |
90 | + return newAliasId; | |
91 | + } else { | |
92 | + return aliasId; | |
93 | + } | |
94 | + } | |
95 | + | |
96 | + function validateAndUpdateDeviceAlias(aliasId, deviceAlias, datasourcesByAliasId, targetDevicesByAliasId) { | |
97 | + aliasId = validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId); | |
98 | + var alias = deviceAlias.alias; | |
99 | + var entityAlias = { | |
100 | + id: aliasId, | |
101 | + alias: alias, | |
102 | + filter: { | |
103 | + type: null, | |
104 | + entityType: types.entityType.device, | |
105 | + resolveMultiple: false | |
106 | + }, | |
107 | + } | |
108 | + if (deviceAlias.deviceFilter) { | |
109 | + entityAlias.filter.type = | |
110 | + deviceAlias.deviceFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value; | |
111 | + if (entityAlias.filter.type == types.aliasFilterType.entityList.value) { | |
112 | + entityAlias.filter.entityList = deviceAlias.deviceFilter.deviceList; | |
113 | + } else { | |
114 | + entityAlias.filter.entityNameFilter = deviceAlias.deviceFilter.deviceNameFilter; | |
115 | + } | |
116 | + } else { | |
117 | + entityAlias.filter.type = types.aliasFilterType.entityList.value; | |
118 | + entityAlias.filter.entityList = [deviceAlias.deviceId]; | |
119 | + } | |
120 | + return entityAlias; | |
121 | + } | |
122 | + | |
123 | + function validateAndUpdateEntityAlias(aliasId, entityAlias, datasourcesByAliasId, targetDevicesByAliasId) { | |
124 | + entityAlias.id = validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId); | |
125 | + if (!entityAlias.filter) { | |
126 | + entityAlias.filter = { | |
127 | + type: entityAlias.entityFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value, | |
128 | + entityType: entityAlias.entityType, | |
129 | + resolveMultiple: false | |
130 | + } | |
131 | + if (entityAlias.filter.type == types.aliasFilterType.entityList.value) { | |
132 | + entityAlias.filter.entityList = entityAlias.entityFilter.entityList; | |
133 | + } else { | |
134 | + entityAlias.filter.entityNameFilter = entityAlias.entityFilter.entityNameFilter; | |
135 | + } | |
136 | + delete entityAlias.entityType; | |
137 | + delete entityAlias.entityFilter; | |
138 | + } | |
139 | + return entityAlias; | |
140 | + } | |
141 | + | |
76 | 142 | function validateAndUpdateWidget(widget) { |
77 | 143 | if (!widget.config) { |
78 | 144 | widget.config = {}; |
... | ... | @@ -166,7 +232,34 @@ function DashboardUtils(types, utils, timeService) { |
166 | 232 | states[firstStateId].root = true; |
167 | 233 | } |
168 | 234 | } |
169 | - dashboard.configuration = validateAndUpdateEntityAliases(dashboard.configuration); | |
235 | + | |
236 | + var datasourcesByAliasId = {}; | |
237 | + var targetDevicesByAliasId = {}; | |
238 | + for (var widgetId in dashboard.configuration.widgets) { | |
239 | + widget = dashboard.configuration.widgets[widgetId]; | |
240 | + widget.config.datasources.forEach(function (datasource) { | |
241 | + if (datasource.entityAliasId) { | |
242 | + var aliasId = datasource.entityAliasId; | |
243 | + var aliasDatasources = datasourcesByAliasId[aliasId]; | |
244 | + if (!aliasDatasources) { | |
245 | + aliasDatasources = []; | |
246 | + datasourcesByAliasId[aliasId] = aliasDatasources; | |
247 | + } | |
248 | + aliasDatasources.push(datasource); | |
249 | + } | |
250 | + }); | |
251 | + if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length) { | |
252 | + var aliasId = widget.config.targetDeviceAliasIds[0]; | |
253 | + var targetDeviceAliasIdsList = targetDevicesByAliasId[aliasId]; | |
254 | + if (!targetDeviceAliasIdsList) { | |
255 | + targetDeviceAliasIdsList = []; | |
256 | + targetDevicesByAliasId[aliasId] = targetDeviceAliasIdsList; | |
257 | + } | |
258 | + targetDeviceAliasIdsList.push(widget.config.targetDeviceAliasIds); | |
259 | + } | |
260 | + } | |
261 | + | |
262 | + dashboard.configuration = validateAndUpdateEntityAliases(dashboard.configuration, datasourcesByAliasId, targetDevicesByAliasId); | |
170 | 263 | |
171 | 264 | if (angular.isUndefined(dashboard.configuration.timewindow)) { |
172 | 265 | dashboard.configuration.timewindow = timeService.defaultTimewindow(); |
... | ... | @@ -243,6 +336,15 @@ function DashboardUtils(types, utils, timeService) { |
243 | 336 | return dashboard; |
244 | 337 | } |
245 | 338 | |
339 | + function createSingleEntityFilter(entityType, entityId) { | |
340 | + return { | |
341 | + type: types.aliasFilterType.entityList.value, | |
342 | + entityList: [entityId], | |
343 | + entityType: entityType, | |
344 | + resolveMultiple: false | |
345 | + }; | |
346 | + } | |
347 | + | |
246 | 348 | function getStateLayoutsData(dashboard, targetState) { |
247 | 349 | var dashboardConfiguration = dashboard.configuration; |
248 | 350 | var states = dashboardConfiguration.states; | ... | ... |
... | ... | @@ -65,6 +65,40 @@ export default angular.module('thingsboard.types', []) |
65 | 65 | clearedUnack: "CLEARED_UNACK", |
66 | 66 | clearedAck: "CLEARED_ACK" |
67 | 67 | }, |
68 | + aliasFilterType: { | |
69 | + entityList: { | |
70 | + value: 'entityList', | |
71 | + name: 'alias.filter-type-entity-list' | |
72 | + }, | |
73 | + entityName: { | |
74 | + value: 'entityName', | |
75 | + name: 'alias.filter-type-entity-name' | |
76 | + }, | |
77 | + stateEntity: { | |
78 | + value: 'stateEntity', | |
79 | + name: 'alias.filter-type-state-entity' | |
80 | + }, | |
81 | + assetType: { | |
82 | + value: 'assetType', | |
83 | + name: 'alias.filter-type-asset-type' | |
84 | + }, | |
85 | + deviceType: { | |
86 | + value: 'deviceType', | |
87 | + name: 'alias.filter-type-device-type' | |
88 | + }, | |
89 | + relationsQuery: { | |
90 | + value: 'relationsQuery', | |
91 | + name: 'alias.filter-type-relations-query' | |
92 | + }, | |
93 | + assetSearchQuery: { | |
94 | + value: 'assetSearchQuery', | |
95 | + name: 'alias.filter-type-asset-search-query' | |
96 | + }, | |
97 | + deviceSearchQuery: { | |
98 | + value: 'deviceSearchQuery', | |
99 | + name: 'alias.filter-type-device-search-query' | |
100 | + } | |
101 | + }, | |
68 | 102 | position: { |
69 | 103 | top: { |
70 | 104 | value: "top", |
... | ... | @@ -109,6 +143,62 @@ export default angular.module('thingsboard.types', []) |
109 | 143 | dashboard: "DASHBOARD", |
110 | 144 | alarm: "ALARM" |
111 | 145 | }, |
146 | + entityTypeTranslations: { | |
147 | + "DEVICE": { | |
148 | + type: 'entity.type-device', | |
149 | + typePlural: 'entity.type-devices', | |
150 | + list: 'entity.list-of-devices', | |
151 | + nameStartsWith: 'entity.device-name-starts-with' | |
152 | + }, | |
153 | + "ASSET": { | |
154 | + type: 'entity.type-asset', | |
155 | + typePlural: 'entity.type-assets', | |
156 | + list: 'entity.list-of-assets', | |
157 | + nameStartsWith: 'entity.asset-name-starts-with' | |
158 | + }, | |
159 | + "RULE": { | |
160 | + type: 'entity.type-rule', | |
161 | + typePlural: 'entity.type-rules', | |
162 | + list: 'entity.list-of-rules', | |
163 | + nameStartsWith: 'entity.rule-name-starts-with' | |
164 | + }, | |
165 | + "PLUGIN": { | |
166 | + type: 'entity.type-plugin', | |
167 | + typePlural: 'entity.type-plugins', | |
168 | + list: 'entity.list-of-plugins', | |
169 | + nameStartsWith: 'entity.plugin-name-starts-with' | |
170 | + }, | |
171 | + "TENANT": { | |
172 | + type: 'entity.type-tenant', | |
173 | + typePlural: 'entity.type-tenants', | |
174 | + list: 'entity.list-of-tenants', | |
175 | + nameStartsWith: 'entity.tenant-name-starts-with' | |
176 | + }, | |
177 | + "CUSTOMER": { | |
178 | + type: 'entity.type-customer', | |
179 | + typePlural: 'entity.type-customers', | |
180 | + list: 'entity.list-of-customers', | |
181 | + nameStartsWith: 'entity.customer-name-starts-with' | |
182 | + }, | |
183 | + "USER": { | |
184 | + type: 'entity.type-user', | |
185 | + typePlural: 'entity.type-users', | |
186 | + list: 'entity.list-of-users', | |
187 | + nameStartsWith: 'entity.user-name-starts-with' | |
188 | + }, | |
189 | + "DASHBOARD": { | |
190 | + type: 'entity.type-dashboard', | |
191 | + typePlural: 'entity.type-dashboards', | |
192 | + list: 'entity.list-of-dashboards', | |
193 | + nameStartsWith: 'entity.dashboard-name-starts-with' | |
194 | + }, | |
195 | + "ALARM": { | |
196 | + type: 'entity.type-alarm', | |
197 | + typePlural: 'entity.type-alarms', | |
198 | + list: 'entity.list-of-alarms', | |
199 | + nameStartsWith: 'entity.alarm-name-starts-with' | |
200 | + } | |
201 | + }, | |
112 | 202 | entitySearchDirection: { |
113 | 203 | from: "FROM", |
114 | 204 | to: "TO" | ... | ... |
... | ... | @@ -106,10 +106,10 @@ function Utils($mdColorPalette, $rootScope, $window, types) { |
106 | 106 | isDescriptorSchemaNotEmpty: isDescriptorSchemaNotEmpty, |
107 | 107 | filterSearchTextEntities: filterSearchTextEntities, |
108 | 108 | guid: guid, |
109 | + cleanCopy: cleanCopy, | |
109 | 110 | isLocalUrl: isLocalUrl, |
110 | 111 | validateDatasources: validateDatasources, |
111 | - createKey: createKey, | |
112 | - entityTypeName: entityTypeName | |
112 | + createKey: createKey | |
113 | 113 | } |
114 | 114 | |
115 | 115 | return service; |
... | ... | @@ -291,6 +291,16 @@ function Utils($mdColorPalette, $rootScope, $window, types) { |
291 | 291 | s4() + '-' + s4() + s4() + s4(); |
292 | 292 | } |
293 | 293 | |
294 | + function cleanCopy(object) { | |
295 | + var copy = angular.copy(object); | |
296 | + for (var prop in copy) { | |
297 | + if (prop && prop.startsWith('$$')) { | |
298 | + delete copy[prop]; | |
299 | + } | |
300 | + } | |
301 | + return copy; | |
302 | + } | |
303 | + | |
294 | 304 | function genNextColor(datasources) { |
295 | 305 | var index = 0; |
296 | 306 | if (datasources) { |
... | ... | @@ -347,27 +357,4 @@ function Utils($mdColorPalette, $rootScope, $window, types) { |
347 | 357 | return dataKey; |
348 | 358 | } |
349 | 359 | |
350 | - function entityTypeName (type) { | |
351 | - switch (type) { | |
352 | - case types.entityType.device: | |
353 | - return 'entity.type-device'; | |
354 | - case types.entityType.asset: | |
355 | - return 'entity.type-asset'; | |
356 | - case types.entityType.rule: | |
357 | - return 'entity.type-rule'; | |
358 | - case types.entityType.plugin: | |
359 | - return 'entity.type-plugin'; | |
360 | - case types.entityType.tenant: | |
361 | - return 'entity.type-tenant'; | |
362 | - case types.entityType.customer: | |
363 | - return 'entity.type-customer'; | |
364 | - case types.entityType.user: | |
365 | - return 'entity.type-user'; | |
366 | - case types.entityType.dashboard: | |
367 | - return 'entity.type-dashboard'; | |
368 | - case types.entityType.alarm: | |
369 | - return 'entity.type-alarm'; | |
370 | - } | |
371 | - } | |
372 | - | |
373 | 360 | } | ... | ... |
... | ... | @@ -52,7 +52,7 @@ function Dashboard() { |
52 | 52 | bindToController: { |
53 | 53 | widgets: '=', |
54 | 54 | widgetLayouts: '=?', |
55 | - aliasesInfo: '=', | |
55 | + aliasController: '=', | |
56 | 56 | stateController: '=', |
57 | 57 | dashboardTimewindow: '=?', |
58 | 58 | columns: '=', |
... | ... | @@ -85,7 +85,7 @@ function Dashboard() { |
85 | 85 | } |
86 | 86 | |
87 | 87 | /*@ngInject*/ |
88 | -function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, timeService, types, utils) { | |
88 | +function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $mdUtil, timeService, types, utils) { | |
89 | 89 | |
90 | 90 | var highlightedMode = false; |
91 | 91 | var highlightedWidget = null; |
... | ... | @@ -329,10 +329,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t |
329 | 329 | $scope.$broadcast('toggleDashboardEditMode', vm.isEdit); |
330 | 330 | }); |
331 | 331 | |
332 | - $scope.$watch('vm.aliasesInfo.entityAliases', function () { | |
333 | - $scope.$broadcast('entityAliasListChanged', vm.aliasesInfo); | |
334 | - }, true); | |
335 | - | |
336 | 332 | $scope.$on('gridster-resized', function (event, sizes, theGridster) { |
337 | 333 | if (checkIsLocalGridsterElement(theGridster)) { |
338 | 334 | vm.gridster = theGridster; |
... | ... | @@ -796,7 +792,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t |
796 | 792 | } |
797 | 793 | |
798 | 794 | function dashboardLoaded() { |
799 | - $timeout(function () { | |
795 | + $mdUtil.nextTick(function () { | |
800 | 796 | if (vm.dashboardTimewindowWatch) { |
801 | 797 | vm.dashboardTimewindowWatch(); |
802 | 798 | vm.dashboardTimewindowWatch = null; |
... | ... | @@ -806,14 +802,27 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t |
806 | 802 | }, true); |
807 | 803 | adoptMaxRows(); |
808 | 804 | vm.dashboardLoading = false; |
809 | - $timeout(function () { | |
810 | - var gridsterScope = gridsterElement.scope(); | |
811 | - vm.gridster = gridsterScope.gridster; | |
812 | - if (vm.onInit) { | |
813 | - vm.onInit({dashboard: vm}); | |
805 | + if ($scope.gridsterScopeWatcher) { | |
806 | + $scope.gridsterScopeWatcher(); | |
807 | + } | |
808 | + $scope.gridsterScopeWatcher = $scope.$watch( | |
809 | + function() { | |
810 | + var hasScope = gridsterElement.scope() ? true : false; | |
811 | + return hasScope; | |
812 | + }, | |
813 | + function(hasScope) { | |
814 | + if (hasScope) { | |
815 | + $scope.gridsterScopeWatcher(); | |
816 | + $scope.gridsterScopeWatcher = null; | |
817 | + var gridsterScope = gridsterElement.scope(); | |
818 | + vm.gridster = gridsterScope.gridster; | |
819 | + if (vm.onInit) { | |
820 | + vm.onInit({dashboard: vm}); | |
821 | + } | |
822 | + } | |
814 | 823 | } |
815 | - }, 0, false); | |
816 | - }, 0, false); | |
824 | + ); | |
825 | + }); | |
817 | 826 | } |
818 | 827 | |
819 | 828 | function loading() { | ... | ... |
... | ... | @@ -20,14 +20,14 @@ export default angular.module('thingsboard.dialogs.datakeyConfigDialog', [things |
20 | 20 | .name; |
21 | 21 | |
22 | 22 | /*@ngInject*/ |
23 | -function DatakeyConfigDialogController($scope, $mdDialog, entityService, dataKey, dataKeySettingsSchema, entityAlias, entityAliases) { | |
23 | +function DatakeyConfigDialogController($scope, $mdDialog, $q, entityService, dataKey, dataKeySettingsSchema, entityAlias, aliasController) { | |
24 | 24 | |
25 | 25 | var vm = this; |
26 | 26 | |
27 | 27 | vm.dataKey = dataKey; |
28 | 28 | vm.dataKeySettingsSchema = dataKeySettingsSchema; |
29 | 29 | vm.entityAlias = entityAlias; |
30 | - vm.entityAliases = entityAliases; | |
30 | + vm.aliasController = aliasController; | |
31 | 31 | |
32 | 32 | vm.hide = function () { |
33 | 33 | $mdDialog.hide(); |
... | ... | @@ -38,12 +38,28 @@ function DatakeyConfigDialogController($scope, $mdDialog, entityService, dataKey |
38 | 38 | }; |
39 | 39 | |
40 | 40 | vm.fetchEntityKeys = function (entityAliasId, query, type) { |
41 | - var alias = vm.entityAliases[entityAliasId]; | |
42 | - if (alias) { | |
43 | - return entityService.getEntityKeys(alias.entityType, alias.entityId, query, type); | |
44 | - } else { | |
45 | - return []; | |
46 | - } | |
41 | + var deferred = $q.defer(); | |
42 | + vm.aliasController.getAliasInfo(entityAliasId).then( | |
43 | + function success(aliasInfo) { | |
44 | + var entity = aliasInfo.currentEntity; | |
45 | + if (entity) { | |
46 | + entityService.getEntityKeys(entity.entityType, entity.id, query, type).then( | |
47 | + function success(keys) { | |
48 | + deferred.resolve(keys); | |
49 | + }, | |
50 | + function fail() { | |
51 | + deferred.resolve([]); | |
52 | + } | |
53 | + ); | |
54 | + } else { | |
55 | + deferred.resolve([]); | |
56 | + } | |
57 | + }, | |
58 | + function fail() { | |
59 | + deferred.resolve([]); | |
60 | + } | |
61 | + ); | |
62 | + return deferred.promise; | |
47 | 63 | }; |
48 | 64 | |
49 | 65 | vm.save = function () { | ... | ... |
... | ... | @@ -103,10 +103,9 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc |
103 | 103 | ngModelCtrl.$render = function () { |
104 | 104 | if (ngModelCtrl.$viewValue) { |
105 | 105 | var entityAliasId = ngModelCtrl.$viewValue.entityAliasId; |
106 | - if (scope.entityAliases[entityAliasId]) { | |
107 | - scope.entityAlias = {id: entityAliasId, alias: scope.entityAliases[entityAliasId].alias, | |
108 | - entityType: scope.entityAliases[entityAliasId].entityType, | |
109 | - entityId: scope.entityAliases[entityAliasId].entityId}; | |
106 | + var entityAliases = scope.aliasController.getEntityAliases(); | |
107 | + if (entityAliases[entityAliasId]) { | |
108 | + scope.entityAlias = entityAliases[entityAliasId]; | |
110 | 109 | } else { |
111 | 110 | scope.entityAlias = null; |
112 | 111 | } |
... | ... | @@ -182,7 +181,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc |
182 | 181 | dataKey: angular.copy(dataKey), |
183 | 182 | dataKeySettingsSchema: scope.datakeySettingsSchema, |
184 | 183 | entityAlias: scope.entityAlias, |
185 | - entityAliases: scope.entityAliases | |
184 | + aliasController: scope.aliasController | |
186 | 185 | }, |
187 | 186 | parent: angular.element($document[0].body), |
188 | 187 | fullscreen: true, |
... | ... | @@ -236,7 +235,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc |
236 | 235 | require: "^ngModel", |
237 | 236 | scope: { |
238 | 237 | widgetType: '=', |
239 | - entityAliases: '=', | |
238 | + aliasController: '=', | |
240 | 239 | datakeySettingsSchema: '=', |
241 | 240 | generateDataKey: '&', |
242 | 241 | fetchEntityKeys: '&', | ... | ... |
... | ... | @@ -18,7 +18,7 @@ |
18 | 18 | <section flex layout='column' layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center"> |
19 | 19 | <tb-entity-alias-select |
20 | 20 | tb-required="true" |
21 | - entity-aliases="entityAliases" | |
21 | + alias-controller="aliasController" | |
22 | 22 | ng-model="entityAlias" |
23 | 23 | on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})"> |
24 | 24 | </tb-entity-alias-select> | ... | ... |
... | ... | @@ -71,6 +71,13 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, |
71 | 71 | } |
72 | 72 | }, true); |
73 | 73 | |
74 | + scope.$watch('datasourceName', function () { | |
75 | + if (ngModelCtrl.$viewValue) { | |
76 | + ngModelCtrl.$viewValue.name = scope.datasourceName; | |
77 | + scope.updateValidity(); | |
78 | + } | |
79 | + }); | |
80 | + | |
74 | 81 | ngModelCtrl.$render = function () { |
75 | 82 | if (ngModelCtrl.$viewValue) { |
76 | 83 | var funcDataKeys = []; |
... | ... | @@ -78,6 +85,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, |
78 | 85 | funcDataKeys = funcDataKeys.concat(ngModelCtrl.$viewValue.dataKeys); |
79 | 86 | } |
80 | 87 | scope.funcDataKeys = funcDataKeys; |
88 | + scope.datasourceName = ngModelCtrl.$viewValue.name; | |
81 | 89 | } |
82 | 90 | }; |
83 | 91 | ... | ... |
... | ... | @@ -15,23 +15,29 @@ |
15 | 15 | */ |
16 | 16 | @import '../../scss/constants'; |
17 | 17 | |
18 | -.tb-func-datakey-autocomplete { | |
19 | - .tb-not-found { | |
20 | - display: block; | |
21 | - line-height: 1.5; | |
22 | - height: 48px; | |
23 | - .tb-no-entries { | |
24 | - line-height: 48px; | |
25 | - } | |
18 | +.tb-datasource-func { | |
19 | + @media (min-width: $layout-breakpoint-gt-sm) { | |
20 | + padding-left: 8px; | |
26 | 21 | } |
27 | - li { | |
28 | - height: auto !important; | |
29 | - white-space: normal !important; | |
22 | + | |
23 | + md-input-container.tb-datasource-name { | |
24 | + .md-errors-spacer { | |
25 | + display: none; | |
26 | + } | |
30 | 27 | } |
31 | -} | |
32 | 28 | |
33 | -tb-datasource-func { | |
34 | - @media (min-width: $layout-breakpoint-gt-sm) { | |
35 | - padding-left: 8px; | |
29 | + .tb-func-datakey-autocomplete { | |
30 | + .tb-not-found { | |
31 | + display: block; | |
32 | + line-height: 1.5; | |
33 | + height: 48px; | |
34 | + .tb-no-entries { | |
35 | + line-height: 48px; | |
36 | + } | |
37 | + } | |
38 | + li { | |
39 | + height: auto !important; | |
40 | + white-space: normal !important; | |
41 | + } | |
36 | 42 | } |
37 | -} | |
\ No newline at end of file | ||
43 | +} | ... | ... |
... | ... | @@ -15,59 +15,68 @@ |
15 | 15 | limitations under the License. |
16 | 16 | |
17 | 17 | --> |
18 | -<section flex layout='column' style="padding-left: 4px;"> | |
19 | - <md-chips flex | |
20 | - id="function_datakey_chips" | |
21 | - ng-required="true" | |
22 | - ng-model="funcDataKeys" md-autocomplete-snap | |
23 | - md-transform-chip="transformDataKeyChip($chip)" | |
24 | - md-require-match="false"> | |
25 | - <md-autocomplete | |
26 | - md-no-cache="false" | |
27 | - id="dataKey" | |
28 | - md-selected-item="selectedDataKey" | |
29 | - md-search-text="dataKeySearchText" | |
30 | - md-items="item in dataKeysSearch(dataKeySearchText)" | |
31 | - md-item-text="item.name" | |
32 | - md-min-length="0" | |
33 | - placeholder="{{ 'datakey.function-types' | translate }}" | |
34 | - md-menu-class="tb-func-datakey-autocomplete"> | |
35 | - <span md-highlight-text="dataKeySearchText" md-highlight-flags="^i">{{item}}</span> | |
36 | - <md-not-found> | |
37 | - <div class="tb-not-found"> | |
38 | - <div class="tb-no-entries" ng-if="!textIsNotEmpty(dataKeySearchText)"> | |
39 | - <span translate>device.no-keys-found</span> | |
18 | +<section class="tb-datasource-func" flex layout='column' | |
19 | + layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center"> | |
20 | + <md-input-container class="tb-datasource-name" md-no-float style="min-width: 200px;"> | |
21 | + <input name="datasourceName" | |
22 | + placeholder="{{ 'datasource.name' | translate }}" | |
23 | + ng-model="datasourceName" | |
24 | + aria-label="{{ 'datasource.name' | translate }}"> | |
25 | + </md-input-container> | |
26 | + <section flex layout='column' style="padding-left: 4px;"> | |
27 | + <md-chips flex | |
28 | + id="function_datakey_chips" | |
29 | + ng-required="true" | |
30 | + ng-model="funcDataKeys" md-autocomplete-snap | |
31 | + md-transform-chip="transformDataKeyChip($chip)" | |
32 | + md-require-match="false"> | |
33 | + <md-autocomplete | |
34 | + md-no-cache="false" | |
35 | + id="dataKey" | |
36 | + md-selected-item="selectedDataKey" | |
37 | + md-search-text="dataKeySearchText" | |
38 | + md-items="item in dataKeysSearch(dataKeySearchText)" | |
39 | + md-item-text="item.name" | |
40 | + md-min-length="0" | |
41 | + placeholder="{{ 'datakey.function-types' | translate }}" | |
42 | + md-menu-class="tb-func-datakey-autocomplete"> | |
43 | + <span md-highlight-text="dataKeySearchText" md-highlight-flags="^i">{{item}}</span> | |
44 | + <md-not-found> | |
45 | + <div class="tb-not-found"> | |
46 | + <div class="tb-no-entries" ng-if="!textIsNotEmpty(dataKeySearchText)"> | |
47 | + <span translate>device.no-keys-found</span> | |
48 | + </div> | |
49 | + <div ng-if="textIsNotEmpty(dataKeySearchText)"> | |
50 | + <span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:'...'}}" }'>device.no-key-matching</span> | |
51 | + <span> | |
52 | + <a translate ng-click="createKey($event, '#function_datakey_chips')">device.create-new-key</a> | |
53 | + </span> | |
54 | + </div> | |
40 | 55 | </div> |
41 | - <div ng-if="textIsNotEmpty(dataKeySearchText)"> | |
42 | - <span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:'...'}}" }'>device.no-key-matching</span> | |
43 | - <span> | |
44 | - <a translate ng-click="createKey($event, '#function_datakey_chips')">device.create-new-key</a> | |
45 | - </span> | |
46 | - </div> | |
47 | - </div> | |
48 | - </md-not-found> | |
49 | - </md-autocomplete> | |
50 | - <md-chip-template> | |
51 | - <div layout="row" layout-align="start center" class="tb-attribute-chip"> | |
52 | - <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;"> | |
53 | - <div class="tb-color-result" ng-style="{background: $chip.color}"></div> | |
54 | - </div> | |
55 | - <div layout="row" flex> | |
56 | - <div class="tb-chip-label"> | |
57 | - {{$chip.label}} | |
56 | + </md-not-found> | |
57 | + </md-autocomplete> | |
58 | + <md-chip-template> | |
59 | + <div layout="row" layout-align="start center" class="tb-attribute-chip"> | |
60 | + <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;"> | |
61 | + <div class="tb-color-result" ng-style="{background: $chip.color}"></div> | |
58 | 62 | </div> |
59 | - <div class="tb-chip-separator">: </div> | |
60 | - <div class="tb-chip-label"> | |
61 | - <strong>{{$chip.name}}</strong> | |
63 | + <div layout="row" flex> | |
64 | + <div class="tb-chip-label"> | |
65 | + {{$chip.label}} | |
66 | + </div> | |
67 | + <div class="tb-chip-separator">: </div> | |
68 | + <div class="tb-chip-label"> | |
69 | + <strong>{{$chip.name}}</strong> | |
70 | + </div> | |
62 | 71 | </div> |
72 | + <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32"> | |
73 | + <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon> | |
74 | + </md-button> | |
63 | 75 | </div> |
64 | - <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32"> | |
65 | - <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon> | |
66 | - </md-button> | |
67 | - </div> | |
68 | - </md-chip-template> | |
69 | - </md-chips> | |
70 | - <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert"> | |
71 | - <div translate ng-message="funcTypes" class="tb-error-message">datakey.function-types-required</div> | |
72 | - </div> | |
76 | + </md-chip-template> | |
77 | + </md-chips> | |
78 | + <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert"> | |
79 | + <div translate ng-message="funcTypes" class="tb-error-message">datakey.function-types-required</div> | |
80 | + </div> | |
81 | + </section> | |
73 | 82 | </section> | ... | ... |
... | ... | @@ -37,7 +37,7 @@ |
37 | 37 | ng-switch-when="entity" |
38 | 38 | ng-required="model.type === types.datasourceType.entity" |
39 | 39 | widget-type="widgetType" |
40 | - entity-aliases="entityAliases" | |
40 | + alias-controller="aliasController" | |
41 | 41 | generate-data-key="generateDataKey({chip: chip, type: type})" |
42 | 42 | fetch-entity-keys="fetchEntityKeys({entityAliasId: entityAliasId, query: query, type: type})" |
43 | 43 | on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})"> | ... | ... |
... | ... | @@ -31,7 +31,7 @@ export default angular.module('thingsboard.directives.entityAliasSelect', []) |
31 | 31 | .name; |
32 | 32 | |
33 | 33 | /*@ngInject*/ |
34 | -function EntityAliasSelect($compile, $templateCache, $mdConstant) { | |
34 | +function EntityAliasSelect($compile, $templateCache, $mdConstant, entityService) { | |
35 | 35 | |
36 | 36 | var linker = function (scope, element, attrs, ngModelCtrl) { |
37 | 37 | var template = $templateCache.get(entityAliasSelectTemplate); |
... | ... | @@ -49,19 +49,18 @@ function EntityAliasSelect($compile, $templateCache, $mdConstant) { |
49 | 49 | ngModelCtrl.$setValidity('entityAlias', valid); |
50 | 50 | }; |
51 | 51 | |
52 | - scope.$watch('entityAliases', function () { | |
52 | + scope.$watch('aliasController', function () { | |
53 | 53 | scope.entityAliasList = []; |
54 | - for (var aliasId in scope.entityAliases) { | |
54 | + var entityAliases = scope.aliasController.getEntityAliases(); | |
55 | + for (var aliasId in entityAliases) { | |
55 | 56 | if (scope.allowedEntityTypes) { |
56 | - if (scope.allowedEntityTypes.indexOf(scope.entityAliases[aliasId].entityType) === -1) { | |
57 | + if (!entityService.filterAliasByEntityTypes(entityAliases[aliasId], scope.allowedEntityTypes)) { | |
57 | 58 | continue; |
58 | 59 | } |
59 | 60 | } |
60 | - var entityAlias = {id: aliasId, alias: scope.entityAliases[aliasId].alias, | |
61 | - entityType: scope.entityAliases[aliasId].entityType, entityId: scope.entityAliases[aliasId].entityId}; | |
62 | - scope.entityAliasList.push(entityAlias); | |
61 | + scope.entityAliasList.push(entityAliases[aliasId]); | |
63 | 62 | } |
64 | - }, true); | |
63 | + }); | |
65 | 64 | |
66 | 65 | scope.$watch('entityAlias', function () { |
67 | 66 | scope.updateView(); |
... | ... | @@ -141,7 +140,7 @@ function EntityAliasSelect($compile, $templateCache, $mdConstant) { |
141 | 140 | link: linker, |
142 | 141 | scope: { |
143 | 142 | tbRequired: '=?', |
144 | - entityAliases: '=', | |
143 | + aliasController: '=', | |
145 | 144 | allowedEntityTypes: '=?', |
146 | 145 | onCreateEntityAlias: '&' |
147 | 146 | } | ... | ... |
... | ... | @@ -128,13 +128,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti |
128 | 128 | } else if (scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) { |
129 | 129 | if (config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0) { |
130 | 130 | var aliasId = config.targetDeviceAliasIds[0]; |
131 | - if (scope.entityAliases[aliasId]) { | |
132 | - scope.targetDeviceAlias.value = { | |
133 | - id: aliasId, | |
134 | - alias: scope.entityAliases[aliasId].alias, | |
135 | - entityType: scope.entityAliases[aliasId].entityType, | |
136 | - entityId: scope.entityAliases[aliasId].entityId | |
137 | - }; | |
131 | + var entityAliases = scope.aliasController.getEntityAliases(); | |
132 | + if (entityAliases[aliasId]) { | |
133 | + scope.targetDeviceAlias.value = entityAliases[aliasId]; | |
138 | 134 | } else { |
139 | 135 | scope.targetDeviceAlias.value = null; |
140 | 136 | } |
... | ... | @@ -395,7 +391,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti |
395 | 391 | widgetType: '=', |
396 | 392 | widgetSettingsSchema: '=', |
397 | 393 | datakeySettingsSchema: '=', |
398 | - entityAliases: '=', | |
394 | + aliasController: '=', | |
399 | 395 | functionsOnly: '=', |
400 | 396 | fetchEntityKeys: '&', |
401 | 397 | onCreateEntityAlias: '&', | ... | ... |
... | ... | @@ -60,7 +60,7 @@ |
60 | 60 | style="padding: 0 0 0 10px; margin: 5px;"> |
61 | 61 | <tb-datasource flex ng-model="datasource.value" |
62 | 62 | widget-type="widgetType" |
63 | - entity-aliases="entityAliases" | |
63 | + alias-controller="aliasController" | |
64 | 64 | functions-only="functionsOnly" |
65 | 65 | datakey-settings-schema="datakeySettingsSchema" |
66 | 66 | generate-data-key="generateDataKey(chip,type)" |
... | ... | @@ -104,7 +104,7 @@ |
104 | 104 | <v-pane-content style="padding: 0 5px;"> |
105 | 105 | <tb-entity-alias-select flex |
106 | 106 | tb-required="widgetType === types.widgetType.rpc.value && !widgetEditMode" |
107 | - entity-aliases="entityAliases" | |
107 | + alias-controller="aliasController" | |
108 | 108 | allowed-entity-types="[types.entityType.device]" |
109 | 109 | ng-model="targetDeviceAlias.value" |
110 | 110 | on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias, allowedEntityTypes: allowedEntityTypes})"> | ... | ... |
... | ... | @@ -20,9 +20,9 @@ import Subscription from '../api/subscription'; |
20 | 20 | /* eslint-disable angular/angularelement */ |
21 | 21 | |
22 | 22 | /*@ngInject*/ |
23 | -export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, tbRaf, types, utils, timeService, | |
23 | +export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, $compile, tbRaf, types, utils, timeService, | |
24 | 24 | datasourceService, entityService, deviceService, visibleRect, isEdit, stDiff, dashboardTimewindow, |
25 | - dashboardTimewindowApi, widget, aliasesInfo, stateController, widgetType) { | |
25 | + dashboardTimewindowApi, widget, aliasController, stateController, widgetInfo, widgetType) { | |
26 | 26 | |
27 | 27 | var vm = this; |
28 | 28 | |
... | ... | @@ -37,23 +37,16 @@ export default function WidgetController($scope, $timeout, $window, $element, $q |
37 | 37 | $scope.executingRpcRequest = false; |
38 | 38 | |
39 | 39 | var gridsterItemInited = false; |
40 | + var subscriptionInited = false; | |
41 | + var widgetSizeDetected = false; | |
40 | 42 | |
41 | 43 | var cafs = {}; |
42 | 44 | |
43 | - /* | |
44 | - * data = array of datasourceData | |
45 | - * datasourceData = { | |
46 | - * tbDatasource, | |
47 | - * dataKey, { name, config } | |
48 | - * data = array of [time, value] | |
49 | - * } | |
50 | - */ | |
51 | - | |
52 | 45 | var widgetContext = { |
53 | 46 | inited: false, |
54 | 47 | $scope: $scope, |
55 | - $container: $('#container', $element), | |
56 | - $containerParent: $($element), | |
48 | + $container: null, | |
49 | + $containerParent: null, | |
57 | 50 | width: 0, |
58 | 51 | height: 0, |
59 | 52 | isEdit: isEdit, |
... | ... | @@ -80,30 +73,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q |
80 | 73 | createSubscription: function(options, subscribe) { |
81 | 74 | return createSubscription(options, subscribe); |
82 | 75 | }, |
83 | - | |
84 | - | |
85 | - // type: "timeseries" or "latest" or "rpc" | |
86 | - /* subscriptionInfo = [ | |
87 | - { | |
88 | - entityType: "" | |
89 | - entityId: "" | |
90 | - entityName: "" | |
91 | - timeseries: [{ name: "", label: "" }, ..] | |
92 | - attributes: [{ name: "", label: "" }, ..] | |
93 | - } | |
94 | - .. | |
95 | - ]*/ | |
96 | - | |
97 | - // options = { | |
98 | - // timeWindowConfig, | |
99 | - // useDashboardTimewindow, | |
100 | - // legendConfig, | |
101 | - // decimals, | |
102 | - // units, | |
103 | - // callbacks [ onDataUpdated(subscription, apply) ] | |
104 | - // } | |
105 | - // | |
106 | - | |
107 | 76 | createSubscriptionFromInfo: function (type, subscriptionsInfo, options, useDefaultComponents, subscribe) { |
108 | 77 | return createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe); |
109 | 78 | }, |
... | ... | @@ -149,7 +118,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q |
149 | 118 | dashboardTimewindowApi: dashboardTimewindowApi, |
150 | 119 | types: types, |
151 | 120 | stDiff: stDiff, |
152 | - aliasesInfo: aliasesInfo | |
121 | + aliasController: aliasController | |
153 | 122 | }; |
154 | 123 | |
155 | 124 | var widgetTypeInstance; |
... | ... | @@ -203,23 +172,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q |
203 | 172 | |
204 | 173 | vm.gridsterItemInitialized = gridsterItemInitialized; |
205 | 174 | |
206 | - initialize(); | |
207 | - | |
208 | - | |
209 | - /* | |
210 | - options = { | |
211 | - type, | |
212 | - targetDeviceAliasIds, // RPC | |
213 | - targetDeviceIds, // RPC | |
214 | - datasources, | |
215 | - timeWindowConfig, | |
216 | - useDashboardTimewindow, | |
217 | - legendConfig, | |
218 | - decimals, | |
219 | - units, | |
220 | - callbacks | |
221 | - } | |
222 | - */ | |
175 | + initialize().then( | |
176 | + function(){ | |
177 | + onInit(); | |
178 | + } | |
179 | + ); | |
223 | 180 | |
224 | 181 | function createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe) { |
225 | 182 | var deferred = $q.defer(); |
... | ... | @@ -233,28 +190,42 @@ export default function WidgetController($scope, $timeout, $window, $element, $q |
233 | 190 | } |
234 | 191 | } |
235 | 192 | |
236 | - entityService.createDatasoucesFromSubscriptionsInfo(subscriptionsInfo).then( | |
193 | + entityService.createDatasourcesFromSubscriptionsInfo(subscriptionsInfo).then( | |
237 | 194 | function (datasources) { |
238 | 195 | options.datasources = datasources; |
239 | - var subscription = createSubscription(options, subscribe); | |
240 | - if (useDefaultComponents) { | |
241 | - defaultSubscriptionOptions(subscription, options); | |
242 | - } | |
243 | - deferred.resolve(subscription); | |
196 | + createSubscription(options, subscribe).then( | |
197 | + function success(subscription) { | |
198 | + if (useDefaultComponents) { | |
199 | + defaultSubscriptionOptions(subscription, options); | |
200 | + } | |
201 | + deferred.resolve(subscription); | |
202 | + }, | |
203 | + function fail() { | |
204 | + deferred.reject(); | |
205 | + } | |
206 | + ); | |
244 | 207 | } |
245 | 208 | ); |
246 | 209 | return deferred.promise; |
247 | 210 | } |
248 | 211 | |
249 | 212 | function createSubscription(options, subscribe) { |
213 | + var deferred = $q.defer(); | |
250 | 214 | options.dashboardTimewindow = dashboardTimewindow; |
251 | - var subscription = | |
252 | - new Subscription(subscriptionContext, options); | |
253 | - widgetContext.subscriptions[subscription.id] = subscription; | |
254 | - if (subscribe) { | |
255 | - subscription.subscribe(); | |
256 | - } | |
257 | - return subscription; | |
215 | + new Subscription(subscriptionContext, options).then( | |
216 | + function success(subscription) { | |
217 | + widgetContext.subscriptions[subscription.id] = subscription; | |
218 | + if (subscribe) { | |
219 | + subscription.subscribe(); | |
220 | + } | |
221 | + deferred.resolve(subscription); | |
222 | + }, | |
223 | + function fail() { | |
224 | + deferred.reject(); | |
225 | + } | |
226 | + ); | |
227 | + | |
228 | + return deferred.promise; | |
258 | 229 | } |
259 | 230 | |
260 | 231 | function defaultComponentsOptions(options) { |
... | ... | @@ -310,8 +281,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q |
310 | 281 | } |
311 | 282 | |
312 | 283 | function createDefaultSubscription() { |
313 | - var subscription; | |
314 | 284 | var options; |
285 | + var deferred = $q.defer(); | |
315 | 286 | if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) { |
316 | 287 | options = { |
317 | 288 | type: widget.type, |
... | ... | @@ -319,16 +290,23 @@ export default function WidgetController($scope, $timeout, $window, $element, $q |
319 | 290 | }; |
320 | 291 | defaultComponentsOptions(options); |
321 | 292 | |
322 | - subscription = createSubscription(options); | |
323 | - | |
324 | - defaultSubscriptionOptions(subscription, options); | |
293 | + createSubscription(options).then( | |
294 | + function success(subscription) { | |
295 | + defaultSubscriptionOptions(subscription, options); | |
325 | 296 | |
326 | - // backward compatibility | |
297 | + // backward compatibility | |
327 | 298 | |
328 | - widgetContext.datasources = subscription.datasources; | |
329 | - widgetContext.data = subscription.data; | |
330 | - widgetContext.hiddenData = subscription.hiddenData; | |
331 | - widgetContext.timeWindow = subscription.timeWindow; | |
299 | + widgetContext.datasources = subscription.datasources; | |
300 | + widgetContext.data = subscription.data; | |
301 | + widgetContext.hiddenData = subscription.hiddenData; | |
302 | + widgetContext.timeWindow = subscription.timeWindow; | |
303 | + widgetContext.defaultSubscription = subscription; | |
304 | + deferred.resolve(); | |
305 | + }, | |
306 | + function fail() { | |
307 | + deferred.reject(); | |
308 | + } | |
309 | + ); | |
332 | 310 | |
333 | 311 | } else if (widget.type === types.widgetType.rpc.value) { |
334 | 312 | $scope.loadingData = false; |
... | ... | @@ -356,30 +334,126 @@ export default function WidgetController($scope, $timeout, $window, $element, $q |
356 | 334 | $scope.rpcRejection = null; |
357 | 335 | } |
358 | 336 | } |
359 | - subscription = createSubscription(options); | |
337 | + createSubscription(options).then( | |
338 | + function success(subscription) { | |
339 | + widgetContext.defaultSubscription = subscription; | |
340 | + deferred.resolve(); | |
341 | + }, | |
342 | + function fail() { | |
343 | + deferred.reject(); | |
344 | + } | |
345 | + ); | |
360 | 346 | } else if (widget.type === types.widgetType.static.value) { |
361 | 347 | $scope.loadingData = false; |
348 | + deferred.resolve(); | |
349 | + } else { | |
350 | + deferred.resolve(); | |
362 | 351 | } |
363 | - if (subscription) { | |
364 | - widgetContext.defaultSubscription = subscription; | |
365 | - } | |
352 | + return deferred.promise; | |
366 | 353 | } |
367 | 354 | |
355 | + function configureWidgetElement() { | |
368 | 356 | |
369 | - function initialize() { | |
357 | + $scope.displayLegend = angular.isDefined(widget.config.showLegend) ? | |
358 | + widget.config.showLegend : widget.type === types.widgetType.timeseries.value; | |
370 | 359 | |
371 | - if (!vm.useCustomDatasources) { | |
372 | - createDefaultSubscription(); | |
360 | + if ($scope.displayLegend) { | |
361 | + $scope.legendConfig = widget.config.legendConfig || | |
362 | + { | |
363 | + position: types.position.bottom.value, | |
364 | + showMin: false, | |
365 | + showMax: false, | |
366 | + showAvg: widget.type === types.widgetType.timeseries.value, | |
367 | + showTotal: false | |
368 | + }; | |
369 | + $scope.legendData = { | |
370 | + keys: [], | |
371 | + data: [] | |
372 | + }; | |
373 | + } | |
374 | + | |
375 | + var html = '<div class="tb-absolute-fill tb-widget-error" ng-if="widgetErrorData">' + | |
376 | + '<span>Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}</span>' + | |
377 | + '</div>' + | |
378 | + '<div class="tb-absolute-fill tb-widget-loading" ng-show="loadingData" layout="column" layout-align="center center">' + | |
379 | + '<md-progress-circular md-mode="indeterminate" ng-disabled="!loadingData" class="md-accent" md-diameter="40"></md-progress-circular>' + | |
380 | + '</div>'; | |
381 | + | |
382 | + var containerHtml = '<div id="container">' + widgetInfo.templateHtml + '</div>'; | |
383 | + if ($scope.displayLegend) { | |
384 | + var layoutType; | |
385 | + if ($scope.legendConfig.position === types.position.top.value || | |
386 | + $scope.legendConfig.position === types.position.bottom.value) { | |
387 | + layoutType = 'column'; | |
388 | + } else { | |
389 | + layoutType = 'row'; | |
390 | + } | |
391 | + | |
392 | + var legendStyle; | |
393 | + switch($scope.legendConfig.position) { | |
394 | + case types.position.top.value: | |
395 | + legendStyle = 'padding-bottom: 8px;'; | |
396 | + break; | |
397 | + case types.position.bottom.value: | |
398 | + legendStyle = 'padding-top: 8px;'; | |
399 | + break; | |
400 | + case types.position.left.value: | |
401 | + legendStyle = 'padding-right: 0px;'; | |
402 | + break; | |
403 | + case types.position.right.value: | |
404 | + legendStyle = 'padding-left: 0px;'; | |
405 | + break; | |
406 | + } | |
407 | + | |
408 | + var legendHtml = '<tb-legend style="'+legendStyle+'" legend-config="legendConfig" legend-data="legendData"></tb-legend>'; | |
409 | + containerHtml = '<div flex id="widget-container">' + containerHtml + '</div>'; | |
410 | + html += '<div class="tb-absolute-fill" layout="'+layoutType+'">'; | |
411 | + if ($scope.legendConfig.position === types.position.top.value || | |
412 | + $scope.legendConfig.position === types.position.left.value) { | |
413 | + html += legendHtml; | |
414 | + html += containerHtml; | |
415 | + } else { | |
416 | + html += containerHtml; | |
417 | + html += legendHtml; | |
418 | + } | |
419 | + html += '</div>'; | |
373 | 420 | } else { |
374 | - $scope.loadingData = false; | |
421 | + html += containerHtml; | |
375 | 422 | } |
376 | 423 | |
424 | + //TODO: | |
425 | + /*if (progressElement) { | |
426 | + progressScope.$destroy(); | |
427 | + progressScope = null; | |
428 | + | |
429 | + progressElement.remove(); | |
430 | + progressElement = null; | |
431 | + }*/ | |
432 | + | |
433 | + $element.html(html); | |
434 | + | |
435 | + var containerElement = $scope.displayLegend ? angular.element($element[0].querySelector('#widget-container')) : $element; | |
436 | + widgetContext.$container = $('#container', containerElement); | |
437 | + widgetContext.$containerParent = $(containerElement); | |
438 | + | |
439 | + $compile($element.contents())($scope); | |
440 | + | |
441 | + addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef | |
442 | + } | |
443 | + | |
444 | + function destroyWidgetElement() { | |
445 | + removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef | |
446 | + $element.html(''); | |
447 | + widgetContext.$container = null; | |
448 | + widgetContext.$containerParent = null; | |
449 | + } | |
450 | + | |
451 | + function initialize() { | |
452 | + | |
377 | 453 | $scope.$on('toggleDashboardEditMode', function (event, isEdit) { |
378 | 454 | onEditModeChanged(isEdit); |
379 | 455 | }); |
380 | 456 | |
381 | - addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef | |
382 | - | |
383 | 457 | $scope.$watch(function () { |
384 | 458 | return widget.row + ',' + widget.col + ',' + widget.config.mobileOrder; |
385 | 459 | }, function () { |
... | ... | @@ -398,18 +472,60 @@ export default function WidgetController($scope, $timeout, $window, $element, $q |
398 | 472 | onMobileModeChanged(newIsMobile); |
399 | 473 | }); |
400 | 474 | |
401 | - $scope.$on('entityAliasListChanged', function (event, aliasesInfo) { | |
402 | - subscriptionContext.aliasesInfo = aliasesInfo; | |
475 | + $scope.$on('entityAliasesChanged', function (event, aliasIds) { | |
476 | + var subscriptionChanged = false; | |
403 | 477 | for (var id in widgetContext.subscriptions) { |
404 | 478 | var subscription = widgetContext.subscriptions[id]; |
405 | - subscription.onAliasesChanged(); | |
479 | + subscriptionChanged = subscriptionChanged || subscription.onAliasesChanged(aliasIds); | |
480 | + } | |
481 | + if (subscriptionChanged && !vm.useCustomDatasources) { | |
482 | + reInit(); | |
406 | 483 | } |
407 | 484 | }); |
408 | 485 | |
409 | 486 | $scope.$on("$destroy", function () { |
410 | - removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef | |
411 | 487 | onDestroy(); |
412 | 488 | }); |
489 | + | |
490 | + configureWidgetElement(); | |
491 | + var deferred = $q.defer(); | |
492 | + if (!vm.useCustomDatasources) { | |
493 | + createDefaultSubscription().then( | |
494 | + function success() { | |
495 | + subscriptionInited = true; | |
496 | + deferred.resolve(); | |
497 | + }, | |
498 | + function fail() { | |
499 | + subscriptionInited = true; | |
500 | + deferred.reject(); | |
501 | + } | |
502 | + ); | |
503 | + } else { | |
504 | + $scope.loadingData = false; | |
505 | + subscriptionInited = true; | |
506 | + deferred.resolve(); | |
507 | + } | |
508 | + return deferred.promise; | |
509 | + } | |
510 | + | |
511 | + function reInit() { | |
512 | + onDestroy(); | |
513 | + configureWidgetElement(); | |
514 | + if (!vm.useCustomDatasources) { | |
515 | + createDefaultSubscription().then( | |
516 | + function success() { | |
517 | + subscriptionInited = true; | |
518 | + onInit(); | |
519 | + }, | |
520 | + function fail() { | |
521 | + subscriptionInited = true; | |
522 | + onInit(); | |
523 | + } | |
524 | + ); | |
525 | + } else { | |
526 | + subscriptionInited = true; | |
527 | + onInit(); | |
528 | + } | |
413 | 529 | } |
414 | 530 | |
415 | 531 | function handleWidgetException(e) { |
... | ... | @@ -417,8 +533,15 @@ export default function WidgetController($scope, $timeout, $window, $element, $q |
417 | 533 | $scope.widgetErrorData = utils.processWidgetException(e); |
418 | 534 | } |
419 | 535 | |
420 | - function onInit() { | |
421 | - if (!widgetContext.inited) { | |
536 | + function isReady() { | |
537 | + return subscriptionInited && gridsterItemInited && widgetSizeDetected; | |
538 | + } | |
539 | + | |
540 | + function onInit(skipSizeCheck) { | |
541 | + if (!skipSizeCheck) { | |
542 | + checkSize(); | |
543 | + } | |
544 | + if (!widgetContext.inited && isReady()) { | |
422 | 545 | widgetContext.inited = true; |
423 | 546 | try { |
424 | 547 | widgetTypeInstance.onInit(); |
... | ... | @@ -443,6 +566,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q |
443 | 566 | widgetContext.width = width; |
444 | 567 | widgetContext.height = height; |
445 | 568 | sizeChanged = true; |
569 | + widgetSizeDetected = true; | |
446 | 570 | } |
447 | 571 | } |
448 | 572 | return sizeChanged; |
... | ... | @@ -462,8 +586,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q |
462 | 586 | handleWidgetException(e); |
463 | 587 | } |
464 | 588 | }); |
465 | - } else if (gridsterItemInited) { | |
466 | - onInit(); | |
589 | + } else { | |
590 | + onInit(true); | |
467 | 591 | } |
468 | 592 | } |
469 | 593 | } |
... | ... | @@ -472,9 +596,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q |
472 | 596 | if (item && item.gridster) { |
473 | 597 | widgetContext.isMobile = item.gridster.isMobile; |
474 | 598 | gridsterItemInited = true; |
475 | - if (checkSize()) { | |
476 | - onInit(); | |
477 | - } | |
599 | + onInit(); | |
478 | 600 | // gridsterItemElement = $(item.$element); |
479 | 601 | //updateVisibility(); |
480 | 602 | } |
... | ... | @@ -544,6 +666,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q |
544 | 666 | var subscription = widgetContext.subscriptions[id]; |
545 | 667 | subscription.destroy(); |
546 | 668 | } |
669 | + subscriptionInited = false; | |
547 | 670 | widgetContext.subscriptions = []; |
548 | 671 | if (widgetContext.inited) { |
549 | 672 | widgetContext.inited = false; |
... | ... | @@ -559,6 +682,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q |
559 | 682 | handleWidgetException(e); |
560 | 683 | } |
561 | 684 | } |
685 | + destroyWidgetElement(); | |
562 | 686 | } |
563 | 687 | |
564 | 688 | //TODO: widgets visibility | ... | ... |
... | ... | @@ -28,7 +28,7 @@ export default angular.module('thingsboard.directives.widget', [thingsboardLegen |
28 | 28 | .name; |
29 | 29 | |
30 | 30 | /*@ngInject*/ |
31 | -function Widget($controller, $compile, types, widgetService) { | |
31 | +function Widget($controller, widgetService) { | |
32 | 32 | return { |
33 | 33 | scope: true, |
34 | 34 | link: function (scope, elem, attrs) { |
... | ... | @@ -81,90 +81,9 @@ function Widget($controller, $compile, types, widgetService) { |
81 | 81 | |
82 | 82 | elem.addClass(widgetNamespace); |
83 | 83 | |
84 | - var html = '<div class="tb-absolute-fill tb-widget-error" ng-if="widgetErrorData">' + | |
85 | - '<span>Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}</span>' + | |
86 | - '</div>' + | |
87 | - '<div class="tb-absolute-fill tb-widget-loading" ng-show="loadingData" layout="column" layout-align="center center">' + | |
88 | - '<md-progress-circular md-mode="indeterminate" ng-disabled="!loadingData" class="md-accent" md-diameter="40"></md-progress-circular>' + | |
89 | - '</div>'; | |
90 | - | |
91 | - scope.displayLegend = angular.isDefined(widget.config.showLegend) ? | |
92 | - widget.config.showLegend : widget.type === types.widgetType.timeseries.value; | |
93 | - | |
94 | - | |
95 | - var containerHtml = '<div id="container">' + widgetInfo.templateHtml + '</div>'; | |
96 | - if (scope.displayLegend) { | |
97 | - scope.legendConfig = widget.config.legendConfig || | |
98 | - { | |
99 | - position: types.position.bottom.value, | |
100 | - showMin: false, | |
101 | - showMax: false, | |
102 | - showAvg: widget.type === types.widgetType.timeseries.value, | |
103 | - showTotal: false | |
104 | - }; | |
105 | - scope.legendData = { | |
106 | - keys: [], | |
107 | - data: [] | |
108 | - }; | |
109 | - | |
110 | - var layoutType; | |
111 | - if (scope.legendConfig.position === types.position.top.value || | |
112 | - scope.legendConfig.position === types.position.bottom.value) { | |
113 | - layoutType = 'column'; | |
114 | - } else { | |
115 | - layoutType = 'row'; | |
116 | - } | |
117 | - | |
118 | - var legendStyle; | |
119 | - switch(scope.legendConfig.position) { | |
120 | - case types.position.top.value: | |
121 | - legendStyle = 'padding-bottom: 8px;'; | |
122 | - break; | |
123 | - case types.position.bottom.value: | |
124 | - legendStyle = 'padding-top: 8px;'; | |
125 | - break; | |
126 | - case types.position.left.value: | |
127 | - legendStyle = 'padding-right: 0px;'; | |
128 | - break; | |
129 | - case types.position.right.value: | |
130 | - legendStyle = 'padding-left: 0px;'; | |
131 | - break; | |
132 | - } | |
133 | - | |
134 | - var legendHtml = '<tb-legend style="'+legendStyle+'" legend-config="legendConfig" legend-data="legendData"></tb-legend>'; | |
135 | - containerHtml = '<div flex id="widget-container">' + containerHtml + '</div>'; | |
136 | - html += '<div class="tb-absolute-fill" layout="'+layoutType+'">'; | |
137 | - if (scope.legendConfig.position === types.position.top.value || | |
138 | - scope.legendConfig.position === types.position.left.value) { | |
139 | - html += legendHtml; | |
140 | - html += containerHtml; | |
141 | - } else { | |
142 | - html += containerHtml; | |
143 | - html += legendHtml; | |
144 | - } | |
145 | - html += '</div>'; | |
146 | - } else { | |
147 | - html += containerHtml; | |
148 | - } | |
149 | - | |
150 | - //TODO: | |
151 | - /*if (progressElement) { | |
152 | - progressScope.$destroy(); | |
153 | - progressScope = null; | |
154 | - | |
155 | - progressElement.remove(); | |
156 | - progressElement = null; | |
157 | - }*/ | |
158 | - | |
159 | - elem.html(html); | |
160 | - | |
161 | - var containerElement = scope.displayLegend ? angular.element(elem[0].querySelector('#widget-container')) : elem; | |
162 | - | |
163 | - $compile(elem.contents())(scope); | |
164 | - | |
165 | 84 | var widgetType = widgetService.getWidgetTypeFunction(widget.bundleAlias, widget.typeAlias, widget.isSystemType); |
166 | 85 | |
167 | - angular.extend(locals, {$scope: scope, $element: containerElement, widgetType: widgetType}); | |
86 | + angular.extend(locals, {$scope: scope, $element: elem, widgetInfo: widgetInfo, widgetType: widgetType}); | |
168 | 87 | |
169 | 88 | widgetController = $controller('WidgetController', locals); |
170 | 89 | ... | ... |
... | ... | @@ -15,17 +15,18 @@ |
15 | 15 | */ |
16 | 16 | /* eslint-disable import/no-unresolved, import/default */ |
17 | 17 | |
18 | -import entityAliasesTemplate from '../entity/entity-aliases.tpl.html'; | |
18 | +import entityAliasDialogTemplate from '../entity/alias/entity-alias-dialog.tpl.html'; | |
19 | 19 | |
20 | 20 | /* eslint-enable import/no-unresolved, import/default */ |
21 | 21 | |
22 | 22 | /*@ngInject*/ |
23 | -export default function AddWidgetController($scope, widgetService, entityService, $mdDialog, $q, $document, types, dashboard, aliasesInfo, widget, widgetInfo) { | |
23 | +export default function AddWidgetController($scope, widgetService, entityService, $mdDialog, $q, $document, types, dashboard, | |
24 | + aliasController, widget, widgetInfo) { | |
24 | 25 | |
25 | 26 | var vm = this; |
26 | 27 | |
27 | 28 | vm.dashboard = dashboard; |
28 | - vm.aliasesInfo = aliasesInfo; | |
29 | + vm.aliasController = aliasController; | |
29 | 30 | vm.widget = widget; |
30 | 31 | vm.widgetInfo = widgetInfo; |
31 | 32 | |
... | ... | @@ -85,7 +86,7 @@ export default function AddWidgetController($scope, widgetService, entityService |
85 | 86 | } |
86 | 87 | |
87 | 88 | function cancel () { |
88 | - $mdDialog.cancel({aliasesInfo: vm.aliasesInfo}); | |
89 | + $mdDialog.cancel(); | |
89 | 90 | } |
90 | 91 | |
91 | 92 | function add () { |
... | ... | @@ -94,52 +95,58 @@ export default function AddWidgetController($scope, widgetService, entityService |
94 | 95 | vm.widget.config = vm.widgetConfig.config; |
95 | 96 | vm.widget.config.mobileOrder = vm.widgetConfig.layout.mobileOrder; |
96 | 97 | vm.widget.config.mobileHeight = vm.widgetConfig.layout.mobileHeight; |
97 | - $mdDialog.hide({widget: vm.widget, aliasesInfo: vm.aliasesInfo}); | |
98 | + $mdDialog.hide({widget: vm.widget}); | |
98 | 99 | } |
99 | 100 | } |
100 | 101 | |
101 | 102 | function fetchEntityKeys (entityAliasId, query, type) { |
102 | - var entityAlias = vm.aliasesInfo.entityAliases[entityAliasId]; | |
103 | - if (entityAlias && entityAlias.entityId) { | |
104 | - return entityService.getEntityKeys(entityAlias.entityType, entityAlias.entityId, query, type); | |
105 | - } else { | |
106 | - return $q.when([]); | |
107 | - } | |
103 | + var deferred = $q.defer(); | |
104 | + vm.aliasController.getAliasInfo(entityAliasId).then( | |
105 | + function success(aliasInfo) { | |
106 | + var entity = aliasInfo.currentEntity; | |
107 | + if (entity) { | |
108 | + entityService.getEntityKeys(entity.entityType, entity.id, query, type).then( | |
109 | + function success(keys) { | |
110 | + deferred.resolve(keys); | |
111 | + }, | |
112 | + function fail() { | |
113 | + deferred.resolve([]); | |
114 | + } | |
115 | + ); | |
116 | + } else { | |
117 | + deferred.resolve([]); | |
118 | + } | |
119 | + }, | |
120 | + function fail() { | |
121 | + deferred.resolve([]); | |
122 | + } | |
123 | + ); | |
124 | + return deferred.promise; | |
108 | 125 | } |
109 | 126 | |
110 | 127 | function createEntityAlias (event, alias, allowedEntityTypes) { |
111 | 128 | |
112 | 129 | var deferred = $q.defer(); |
113 | - var singleEntityAlias = {id: null, alias: alias, entityType: types.entityType.device, entityFilter: null}; | |
130 | + var singleEntityAlias = {id: null, alias: alias, filter: {}}; | |
114 | 131 | |
115 | 132 | $mdDialog.show({ |
116 | - controller: 'EntityAliasesController', | |
133 | + controller: 'EntityAliasDialogController', | |
117 | 134 | controllerAs: 'vm', |
118 | - templateUrl: entityAliasesTemplate, | |
135 | + templateUrl: entityAliasDialogTemplate, | |
119 | 136 | locals: { |
120 | - config: { | |
121 | - entityAliases: angular.copy(vm.dashboard.configuration.entityAliases), | |
122 | - widgets: null, | |
123 | - isSingleEntityAlias: true, | |
124 | - singleEntityAlias: singleEntityAlias, | |
125 | - allowedEntityTypes: allowedEntityTypes | |
126 | - } | |
137 | + isAdd: true, | |
138 | + allowedEntityTypes: allowedEntityTypes, | |
139 | + entityAliases: vm.dashboard.configuration.entityAliases, | |
140 | + alias: singleEntityAlias | |
127 | 141 | }, |
128 | 142 | parent: angular.element($document[0].body), |
129 | 143 | fullscreen: true, |
130 | 144 | skipHide: true, |
131 | 145 | targetEvent: event |
132 | 146 | }).then(function (singleEntityAlias) { |
133 | - vm.dashboard.configuration.entityAliases[singleEntityAlias.id] = | |
134 | - { alias: singleEntityAlias.alias, entityType: singleEntityAlias.entityType, entityFilter: singleEntityAlias.entityFilter }; | |
135 | - entityService.processEntityAliases(vm.dashboard.configuration.entityAliases).then( | |
136 | - function(resolution) { | |
137 | - if (!resolution.error) { | |
138 | - vm.aliasesInfo = resolution.aliasesInfo; | |
139 | - } | |
140 | - deferred.resolve(singleEntityAlias); | |
141 | - } | |
142 | - ); | |
147 | + vm.dashboard.configuration.entityAliases[singleEntityAlias.id] = singleEntityAlias; | |
148 | + vm.aliasController.updateEntityAliases(vm.dashboard.configuration.entityAliases); | |
149 | + deferred.resolve(singleEntityAlias); | |
143 | 150 | }, function () { |
144 | 151 | deferred.reject(); |
145 | 152 | }); | ... | ... |
... | ... | @@ -37,7 +37,7 @@ |
37 | 37 | ng-model="vm.widgetConfig" |
38 | 38 | widget-settings-schema="vm.settingsSchema" |
39 | 39 | datakey-settings-schema="vm.dataKeySettingsSchema" |
40 | - entity-aliases="vm.aliasesInfo.entityAliases" | |
40 | + alias-controller="vm.aliasController" | |
41 | 41 | functions-only="vm.functionsOnly" |
42 | 42 | fetch-entity-keys="vm.fetchEntityKeys(entityAliasId, query, type)" |
43 | 43 | on-create-entity-alias="vm.createEntityAlias(event, alias, allowedEntityTypes)" | ... | ... |
... | ... | @@ -15,7 +15,7 @@ |
15 | 15 | */ |
16 | 16 | /* eslint-disable import/no-unresolved, import/default */ |
17 | 17 | |
18 | -import entityAliasesTemplate from '../entity/entity-aliases.tpl.html'; | |
18 | +import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html'; | |
19 | 19 | import dashboardSettingsTemplate from './dashboard-settings.tpl.html'; |
20 | 20 | import manageDashboardLayoutsTemplate from './layouts/manage-dashboard-layouts.tpl.html'; |
21 | 21 | import manageDashboardStatesTemplate from './states/manage-dashboard-states.tpl.html'; |
... | ... | @@ -24,8 +24,10 @@ import selectTargetLayoutTemplate from './layouts/select-target-layout.tpl.html' |
24 | 24 | |
25 | 25 | /* eslint-enable import/no-unresolved, import/default */ |
26 | 26 | |
27 | +import AliasController from '../api/alias-controller'; | |
28 | + | |
27 | 29 | /*@ngInject*/ |
28 | -export default function DashboardController(types, dashboardUtils, widgetService, userService, | |
30 | +export default function DashboardController(types, utils, dashboardUtils, widgetService, userService, | |
29 | 31 | dashboardService, timeService, entityService, itembuffer, importExport, hotkeys, $window, $rootScope, |
30 | 32 | $scope, $element, $state, $stateParams, $mdDialog, $mdMedia, $timeout, $document, $q, $translate, $filter) { |
31 | 33 | |
... | ... | @@ -342,6 +344,8 @@ export default function DashboardController(types, dashboardUtils, widgetService |
342 | 344 | vm.dashboardConfiguration = vm.dashboard.configuration; |
343 | 345 | vm.dashboardCtx.dashboard = vm.dashboard; |
344 | 346 | vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow; |
347 | + vm.dashboardCtx.aliasController = new AliasController($scope, $q, $filter, utils, | |
348 | + types, entityService, vm.dashboardCtx.stateController, vm.dashboardConfiguration.entityAliases); | |
345 | 349 | var parentScope = $window.parent.angular.element($window.frameElement).scope(); |
346 | 350 | parentScope.$root.$broadcast('widgetEditModeInited'); |
347 | 351 | parentScope.$root.$apply(); |
... | ... | @@ -349,7 +353,13 @@ export default function DashboardController(types, dashboardUtils, widgetService |
349 | 353 | dashboardService.getDashboard($stateParams.dashboardId) |
350 | 354 | .then(function success(dashboard) { |
351 | 355 | vm.dashboard = dashboardUtils.validateAndUpdateDashboard(dashboard); |
352 | - entityService.processEntityAliases(vm.dashboard.configuration.entityAliases) | |
356 | + vm.dashboardConfiguration = vm.dashboard.configuration; | |
357 | + vm.dashboardCtx.dashboard = vm.dashboard; | |
358 | + vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow; | |
359 | + vm.dashboardCtx.aliasController = new AliasController($scope, $q, $filter, utils, | |
360 | + types, entityService, vm.dashboardCtx.stateController, vm.dashboardConfiguration.entityAliases); | |
361 | + | |
362 | + /* entityService.processEntityAliases(vm.dashboard.configuration.entityAliases) | |
353 | 363 | .then( |
354 | 364 | function(resolution) { |
355 | 365 | if (resolution.error && !isTenantAdmin()) { |
... | ... | @@ -362,7 +372,7 @@ export default function DashboardController(types, dashboardUtils, widgetService |
362 | 372 | vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow; |
363 | 373 | } |
364 | 374 | } |
365 | - ); | |
375 | + );*/ | |
366 | 376 | }, function fail() { |
367 | 377 | vm.configurationError = true; |
368 | 378 | }); |
... | ... | @@ -373,6 +383,7 @@ export default function DashboardController(types, dashboardUtils, widgetService |
373 | 383 | var layoutsData = dashboardUtils.getStateLayoutsData(vm.dashboard, state); |
374 | 384 | if (layoutsData) { |
375 | 385 | vm.dashboardCtx.state = state; |
386 | + vm.dashboardCtx.aliasController.dashboardStateChanged(); | |
376 | 387 | var layoutVisibilityChanged = false; |
377 | 388 | for (var l in vm.layouts) { |
378 | 389 | var layout = vm.layouts[l]; |
... | ... | @@ -916,7 +927,7 @@ export default function DashboardController(types, dashboardUtils, widgetService |
916 | 927 | templateUrl: addWidgetTemplate, |
917 | 928 | locals: { |
918 | 929 | dashboard: vm.dashboard, |
919 | - aliasesInfo: vm.dashboardCtx.aliasesInfo, | |
930 | + aliasController: vm.dashboardCtx.aliasController, | |
920 | 931 | widget: newWidget, |
921 | 932 | widgetInfo: widgetTypeInfo |
922 | 933 | }, |
... | ... | @@ -930,10 +941,8 @@ export default function DashboardController(types, dashboardUtils, widgetService |
930 | 941 | } |
931 | 942 | }).then(function (result) { |
932 | 943 | var widget = result.widget; |
933 | - vm.dashboardCtx.aliasesInfo = result.aliasesInfo; | |
934 | 944 | addWidget(widget); |
935 | - }, function (rejection) { | |
936 | - vm.dashboardCtx.aliasesInfo = rejection.aliasesInfo; | |
945 | + }, function () { | |
937 | 946 | }); |
938 | 947 | } |
939 | 948 | } |
... | ... | @@ -1025,7 +1034,7 @@ export default function DashboardController(types, dashboardUtils, widgetService |
1025 | 1034 | notifyDashboardUpdated(); |
1026 | 1035 | } |
1027 | 1036 | |
1028 | - function showAliasesResolutionError(error) { | |
1037 | +/* function showAliasesResolutionError(error) { | |
1029 | 1038 | var alert = $mdDialog.alert() |
1030 | 1039 | .parent(angular.element($document[0].body)) |
1031 | 1040 | .clickOutsideToClose(true) |
... | ... | @@ -1037,20 +1046,10 @@ export default function DashboardController(types, dashboardUtils, widgetService |
1037 | 1046 | alert._options.fullscreen = true; |
1038 | 1047 | |
1039 | 1048 | $mdDialog.show(alert); |
1040 | - } | |
1049 | + }*/ | |
1041 | 1050 | |
1042 | 1051 | function entityAliasesUpdated() { |
1043 | - var deferred = $q.defer(); | |
1044 | - entityService.processEntityAliases(vm.dashboard.configuration.entityAliases) | |
1045 | - .then( | |
1046 | - function(resolution) { | |
1047 | - if (resolution.aliasesInfo) { | |
1048 | - vm.dashboardCtx.aliasesInfo = resolution.aliasesInfo; | |
1049 | - } | |
1050 | - deferred.resolve(); | |
1051 | - } | |
1052 | - ); | |
1053 | - return deferred.promise; | |
1052 | + vm.dashboardCtx.aliasController.updateEntityAliases(vm.dashboard.configuration.entityAliases); | |
1054 | 1053 | } |
1055 | 1054 | |
1056 | 1055 | function notifyDashboardUpdated() { | ... | ... |
... | ... | @@ -57,8 +57,7 @@ |
57 | 57 | </tb-timewindow> |
58 | 58 | <tb-aliases-entity-select ng-show="!vm.isEdit && vm.displayEntitiesSelect()" |
59 | 59 | tooltip-direction="bottom" |
60 | - ng-model="vm.dashboardCtx.aliasesInfo.entityAliases" | |
61 | - entity-aliases-info="vm.dashboardCtx.aliasesInfo.entityAliasesInfo"> | |
60 | + alias-controller="vm.dashboardCtx.aliasController"> | |
62 | 61 | </tb-aliases-entity-select> |
63 | 62 | <md-button ng-show="vm.isEdit" aria-label="{{ 'entity.aliases' | translate }}" class="md-icon-button" |
64 | 63 | ng-click="vm.openEntityAliases($event)"> |
... | ... | @@ -179,7 +178,7 @@ |
179 | 178 | <form name="vm.widgetForm" ng-if="vm.isEditingWidget"> |
180 | 179 | <tb-edit-widget |
181 | 180 | dashboard="vm.dashboard" |
182 | - aliases-info="vm.dashboardCtx.aliasesInfo" | |
181 | + alias-controller="vm.dashboardCtx.aliasController" | |
183 | 182 | widget="vm.editingWidget" |
184 | 183 | widget-layout="vm.editingWidgetLayout" |
185 | 184 | the-form="vm.widgetForm"> | ... | ... |
... | ... | @@ -15,7 +15,7 @@ |
15 | 15 | */ |
16 | 16 | /* eslint-disable import/no-unresolved, import/default */ |
17 | 17 | |
18 | -import entityAliasesTemplate from '../entity/entity-aliases.tpl.html'; | |
18 | +import entityAliasDialogTemplate from '../entity/alias/entity-alias-dialog.tpl.html'; | |
19 | 19 | import editWidgetTemplate from './edit-widget.tpl.html'; |
20 | 20 | |
21 | 21 | /* eslint-enable import/no-unresolved, import/default */ |
... | ... | @@ -68,47 +68,53 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid |
68 | 68 | }); |
69 | 69 | |
70 | 70 | scope.fetchEntityKeys = function (entityAliasId, query, type) { |
71 | - var entityAlias = scope.aliasesInfo.entityAliases[entityAliasId]; | |
72 | - if (entityAlias && entityAlias.entityId) { | |
73 | - return entityService.getEntityKeys(entityAlias.entityType, entityAlias.entityId, query, type); | |
74 | - } else { | |
75 | - return $q.when([]); | |
76 | - } | |
71 | + var deferred = $q.defer(); | |
72 | + scope.aliasController.getAliasInfo(entityAliasId).then( | |
73 | + function success(aliasInfo) { | |
74 | + var entity = aliasInfo.currentEntity; | |
75 | + if (entity) { | |
76 | + entityService.getEntityKeys(entity.entityType, entity.id, query, type).then( | |
77 | + function success(keys) { | |
78 | + deferred.resolve(keys); | |
79 | + }, | |
80 | + function fail() { | |
81 | + deferred.resolve([]); | |
82 | + } | |
83 | + ); | |
84 | + } else { | |
85 | + deferred.resolve([]); | |
86 | + } | |
87 | + }, | |
88 | + function fail() { | |
89 | + deferred.resolve([]); | |
90 | + } | |
91 | + ); | |
92 | + return deferred.promise; | |
77 | 93 | }; |
78 | 94 | |
79 | 95 | scope.createEntityAlias = function (event, alias, allowedEntityTypes) { |
80 | 96 | |
81 | 97 | var deferred = $q.defer(); |
82 | - var singleEntityAlias = {id: null, alias: alias, entityType: types.entityType.device, entityFilter: null}; | |
98 | + var singleEntityAlias = {id: null, alias: alias, filter: {}}; | |
83 | 99 | |
84 | 100 | $mdDialog.show({ |
85 | - controller: 'EntityAliasesController', | |
101 | + controller: 'EntityAliasDialogController', | |
86 | 102 | controllerAs: 'vm', |
87 | - templateUrl: entityAliasesTemplate, | |
103 | + templateUrl: entityAliasDialogTemplate, | |
88 | 104 | locals: { |
89 | - config: { | |
90 | - entityAliases: angular.copy(scope.dashboard.configuration.entityAliases), | |
91 | - widgets: null, | |
92 | - isSingleEntityAlias: true, | |
93 | - singleEntityAlias: singleEntityAlias, | |
94 | - allowedEntityTypes: allowedEntityTypes | |
95 | - } | |
105 | + isAdd: true, | |
106 | + allowedEntityTypes: allowedEntityTypes, | |
107 | + entityAliases: scope.dashboard.configuration.entityAliases, | |
108 | + alias: singleEntityAlias | |
96 | 109 | }, |
97 | 110 | parent: angular.element($document[0].body), |
98 | 111 | fullscreen: true, |
99 | 112 | skipHide: true, |
100 | 113 | targetEvent: event |
101 | 114 | }).then(function (singleEntityAlias) { |
102 | - scope.dashboard.configuration.entityAliases[singleEntityAlias.id] = | |
103 | - { alias: singleEntityAlias.alias, entityType: singleEntityAlias.entityType, entityFilter: singleEntityAlias.entityFilter }; | |
104 | - entityService.processEntityAliases(scope.dashboard.configuration.entityAliases).then( | |
105 | - function(resolution) { | |
106 | - if (!resolution.error) { | |
107 | - scope.aliasesInfo = resolution.aliasesInfo; | |
108 | - } | |
109 | - deferred.resolve(singleEntityAlias); | |
110 | - } | |
111 | - ); | |
115 | + scope.dashboard.configuration.entityAliases[singleEntityAlias.id] = singleEntityAlias; | |
116 | + scope.aliasController.updateEntityAliases(scope.dashboard.configuration.entityAliases); | |
117 | + deferred.resolve(singleEntityAlias); | |
112 | 118 | }, function () { |
113 | 119 | deferred.reject(); |
114 | 120 | }); |
... | ... | @@ -124,7 +130,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid |
124 | 130 | link: linker, |
125 | 131 | scope: { |
126 | 132 | dashboard: '=', |
127 | - aliasesInfo: '=', | |
133 | + aliasController: '=', | |
128 | 134 | widget: '=', |
129 | 135 | widgetLayout: '=', |
130 | 136 | theForm: '=' | ... | ... |
... | ... | @@ -21,7 +21,7 @@ |
21 | 21 | is-data-enabled="isDataEnabled" |
22 | 22 | widget-settings-schema="settingsSchema" |
23 | 23 | datakey-settings-schema="dataKeySettingsSchema" |
24 | - entity-aliases="aliasesInfo.entityAliases" | |
24 | + alias-controller="aliasController" | |
25 | 25 | functions-only="functionsOnly" |
26 | 26 | fetch-entity-keys="fetchEntityKeys(entityAliasId, query, type)" |
27 | 27 | on-create-entity-alias="createEntityAlias(event, alias, allowedEntityTypes)" | ... | ... |
... | ... | @@ -45,7 +45,7 @@ |
45 | 45 | widget-layouts="vm.layoutCtx.widgetLayouts" |
46 | 46 | columns="vm.layoutCtx.gridSettings.columns" |
47 | 47 | margins="vm.layoutCtx.gridSettings.margins" |
48 | - aliases-info="vm.dashboardCtx.aliasesInfo" | |
48 | + alias-controller="vm.dashboardCtx.aliasController" | |
49 | 49 | state-controller="vm.dashboardCtx.stateController" |
50 | 50 | dashboard-timewindow="vm.dashboardCtx.dashboardTimewindow" |
51 | 51 | is-edit="vm.isEdit" | ... | ... |
ui/src/app/entity/alias/aliases-entity-select-button.tpl.html
renamed from
ui/src/app/entity/aliases-entity-select-button.tpl.html
ui/src/app/entity/alias/aliases-entity-select-panel.controller.js
renamed from
ui/src/app/entity/aliases-entity-select-panel.controller.js
... | ... | @@ -15,17 +15,35 @@ |
15 | 15 | */ |
16 | 16 | |
17 | 17 | /*@ngInject*/ |
18 | -export default function AliasesEntitySelectPanelController(mdPanelRef, $scope, types, entityAliases, entityAliasesInfo, onEntityAliasesUpdate) { | |
18 | +export default function AliasesEntitySelectPanelController(mdPanelRef, $scope, $filter, types, aliasController, onEntityAliasesUpdate) { | |
19 | 19 | |
20 | 20 | var vm = this; |
21 | 21 | vm._mdPanelRef = mdPanelRef; |
22 | - vm.entityAliases = entityAliases; | |
23 | - vm.entityAliasesInfo = entityAliasesInfo; | |
22 | + vm.aliasController = aliasController; | |
24 | 23 | vm.onEntityAliasesUpdate = onEntityAliasesUpdate; |
24 | + vm.entityAliases = {}; | |
25 | + vm.entityAliasesInfo = {}; | |
25 | 26 | |
26 | - $scope.$watch('vm.entityAliases', function () { | |
27 | - if (onEntityAliasesUpdate) { | |
28 | - onEntityAliasesUpdate(vm.entityAliases); | |
27 | + vm.currentAliasEntityChanged = currentAliasEntityChanged; | |
28 | + | |
29 | + var allEntityAliases = vm.aliasController.getEntityAliases(); | |
30 | + for (var aliasId in allEntityAliases) { | |
31 | + var aliasInfo = vm.aliasController.getInstantAliasInfo(aliasId); | |
32 | + if (aliasInfo && !aliasInfo.resolveMultiple && aliasInfo.currentEntity) { | |
33 | + vm.entityAliasesInfo[aliasId] = angular.copy(aliasInfo); | |
34 | + vm.entityAliasesInfo[aliasId].selectedId = aliasInfo.currentEntity.id; | |
35 | + } | |
36 | + } | |
37 | + | |
38 | + function currentAliasEntityChanged(aliasId, selectedId) { | |
39 | + var resolvedEntities = vm.entityAliasesInfo[aliasId].resolvedEntities; | |
40 | + var selected = $filter('filter')(resolvedEntities, {id: selectedId}); | |
41 | + if (selected && selected.length) { | |
42 | + vm.aliasController.updateCurrentAliasEntity(aliasId, selected[0]); | |
43 | + if (onEntityAliasesUpdate) { | |
44 | + onEntityAliasesUpdate(); | |
45 | + } | |
29 | 46 | } |
30 | - }, true); | |
47 | + } | |
48 | + | |
31 | 49 | } | ... | ... |
ui/src/app/entity/alias/aliases-entity-select-panel.tpl.html
renamed from
ui/src/app/entity/aliases-entity-select-panel.tpl.html
... | ... | @@ -18,12 +18,12 @@ |
18 | 18 | <md-content flex layout="column"> |
19 | 19 | <section flex layout="column"> |
20 | 20 | <md-content flex class="md-padding" layout="column"> |
21 | - <div flex layout="row" ng-repeat="(aliasId, entityAlias) in vm.entityAliases"> | |
21 | + <div flex layout="row" ng-repeat="(aliasId, entityAliasInfo) in vm.entityAliasesInfo"> | |
22 | 22 | <md-input-container flex> |
23 | - <label>{{entityAlias.alias}}</label> | |
24 | - <md-select ng-model="vm.entityAliases[aliasId].entityId"> | |
25 | - <md-option ng-repeat="entityInfo in vm.entityAliasesInfo[aliasId]" ng-value="entityInfo.id"> | |
26 | - {{entityInfo.name}} | |
23 | + <label>{{entityAliasInfo.alias}}</label> | |
24 | + <md-select ng-model="entityAliasInfo.selectedId" ng-change="vm.currentAliasEntityChanged(aliasId, entityAliasInfo.selectedId)"> | |
25 | + <md-option ng-repeat="resolvedEntity in entityAliasInfo.resolvedEntities" ng-value="resolvedEntity.id"> | |
26 | + {{resolvedEntity.name}} | |
27 | 27 | </md-option> |
28 | 28 | </md-select> |
29 | 29 | </md-input-container> | ... | ... |
ui/src/app/entity/alias/aliases-entity-select.directive.js
renamed from
ui/src/app/entity/aliases-entity-select.directive.js
... | ... | @@ -29,7 +29,7 @@ import aliasesEntitySelectPanelTemplate from './aliases-entity-select-panel.tpl. |
29 | 29 | /*@ngInject*/ |
30 | 30 | export default function AliasesEntitySelectDirective($compile, $templateCache, $mdMedia, types, $mdPanel, $document, $translate) { |
31 | 31 | |
32 | - var linker = function (scope, element, attrs, ngModelCtrl) { | |
32 | + var linker = function (scope, element, attrs) { | |
33 | 33 | |
34 | 34 | /* tbAliasesEntitySelect (ng-model) |
35 | 35 | * { |
... | ... | @@ -81,10 +81,8 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $ |
81 | 81 | position: position, |
82 | 82 | fullscreen: false, |
83 | 83 | locals: { |
84 | - 'entityAliases': angular.copy(scope.model), | |
85 | - 'entityAliasesInfo': scope.entityAliasesInfo, | |
86 | - 'onEntityAliasesUpdate': function (entityAliases) { | |
87 | - scope.model = entityAliases; | |
84 | + 'aliasController': scope.aliasController, | |
85 | + 'onEntityAliasesUpdate': function () { | |
88 | 86 | scope.updateView(); |
89 | 87 | } |
90 | 88 | }, |
... | ... | @@ -96,41 +94,40 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $ |
96 | 94 | $mdPanel.open(config); |
97 | 95 | } |
98 | 96 | |
97 | + scope.$on('entityAliasesChanged', function() { | |
98 | + scope.updateView(); | |
99 | + }); | |
100 | + | |
101 | + scope.$on('entityAliasResolved', function() { | |
102 | + scope.updateView(); | |
103 | + }); | |
104 | + | |
99 | 105 | scope.updateView = function () { |
100 | - var value = angular.copy(scope.model); | |
101 | - ngModelCtrl.$setViewValue(value); | |
102 | 106 | updateDisplayValue(); |
103 | 107 | } |
104 | 108 | |
105 | - ngModelCtrl.$render = function () { | |
106 | - if (ngModelCtrl.$viewValue) { | |
107 | - var value = ngModelCtrl.$viewValue; | |
108 | - scope.model = angular.copy(value); | |
109 | - updateDisplayValue(); | |
110 | - } | |
111 | - } | |
112 | - | |
113 | 109 | function updateDisplayValue() { |
114 | 110 | var displayValue; |
115 | 111 | var singleValue = true; |
116 | 112 | var currentAliasId; |
117 | - for (var aliasId in scope.model) { | |
118 | - if (!currentAliasId) { | |
119 | - currentAliasId = aliasId; | |
120 | - } else { | |
121 | - singleValue = false; | |
122 | - break; | |
113 | + var entityAliases = scope.aliasController.getEntityAliases(); | |
114 | + for (var aliasId in entityAliases) { | |
115 | + var entityAlias = entityAliases[aliasId]; | |
116 | + if (!entityAlias.filter.resolveMultiple) { | |
117 | + var resolvedAlias = scope.aliasController.getInstantAliasInfo(aliasId); | |
118 | + if (resolvedAlias && resolvedAlias.currentEntity) { | |
119 | + if (!currentAliasId) { | |
120 | + currentAliasId = aliasId; | |
121 | + } else { | |
122 | + singleValue = false; | |
123 | + break; | |
124 | + } | |
125 | + } | |
123 | 126 | } |
124 | 127 | } |
125 | 128 | if (singleValue && currentAliasId) { |
126 | - var entityId = scope.model[currentAliasId].entityId; | |
127 | - var entitiesInfo = scope.entityAliasesInfo[currentAliasId]; | |
128 | - for (var i=0;i<entitiesInfo.length;i++) { | |
129 | - if (entitiesInfo[i].id === entityId) { | |
130 | - displayValue = entitiesInfo[i].name; | |
131 | - break; | |
132 | - } | |
133 | - } | |
129 | + var aliasInfo = scope.aliasController.getInstantAliasInfo(currentAliasId); | |
130 | + displayValue = aliasInfo.currentEntity.name; | |
134 | 131 | } else { |
135 | 132 | displayValue = $translate.instant('entity.entities'); |
136 | 133 | } |
... | ... | @@ -142,9 +139,8 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $ |
142 | 139 | |
143 | 140 | return { |
144 | 141 | restrict: "E", |
145 | - require: "^ngModel", | |
146 | 142 | scope: { |
147 | - entityAliasesInfo:'=' | |
143 | + aliasController:'=' | |
148 | 144 | }, |
149 | 145 | link: linker |
150 | 146 | }; | ... | ... |
ui/src/app/entity/alias/aliases-entity-select.scss
renamed from
ui/src/app/entity/aliases-entity-select.scss
1 | +/* | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +import './entity-alias-dialog.scss'; | |
18 | + | |
19 | +/*@ngInject*/ | |
20 | +export default function EntityAliasDialogController($scope, $mdDialog, $q, $filter, utils, entityService, types, isAdd, allowedEntityTypes, entityAliases, alias) { | |
21 | + | |
22 | + var vm = this; | |
23 | + | |
24 | + vm.types = types; | |
25 | + vm.isAdd = isAdd; | |
26 | + vm.allowedEntityTypes = allowedEntityTypes; | |
27 | + if (angular.isArray(entityAliases)) { | |
28 | + vm.entityAliases = entityAliases; | |
29 | + } else { | |
30 | + vm.entityAliases = []; | |
31 | + for (var aliasId in entityAliases) { | |
32 | + vm.entityAliases.push(entityAliases[aliasId]); | |
33 | + } | |
34 | + } | |
35 | + if (vm.isAdd && !alias) { | |
36 | + vm.alias = { | |
37 | + alias: '', | |
38 | + filter: { | |
39 | + resolveMultiple: false | |
40 | + } | |
41 | + }; | |
42 | + } else { | |
43 | + vm.alias = alias; | |
44 | + } | |
45 | + | |
46 | + vm.cancel = cancel; | |
47 | + vm.save = save; | |
48 | + | |
49 | + $scope.$watch('vm.alias.alias', function (newAlias) { | |
50 | + if (newAlias) { | |
51 | + var valid = true; | |
52 | + var result = $filter('filter')(vm.entityAliases, {alias: newAlias}, true); | |
53 | + if (result && result.length) { | |
54 | + if (vm.isAdd || vm.alias.id != result[0].id) { | |
55 | + valid = false; | |
56 | + } | |
57 | + } | |
58 | + $scope.theForm.aliasName.$setValidity('duplicateAliasName', valid); | |
59 | + } | |
60 | + }); | |
61 | + | |
62 | + $scope.$watch('theForm.$pristine', function() { | |
63 | + if ($scope.theForm && !$scope.theForm.$pristine) { | |
64 | + $scope.theForm.$setValidity('entityFilter', true); | |
65 | + } | |
66 | + }); | |
67 | + | |
68 | + function validate() { | |
69 | + var deferred = $q.defer(); | |
70 | + var validationResult = { | |
71 | + entity: null, | |
72 | + stateEntity: false | |
73 | + } | |
74 | + entityService.resolveAliasFilter(vm.alias.filter, null, 1).then( | |
75 | + function success(result) { | |
76 | + validationResult.stateEntity = result.stateEntity; | |
77 | + var entities = result.entities; | |
78 | + if (entities.length) { | |
79 | + validationResult.entity = entities[0]; | |
80 | + } | |
81 | + deferred.resolve(validationResult); | |
82 | + }, | |
83 | + function fail() { | |
84 | + deferred.reject(); | |
85 | + } | |
86 | + ); | |
87 | + return deferred.promise; | |
88 | + } | |
89 | + | |
90 | + function cancel() { | |
91 | + $mdDialog.cancel(); | |
92 | + } | |
93 | + | |
94 | + function save() { | |
95 | + $scope.theForm.$setPristine(); | |
96 | + validate().then( | |
97 | + function success() { | |
98 | + if (vm.isAdd) { | |
99 | + vm.alias.id = utils.guid(); | |
100 | + } | |
101 | + $mdDialog.hide(vm.alias); | |
102 | + }, | |
103 | + function fail() { | |
104 | + $scope.theForm.$setValidity('entityFilter', false); | |
105 | + } | |
106 | + ) | |
107 | + } | |
108 | + | |
109 | +} | ... | ... |
ui/src/app/entity/alias/entity-alias-dialog.scss
renamed from
ui/src/app/entity/entity-aliases.scss
... | ... | @@ -14,15 +14,14 @@ |
14 | 14 | * limitations under the License. |
15 | 15 | */ |
16 | 16 | |
17 | -.tb-aliases-dialog { | |
18 | - .md-dialog-content { | |
19 | - padding-bottom: 0px; | |
20 | - } | |
21 | - .tb-alias { | |
22 | - padding: 10px 0 0 10px; | |
23 | - margin: 5px; | |
24 | - md-select.tb-entity-type-select { | |
25 | - padding-bottom: 24px; | |
17 | +.tb-entity-alias-dialog { | |
18 | + .tb-resolve-multiple-switch { | |
19 | + padding-left: 10px; | |
20 | + .resolve-multiple-switch { | |
21 | + margin: 0; | |
22 | + } | |
23 | + .resolve-multiple-label { | |
24 | + margin: 5px 0; | |
26 | 25 | } |
27 | 26 | } |
28 | -} | |
27 | +} | |
\ No newline at end of file | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2017 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<md-dialog class="tb-entity-alias-dialog" style="width: 600px;" aria-label="{{ (vm.isAdd ? 'alias.add' : 'alias.edit') | translate }}"> | |
19 | + <form name="theForm" ng-submit="vm.save()"> | |
20 | + <md-toolbar> | |
21 | + <div class="md-toolbar-tools"> | |
22 | + <h2>{{ (vm.isAdd ? 'alias.add' : 'alias.edit') | translate }}</h2> | |
23 | + <span flex></span> | |
24 | + <md-button class="md-icon-button" ng-click="vm.cancel()"> | |
25 | + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> | |
26 | + </md-button> | |
27 | + </div> | |
28 | + </md-toolbar> | |
29 | + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear> | |
30 | + <span style="min-height: 5px;" flex="" ng-show="!loading"></span> | |
31 | + <md-dialog-content> | |
32 | + <div class="md-dialog-content"> | |
33 | + <fieldset ng-disabled="loading"> | |
34 | + <div flex layout="column"> | |
35 | + <div layout="row"> | |
36 | + <md-input-container flex class="md-block"> | |
37 | + <label translate>alias.name</label> | |
38 | + <input required name="aliasName" | |
39 | + ng-model="vm.alias.alias" | |
40 | + aria-label="{{ 'alias.name' | translate }}"> | |
41 | + <div ng-messages="theForm.aliasName.$error"> | |
42 | + <div ng-message="required" translate>alias.name-required</div> | |
43 | + <div ng-message="duplicateAliasName" translate>alias.duplicate-alias</div> | |
44 | + </div> | |
45 | + </md-input-container> | |
46 | + <section class="tb-resolve-multiple-switch" layout="column" layout-align="start center"> | |
47 | + <label class="tb-small resolve-multiple-label" translate>alias.resolve-multiple</label> | |
48 | + <md-switch class="resolve-multiple-switch" ng-model="vm.alias.filter.resolveMultiple" | |
49 | + aria-label="{{ 'alias.resolve-multiple' | translate }}"> | |
50 | + </md-switch> | |
51 | + </section> | |
52 | + </div> | |
53 | + <tb-entity-filter | |
54 | + ng-model="vm.alias.filter" | |
55 | + allowed-entity-types="vm.allowedEntityTypes" | |
56 | + the-form="theForm"> | |
57 | + </tb-entity-filter> | |
58 | + <div class="tb-error-messages" ng-messages="theForm.$error" role="alert"> | |
59 | + <div translate ng-message="entityFilter" class="tb-error-message">alias.entity-filter-no-entity-matched</div> | |
60 | + </div> | |
61 | + </div> | |
62 | + </fieldset> | |
63 | + </div> | |
64 | + </md-dialog-content> | |
65 | + <md-dialog-actions layout="row"> | |
66 | + <span flex></span> | |
67 | + <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary"> | |
68 | + {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }} | |
69 | + </md-button> | |
70 | + <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button> | |
71 | + </md-dialog-actions> | |
72 | + </form> | |
73 | +</md-dialog> | ... | ... |
ui/src/app/entity/alias/entity-aliases.controller.js
renamed from
ui/src/app/entity/entity-aliases.controller.js
... | ... | @@ -15,22 +15,27 @@ |
15 | 15 | */ |
16 | 16 | import './entity-aliases.scss'; |
17 | 17 | |
18 | +/* eslint-disable import/no-unresolved, import/default */ | |
19 | + | |
20 | +import entityAliasDialogTemplate from './entity-alias-dialog.tpl.html'; | |
21 | + | |
22 | +/* eslint-enable import/no-unresolved, import/default */ | |
23 | + | |
18 | 24 | /*@ngInject*/ |
19 | 25 | export default function EntityAliasesController(utils, entityService, toast, $scope, $mdDialog, $document, $q, $translate, |
20 | 26 | types, config) { |
21 | 27 | |
22 | 28 | var vm = this; |
23 | 29 | |
24 | - vm.isSingleEntityAlias = config.isSingleEntityAlias; | |
25 | - vm.singleEntityAlias = config.singleEntityAlias; | |
30 | + vm.types = types; | |
26 | 31 | vm.entityAliases = []; |
27 | 32 | vm.title = config.customTitle ? config.customTitle : 'entity.aliases'; |
28 | 33 | vm.disableAdd = config.disableAdd; |
29 | 34 | vm.aliasToWidgetsMap = {}; |
30 | 35 | vm.allowedEntityTypes = config.allowedEntityTypes; |
31 | 36 | |
32 | - vm.onFilterEntityChanged = onFilterEntityChanged; | |
33 | 37 | vm.addAlias = addAlias; |
38 | + vm.editAlias = editAlias; | |
34 | 39 | vm.removeAlias = removeAlias; |
35 | 40 | |
36 | 41 | vm.cancel = cancel; |
... | ... | @@ -79,51 +84,61 @@ export default function EntityAliasesController(utils, entityService, toast, $sc |
79 | 84 | } |
80 | 85 | } |
81 | 86 | |
82 | - if (vm.isSingleEntityAlias) { | |
83 | - checkEntityAlias(vm.singleEntityAlias); | |
84 | - } | |
85 | - | |
86 | 87 | for (aliasId in config.entityAliases) { |
87 | 88 | var entityAlias = config.entityAliases[aliasId]; |
88 | - var result = {id: aliasId, alias: entityAlias.alias, entityType: entityAlias.entityType, entityFilter: entityAlias.entityFilter, changed: true}; | |
89 | - checkEntityAlias(result); | |
89 | + var filter = entityAlias.filter; | |
90 | + if (!filter) { | |
91 | + filter = { | |
92 | + resolveMultiple: false | |
93 | + }; | |
94 | + } | |
95 | + if (!filter.resolveMultiple) { | |
96 | + filter.resolveMultiple = false; | |
97 | + } | |
98 | + var result = {id: aliasId, alias: entityAlias.alias, filter: filter}; | |
90 | 99 | vm.entityAliases.push(result); |
91 | 100 | } |
92 | 101 | } |
93 | 102 | |
94 | - function checkEntityAlias(entityAlias) { | |
95 | - if (!entityAlias.entityType) { | |
96 | - entityAlias.entityType = types.entityType.device; | |
97 | - } | |
98 | - if (!entityAlias.entityFilter || entityAlias.entityFilter == null) { | |
99 | - entityAlias.entityFilter = { | |
100 | - useFilter: false, | |
101 | - entityNameFilter: '', | |
102 | - entityList: [], | |
103 | - }; | |
104 | - } | |
103 | + function addAlias($event) { | |
104 | + openAliasDialog($event); | |
105 | 105 | } |
106 | 106 | |
107 | - function onFilterEntityChanged(entity, entityAlias) { | |
108 | - if (entityAlias) { | |
109 | - if (!entityAlias.alias || entityAlias.alias.length == 0) { | |
110 | - entityAlias.changed = false; | |
111 | - } | |
112 | - if (!entityAlias.changed && entity && entityAlias.entityType) { | |
113 | - entityAlias.alias = entity.name; | |
114 | - } | |
115 | - } | |
107 | + function editAlias($event, entityAlias) { | |
108 | + openAliasDialog($event, entityAlias); | |
116 | 109 | } |
117 | 110 | |
118 | - function addAlias() { | |
119 | - var aliasId = 0; | |
120 | - for (var a in vm.entityAliases) { | |
121 | - aliasId = Math.max(vm.entityAliases[a].id, aliasId); | |
111 | + function openAliasDialog($event, entityAlias) { | |
112 | + var isAdd = entityAlias ? false : true; | |
113 | + var aliasIndex; | |
114 | + if (!isAdd) { | |
115 | + aliasIndex = vm.entityAliases.indexOf(entityAlias); | |
122 | 116 | } |
123 | - aliasId++; | |
124 | - var entityAlias = {id: aliasId, alias: '', entityType: types.entityType.device, | |
125 | - entityFilter: {useFilter: false, entityNameFilter: '', entityList: []}, changed: false}; | |
126 | - vm.entityAliases.push(entityAlias); | |
117 | + $mdDialog.show({ | |
118 | + controller: 'EntityAliasDialogController', | |
119 | + controllerAs: 'vm', | |
120 | + templateUrl: entityAliasDialogTemplate, | |
121 | + locals: { | |
122 | + isAdd: isAdd, | |
123 | + allowedEntityTypes: vm.allowedEntityTypes, | |
124 | + entityAliases: vm.entityAliases, | |
125 | + alias: isAdd ? null : angular.copy(entityAlias) | |
126 | + }, | |
127 | + parent: angular.element($document[0].body), | |
128 | + fullscreen: true, | |
129 | + skipHide: true, | |
130 | + targetEvent: $event | |
131 | + }).then(function (alias) { | |
132 | + if (isAdd) { | |
133 | + vm.entityAliases.push(alias); | |
134 | + } else { | |
135 | + vm.entityAliases[aliasIndex] = alias; | |
136 | + } | |
137 | + if ($scope.theForm) { | |
138 | + $scope.theForm.$setDirty(); | |
139 | + } | |
140 | + }, function () { | |
141 | + }); | |
127 | 142 | } |
128 | 143 | |
129 | 144 | function removeAlias($event, entityAlias) { |
... | ... | @@ -160,61 +175,36 @@ export default function EntityAliasesController(utils, entityService, toast, $sc |
160 | 175 | $mdDialog.cancel(); |
161 | 176 | } |
162 | 177 | |
163 | - function cleanupEntityFilter(entityFilter) { | |
164 | - if (entityFilter.useFilter) { | |
165 | - entityFilter.entityList = []; | |
166 | - } else { | |
167 | - entityFilter.entityNameFilter = ''; | |
168 | - } | |
169 | - return entityFilter; | |
170 | - } | |
171 | - | |
172 | 178 | function save() { |
173 | 179 | |
174 | 180 | var entityAliases = {}; |
175 | 181 | var uniqueAliasList = {}; |
176 | 182 | |
177 | 183 | var valid = true; |
178 | - var aliasId, maxAliasId; | |
179 | - var alias; | |
180 | - var i; | |
181 | - | |
182 | - if (vm.isSingleEntityAlias) { | |
183 | - maxAliasId = 0; | |
184 | - vm.singleEntityAlias.entityFilter = cleanupEntityFilter(vm.singleEntityAlias.entityFilter); | |
185 | - for (i = 0; i < vm.entityAliases.length; i ++) { | |
186 | - aliasId = vm.entityAliases[i].id; | |
187 | - alias = vm.entityAliases[i].alias; | |
188 | - if (alias === vm.singleEntityAlias.alias) { | |
189 | - valid = false; | |
190 | - break; | |
191 | - } | |
192 | - maxAliasId = Math.max(aliasId, maxAliasId); | |
193 | - } | |
194 | - maxAliasId++; | |
195 | - vm.singleEntityAlias.id = maxAliasId; | |
196 | - } else { | |
197 | - for (i = 0; i < vm.entityAliases.length; i++) { | |
198 | - aliasId = vm.entityAliases[i].id; | |
199 | - alias = vm.entityAliases[i].alias; | |
200 | - if (!uniqueAliasList[alias]) { | |
201 | - uniqueAliasList[alias] = alias; | |
202 | - entityAliases[aliasId] = {alias: alias, entityType: vm.entityAliases[i].entityType, entityFilter: cleanupEntityFilter(vm.entityAliases[i].entityFilter)}; | |
203 | - } else { | |
204 | - valid = false; | |
205 | - break; | |
206 | - } | |
184 | + var message, aliasId, alias, filter; | |
185 | + | |
186 | + for (var i = 0; i < vm.entityAliases.length; i++) { | |
187 | + aliasId = vm.entityAliases[i].id; | |
188 | + alias = vm.entityAliases[i].alias; | |
189 | + filter = vm.entityAliases[i].filter; | |
190 | + if (uniqueAliasList[alias]) { | |
191 | + valid = false; | |
192 | + message = $translate.instant('entity.duplicate-alias-error', {alias: alias}); | |
193 | + break; | |
194 | + } else if (!filter || !filter.type) { | |
195 | + valid = false; | |
196 | + message = $translate.instant('entity.missing-entity-filter-error', {alias: alias}); | |
197 | + break; | |
198 | + } else { | |
199 | + uniqueAliasList[alias] = alias; | |
200 | + entityAliases[aliasId] = {id: aliasId, alias: alias, filter: filter}; | |
207 | 201 | } |
208 | 202 | } |
209 | 203 | if (valid) { |
210 | 204 | $scope.theForm.$setPristine(); |
211 | - if (vm.isSingleEntityAlias) { | |
212 | - $mdDialog.hide(vm.singleEntityAlias); | |
213 | - } else { | |
214 | - $mdDialog.hide(entityAliases); | |
215 | - } | |
205 | + $mdDialog.hide(entityAliases); | |
216 | 206 | } else { |
217 | - toast.showError($translate.instant('entity.duplicate-alias-error', {alias: alias})); | |
207 | + toast.showError(message); | |
218 | 208 | } |
219 | 209 | } |
220 | 210 | ... | ... |
ui/src/app/entity/alias/entity-aliases.scss
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +.tb-aliases-dialog { | |
18 | + .md-dialog-content { | |
19 | + padding-bottom: 0px; | |
20 | + padding-top: 0px; | |
21 | + } | |
22 | + .tb-aliases-header { | |
23 | + min-height: 40px; | |
24 | + padding: 0 34px 0 34px; | |
25 | + margin: 5px; | |
26 | + .tb-header-label { | |
27 | + font-size: 14px; | |
28 | + color: rgba(0, 0, 0, 0.570588); | |
29 | + } | |
30 | + } | |
31 | + .tb-alias { | |
32 | + padding: 0 0 0 10px; | |
33 | + margin: 5px; | |
34 | + md-input-container { | |
35 | + margin: 0px; | |
36 | + } | |
37 | + .tb-resolve-multiple-switch { | |
38 | + padding-left: 10px; | |
39 | + .resolve-multiple-switch { | |
40 | + margin: 0; | |
41 | + } | |
42 | + } | |
43 | + .md-button { | |
44 | + &.md-icon-button { | |
45 | + margin: 0px; | |
46 | + } | |
47 | + } | |
48 | + } | |
49 | +} | ... | ... |
ui/src/app/entity/alias/entity-aliases.tpl.html
renamed from
ui/src/app/entity/entity-aliases.tpl.html
... | ... | @@ -19,7 +19,7 @@ |
19 | 19 | <form name="theForm" ng-submit="vm.save()"> |
20 | 20 | <md-toolbar> |
21 | 21 | <div class="md-toolbar-tools"> |
22 | - <h2>{{ vm.isSingleEntityAlias ? ('entity.configure-alias' | translate:vm.singleEntityAlias ) : (vm.title | translate) }}</h2> | |
22 | + <h2>{{ vm.title | translate }}</h2> | |
23 | 23 | <span flex></span> |
24 | 24 | <md-button class="md-icon-button" ng-click="vm.cancel()"> |
25 | 25 | <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> |
... | ... | @@ -28,74 +28,73 @@ |
28 | 28 | </md-toolbar> |
29 | 29 | <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear> |
30 | 30 | <span style="min-height: 5px;" flex="" ng-show="!loading"></span> |
31 | + <div class="tb-aliases-header" flex layout="row" layout-align="start center"> | |
32 | + <span flex="5"></span> | |
33 | + <div flex layout="row" layout-align="start center"> | |
34 | + <span class="tb-header-label" translate flex="20" style="min-width: 150px;">alias.name</span> | |
35 | + <span class="tb-header-label" translate flex="70" style="padding-left: 10px;">alias.entity-filter</span> | |
36 | + <span class="tb-header-label" translate flex="10" style="padding-left: 10px; min-width: 120px;">alias.resolve-multiple</span> | |
37 | + <span style="min-width: 80px;"></span> | |
38 | + </div> | |
39 | + </div> | |
40 | + <md-divider></md-divider> | |
31 | 41 | <md-dialog-content> |
32 | 42 | <div class="md-dialog-content"> |
33 | 43 | <fieldset ng-disabled="loading"> |
34 | - <div ng-show="vm.isSingleEntityAlias" layout="row"> | |
35 | - <tb-entity-type-select style="min-width: 100px;" | |
36 | - ng-model="vm.singleEntityAlias.entityType" | |
37 | - allowed-entity-types="vm.allowedEntityTypes"> | |
38 | - </tb-entity-type-select> | |
39 | - <tb-entity-filter flex entity-type="vm.singleEntityAlias.entityType" ng-model="vm.singleEntityAlias.entityFilter"> | |
40 | - </tb-entity-filter> | |
41 | - </div> | |
42 | - <div ng-show="!vm.isSingleEntityAlias" flex layout="row" layout-align="start center"> | |
43 | - <span flex="5"></span> | |
44 | - <div flex layout="row" layout-align="start center" | |
45 | - style="padding: 0 0 0 10px; margin: 5px;"> | |
46 | - <span translate flex="20" style="min-width: 100px;">entity.alias</span> | |
47 | - <span translate flex="20" style="min-width: 100px;">entity.type</span> | |
48 | - <span translate flex="60" style="min-width: 190px; padding-left: 10px;">entity.entities</span> | |
49 | - <span style="min-width: 40px;"></span> | |
50 | - </div> | |
51 | - </div> | |
52 | - <div ng-show="!vm.isSingleEntityAlias" style="max-height: 500px; overflow: auto; padding-bottom: 20px;"> | |
53 | - <div ng-form name="aliasForm" flex layout="row" layout-align="start center" ng-repeat="entityAlias in vm.entityAliases track by $index"> | |
54 | - <span flex="5">{{$index + 1}}.</span> | |
55 | - <div class="md-whiteframe-4dp tb-alias" flex layout="row" layout-align="start center"> | |
56 | - <md-input-container flex="20" style="min-width: 100px;" md-no-float class="md-block"> | |
57 | - <input required ng-change="entityAlias.changed=true" name="alias" placeholder="{{ 'entity.alias' | translate }}" ng-model="entityAlias.alias"> | |
58 | - <div ng-messages="aliasForm.alias.$error"> | |
59 | - <div translate ng-message="required">entity.alias-required</div> | |
60 | - </div> | |
61 | - </md-input-container> | |
62 | - <section flex="20" layout="column" style="min-width: 100px;" > | |
63 | - <tb-entity-type-select hide-label style="padding-left: 10px;" | |
64 | - ng-model="entityAlias.entityType" | |
65 | - allowed-entity-types="vm.allowedEntityTypes"> | |
66 | - </tb-entity-type-select> | |
67 | - </section> | |
68 | - <section flex="60" layout="column"> | |
69 | - <tb-entity-filter style="padding-left: 10px;" | |
70 | - entity-type="entityAlias.entityType" | |
71 | - ng-model="entityAlias.entityFilter" | |
72 | - on-matching-entity-change="vm.onFilterEntityChanged(entity, entityAlias)"> | |
73 | - </tb-entity-filter> | |
74 | - </section> | |
75 | - <md-button ng-disabled="loading" class="md-icon-button md-primary" style="min-width: 40px;" | |
76 | - ng-click="vm.removeAlias($event, entityAlias)" aria-label="{{ 'action.remove' | translate }}"> | |
77 | - <md-tooltip md-direction="top"> | |
78 | - {{ 'entity.remove-alias' | translate }} | |
79 | - </md-tooltip> | |
80 | - <md-icon aria-label="{{ 'action.delete' | translate }}" class="material-icons"> | |
81 | - close | |
82 | - </md-icon> | |
83 | - </md-button> | |
84 | - </div> | |
85 | - </div> | |
86 | - </div> | |
87 | - <div ng-show="!vm.isSingleEntityAlias && !vm.disableAdd" style="padding-bottom: 10px;"> | |
88 | - <md-button ng-disabled="loading" class="md-primary md-raised" ng-click="vm.addAlias($event)" aria-label="{{ 'action.add' | translate }}"> | |
89 | - <md-tooltip md-direction="top"> | |
90 | - {{ 'entity.add-alias' | translate }} | |
91 | - </md-tooltip> | |
92 | - <span translate>action.add</span> | |
93 | - </md-button> | |
44 | + <div ng-form name="aliasForm" flex layout="row" layout-align="start center" ng-repeat="entityAlias in vm.entityAliases track by $index"> | |
45 | + <span flex="5">{{$index + 1}}.</span> | |
46 | + <di class="md-whiteframe-4dp tb-alias" flex layout="row" layout-align="start center"> | |
47 | + <md-input-container flex="20" style="min-width: 150px;" md-no-float class="md-block"> | |
48 | + <input required name="alias" placeholder="{{ 'entity.alias' | translate }}" ng-model="entityAlias.alias"> | |
49 | + <div ng-messages="aliasForm.alias.$error"> | |
50 | + <div translate ng-message="required">entity.alias-required</div> | |
51 | + </div> | |
52 | + </md-input-container> | |
53 | + <tb-entity-filter-view | |
54 | + flex="70" style="padding-left: 10px;" | |
55 | + ng-model="entityAlias.filter"> | |
56 | + </tb-entity-filter-view> | |
57 | + <section flex="10" style="padding-left: 10px; min-width: 120px;" | |
58 | + class="tb-resolve-multiple-switch" | |
59 | + layout="column" | |
60 | + layout-align="center center"> | |
61 | + <md-switch class="resolve-multiple-switch" | |
62 | + ng-model="entityAlias.filter.resolveMultiple" | |
63 | + aria-label="resolve-multiple-switcher"> | |
64 | + </md-switch> | |
65 | + </section> | |
66 | + <md-button ng-disabled="loading" class="md-icon-button md-primary" style="min-width: 40px;" | |
67 | + ng-click="vm.editAlias($event, entityAlias)" aria-label="{{ 'action.edit' | translate }}"> | |
68 | + <md-tooltip md-direction="top"> | |
69 | + {{ 'alias.edit' | translate }} | |
70 | + </md-tooltip> | |
71 | + <md-icon aria-label="{{ 'alias.edit' | translate }}" class="material-icons"> | |
72 | + edit | |
73 | + </md-icon> | |
74 | + </md-button> | |
75 | + <md-button ng-disabled="loading" class="md-icon-button md-primary" style="min-width: 40px;" | |
76 | + ng-click="vm.removeAlias($event, entityAlias)" aria-label="{{ 'action.remove' | translate }}"> | |
77 | + <md-tooltip md-direction="top"> | |
78 | + {{ 'entity.remove-alias' | translate }} | |
79 | + </md-tooltip> | |
80 | + <md-icon aria-label="{{ 'action.delete' | translate }}" class="material-icons"> | |
81 | + close | |
82 | + </md-icon> | |
83 | + </md-button> | |
84 | + </di> | |
94 | 85 | </div> |
95 | 86 | </fieldset> |
96 | 87 | </div> |
97 | 88 | </md-dialog-content> |
98 | 89 | <md-dialog-actions layout="row"> |
90 | + <md-button ng-show="!vm.disableAdd" ng-disabled="loading" class="md-primary md-raised" | |
91 | + ng-click="vm.addAlias($event)" | |
92 | + aria-label="{{ 'alias.add' | translate }}"> | |
93 | + <md-tooltip md-direction="top"> | |
94 | + {{ 'alias.add' | translate }} | |
95 | + </md-tooltip> | |
96 | + <span translate>alias.add</span> | |
97 | + </md-button> | |
99 | 98 | <span flex></span> |
100 | 99 | <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary"> |
101 | 100 | {{ 'action.save' | translate }} | ... | ... |
... | ... | @@ -22,7 +22,8 @@ import selectTargetLayoutTemplate from '../../dashboard/layouts/select-target-la |
22 | 22 | /* eslint-enable import/no-unresolved, import/default */ |
23 | 23 | |
24 | 24 | /*@ngInject*/ |
25 | -export default function AddWidgetToDashboardDialogController($scope, $mdDialog, $state, $q, $document, itembuffer, dashboardService, entityId, entityType, entityName, widget) { | |
25 | +export default function AddWidgetToDashboardDialogController($scope, $mdDialog, $state, $q, $document, dashboardUtils, | |
26 | + types, itembuffer, dashboardService, entityId, entityType, entityName, widget) { | |
26 | 27 | |
27 | 28 | var vm = this; |
28 | 29 | |
... | ... | @@ -125,13 +126,8 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog, |
125 | 126 | targetDeviceAliases: {} |
126 | 127 | }; |
127 | 128 | aliasesInfo.datasourceAliases[0] = { |
128 | - aliasName: entityName, | |
129 | - entityType: entityType, | |
130 | - entityFilter: { | |
131 | - useFilter: false, | |
132 | - entityNameFilter: '', | |
133 | - entityList: [entityId] | |
134 | - } | |
129 | + alias: entityName, | |
130 | + filter: dashboardUtils.createSingleEntityFilter(entityType, entityId) | |
135 | 131 | }; |
136 | 132 | itembuffer.addWidgetToDashboard(theDashboard, targetState, targetLayout, vm.widget, aliasesInfo, null, 48, null, -1, -1).then( |
137 | 133 | function(theDashboard) { | ... | ... |
... | ... | @@ -26,10 +26,12 @@ import editAttributeValueTemplate from './edit-attribute-value.tpl.html'; |
26 | 26 | /* eslint-enable import/no-unresolved, import/default */ |
27 | 27 | |
28 | 28 | import EditAttributeValueController from './edit-attribute-value.controller'; |
29 | +import AliasController from '../../api/alias-controller'; | |
29 | 30 | |
30 | 31 | /*@ngInject*/ |
31 | 32 | export default function AttributeTableDirective($compile, $templateCache, $rootScope, $q, $mdEditDialog, $mdDialog, |
32 | - $document, $translate, $filter, utils, types, dashboardService, attributeService, widgetService) { | |
33 | + $mdUtil, $document, $translate, $filter, utils, types, dashboardUtils, | |
34 | + dashboardService, entityService, attributeService, widgetService) { | |
33 | 35 | |
34 | 36 | var linker = function (scope, element, attrs) { |
35 | 37 | |
... | ... | @@ -246,15 +248,19 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS |
246 | 248 | } |
247 | 249 | |
248 | 250 | scope.nextWidget = function() { |
249 | - if (scope.widgetsCarousel.index < scope.widgetsList.length-1) { | |
250 | - scope.widgetsCarousel.index++; | |
251 | - } | |
251 | + $mdUtil.nextTick(function () { | |
252 | + if (scope.widgetsCarousel.index < scope.widgetsList.length - 1) { | |
253 | + scope.widgetsCarousel.index++; | |
254 | + } | |
255 | + }); | |
252 | 256 | } |
253 | 257 | |
254 | 258 | scope.prevWidget = function() { |
255 | - if (scope.widgetsCarousel.index > 0) { | |
256 | - scope.widgetsCarousel.index--; | |
257 | - } | |
259 | + $mdUtil.nextTick(function () { | |
260 | + if (scope.widgetsCarousel.index > 0) { | |
261 | + scope.widgetsCarousel.index--; | |
262 | + } | |
263 | + }); | |
258 | 264 | } |
259 | 265 | |
260 | 266 | scope.enterWidgetMode = function() { |
... | ... | @@ -281,23 +287,28 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS |
281 | 287 | scope.firstBundle = true; |
282 | 288 | scope.selectedWidgetsBundleAlias = types.systemBundleAlias.cards; |
283 | 289 | |
284 | - scope.aliasesInfo = { | |
285 | - entityAliases: { | |
286 | - '1': {alias: scope.entityName, entityType: scope.entityType, entityId: scope.entityId} | |
287 | - }, | |
288 | - entityAliasesInfo: { | |
289 | - '1': [ | |
290 | - {name: scope.entityName, entityType: scope.entityType, id: scope.entityId} | |
291 | - ] | |
290 | + var entityAlias = { | |
291 | + id: utils.guid(), | |
292 | + alias: scope.entityName, | |
293 | + filter: dashboardUtils.createSingleEntityFilter(scope.entityType, scope.entityId) | |
294 | + }; | |
295 | + var entitiAliases = {}; | |
296 | + entitiAliases[entityAlias.id] = entityAlias; | |
297 | + | |
298 | + var stateController = { | |
299 | + getStateParams: function() { | |
300 | + return {}; | |
292 | 301 | } |
293 | 302 | }; |
303 | + scope.aliasController = new AliasController(scope, $q, $filter, utils, | |
304 | + types, entityService, stateController, entitiAliases); | |
294 | 305 | |
295 | 306 | var dataKeyType = scope.attributeScope === types.latestTelemetry ? |
296 | 307 | types.dataKeyType.timeseries : types.dataKeyType.attribute; |
297 | 308 | |
298 | 309 | var datasource = { |
299 | 310 | type: types.datasourceType.entity, |
300 | - entityAliasId: '1', | |
311 | + entityAliasId: entityAlias.id, | |
301 | 312 | dataKeys: [] |
302 | 313 | } |
303 | 314 | var i = 0; | ... | ... |
... | ... | @@ -156,7 +156,7 @@ |
156 | 156 | rn-swipe-disabled="true"> |
157 | 157 | <li ng-repeat="widgets in widgetsList"> |
158 | 158 | <tb-dashboard |
159 | - aliases-info="aliasesInfo" | |
159 | + alias-controller="aliasController" | |
160 | 160 | widgets="widgets" |
161 | 161 | get-st-diff="getServerTimeDiff()" |
162 | 162 | columns="20" | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +/* eslint-disable import/no-unresolved, import/default */ | |
18 | + | |
19 | +import entityFilterViewTemplate from './entity-filter-view.tpl.html'; | |
20 | + | |
21 | +/* eslint-enable import/no-unresolved, import/default */ | |
22 | + | |
23 | +import './entity-filter-view.scss'; | |
24 | + | |
25 | +/*@ngInject*/ | |
26 | +export default function EntityFilterViewDirective($compile, $templateCache, $q, $document, $mdDialog, $translate, types/*, entityService*/) { | |
27 | + | |
28 | + var linker = function (scope, element, attrs, ngModelCtrl) { | |
29 | + | |
30 | + var template = $templateCache.get(entityFilterViewTemplate); | |
31 | + element.html(template); | |
32 | + | |
33 | + scope.ngModelCtrl = ngModelCtrl; | |
34 | + scope.types = types; | |
35 | + scope.filterDisplayValue = ''; | |
36 | + | |
37 | + scope.$watch('filter', function () { | |
38 | + scope.updateDisplayValue(); | |
39 | + }); | |
40 | + | |
41 | + scope.updateDisplayValue = function() { | |
42 | + if (scope.filter && scope.filter.type) { | |
43 | + var entityType; | |
44 | + var prefix; | |
45 | + switch (scope.filter.type) { | |
46 | + case types.aliasFilterType.entityList.value: | |
47 | + entityType = scope.filter.entityType; | |
48 | + var count = scope.filter.entityList.length; | |
49 | + scope.filterDisplayValue = $translate.instant(types.entityTypeTranslations[entityType].list, {count: count}, 'messageformat'); | |
50 | + break; | |
51 | + case types.aliasFilterType.entityName.value: | |
52 | + entityType = scope.filter.entityType; | |
53 | + prefix = scope.filter.entityNameFilter; | |
54 | + scope.filterDisplayValue = $translate.instant(types.entityTypeTranslations[entityType].nameStartsWith, {prefix: prefix}); | |
55 | + break; | |
56 | + case types.aliasFilterType.stateEntity.value: | |
57 | + scope.filterDisplayValue = $translate.instant('alias.filter-type-state-entity-description'); | |
58 | + break; | |
59 | + case types.aliasFilterType.assetType.value: | |
60 | + var assetType = scope.filter.assetType; | |
61 | + prefix = scope.filter.assetNameFilter; | |
62 | + if (prefix && prefix.length) { | |
63 | + scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-type-and-name-description', {assetType: assetType, prefix: prefix}); | |
64 | + } else { | |
65 | + scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-type-description', {assetType: assetType}); | |
66 | + } | |
67 | + break; | |
68 | + case types.aliasFilterType.deviceType.value: | |
69 | + var deviceType = scope.filter.deviceType; | |
70 | + prefix = scope.filter.deviceNameFilter; | |
71 | + if (prefix && prefix.length) { | |
72 | + scope.filterDisplayValue = $translate.instant('alias.filter-type-device-type-and-name-description', {deviceType: deviceType, prefix: prefix}); | |
73 | + } else { | |
74 | + scope.filterDisplayValue = $translate.instant('alias.filter-type-device-type-description', {deviceType: deviceType}); | |
75 | + } | |
76 | + break; | |
77 | + case types.aliasFilterType.relationsQuery.value: | |
78 | + var rootEntityText; | |
79 | + var directionText; | |
80 | + var allEntitiesText = $translate.instant('alias.all-entities'); | |
81 | + var anyRelationText = $translate.instant('alias.any-relation'); | |
82 | + if (scope.filter.rootStateEntity) { | |
83 | + rootEntityText = $translate.instant('alias.state-entity'); | |
84 | + } else { | |
85 | + rootEntityText = $translate.instant(types.entityTypeTranslations[scope.filter.rootEntity.entityType].type); | |
86 | + } | |
87 | + directionText = $translate.instant('relation.direction-type.' + scope.filter.direction); | |
88 | + var relationFilters = scope.filter.filters; | |
89 | + if (relationFilters && relationFilters.length) { | |
90 | + var relationFiltersDisplayValues = []; | |
91 | + relationFilters.forEach(function(relationFilter) { | |
92 | + var entitiesText; | |
93 | + if (relationFilter.entityTypes && relationFilter.entityTypes.length) { | |
94 | + var entitiesNamesList = []; | |
95 | + relationFilter.entityTypes.forEach(function(entityType) { | |
96 | + entitiesNamesList.push( | |
97 | + $translate.instant(types.entityTypeTranslations[entityType].typePlural) | |
98 | + ); | |
99 | + }); | |
100 | + entitiesText = entitiesNamesList.join(', '); | |
101 | + } else { | |
102 | + entitiesText = allEntitiesText; | |
103 | + } | |
104 | + var relationTypeText; | |
105 | + if (relationFilter.relationType && relationFilter.relationType.length) { | |
106 | + relationTypeText = "'" + relationFilter.relationType + "'"; | |
107 | + } else { | |
108 | + relationTypeText = anyRelationText; | |
109 | + } | |
110 | + var relationFilterDisplayValue = $translate.instant('alias.filter-type-relations-query-description', | |
111 | + { | |
112 | + entities: entitiesText, | |
113 | + relationType: relationTypeText, | |
114 | + direction: directionText, | |
115 | + rootEntity: rootEntityText | |
116 | + } | |
117 | + ); | |
118 | + relationFiltersDisplayValues.push(relationFilterDisplayValue); | |
119 | + }); | |
120 | + scope.filterDisplayValue = relationFiltersDisplayValues.join(', '); | |
121 | + } else { | |
122 | + scope.filterDisplayValue = $translate.instant('alias.filter-type-relations-query-description', | |
123 | + { | |
124 | + entities: allEntitiesText, | |
125 | + relationType: anyRelationText, | |
126 | + direction: directionText, | |
127 | + rootEntity: rootEntityText | |
128 | + } | |
129 | + ); | |
130 | + } | |
131 | + break; | |
132 | + case types.aliasFilterType.assetSearchQuery.value: | |
133 | + case types.aliasFilterType.deviceSearchQuery.value: | |
134 | + allEntitiesText = $translate.instant('alias.all-entities'); | |
135 | + anyRelationText = $translate.instant('alias.any-relation'); | |
136 | + if (scope.filter.rootStateEntity) { | |
137 | + rootEntityText = $translate.instant('alias.state-entity'); | |
138 | + } else { | |
139 | + rootEntityText = $translate.instant(types.entityTypeTranslations[scope.filter.rootEntity.entityType].type); | |
140 | + } | |
141 | + directionText = $translate.instant('relation.direction-type.' + scope.filter.direction); | |
142 | + var relationTypeText; | |
143 | + if (scope.filter.relationType && scope.filter.relationType.length) { | |
144 | + relationTypeText = "'" + scope.filter.relationType + "'"; | |
145 | + } else { | |
146 | + relationTypeText = anyRelationText; | |
147 | + } | |
148 | + | |
149 | + var translationValues = { | |
150 | + relationType: relationTypeText, | |
151 | + direction: directionText, | |
152 | + rootEntity: rootEntityText | |
153 | + } | |
154 | + | |
155 | + if (scope.filter.type == types.aliasFilterType.assetSearchQuery.value) { | |
156 | + var assetTypesQuoted = []; | |
157 | + scope.filter.assetTypes.forEach(function(assetType) { | |
158 | + assetTypesQuoted.push("'"+assetType+"'"); | |
159 | + }); | |
160 | + var assetTypesText = assetTypesQuoted.join(', '); | |
161 | + translationValues.assetTypes = assetTypesText; | |
162 | + scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-search-query-description', | |
163 | + translationValues | |
164 | + ); | |
165 | + } else { | |
166 | + var deviceTypesQuoted = []; | |
167 | + scope.filter.deviceTypes.forEach(function(deviceType) { | |
168 | + deviceTypesQuoted.push("'"+deviceType+"'"); | |
169 | + }); | |
170 | + var deviceTypesText = deviceTypesQuoted.join(', '); | |
171 | + translationValues.deviceTypes = deviceTypesText; | |
172 | + scope.filterDisplayValue = $translate.instant('alias.filter-type-device-search-query-description', | |
173 | + translationValues | |
174 | + ); | |
175 | + } | |
176 | + break; | |
177 | + default: | |
178 | + scope.filterDisplayValue = scope.filter.type; | |
179 | + break; | |
180 | + } | |
181 | + } else { | |
182 | + scope.filterDisplayValue = ''; | |
183 | + } | |
184 | + } | |
185 | + | |
186 | + ngModelCtrl.$render = function () { | |
187 | + if (ngModelCtrl.$viewValue) { | |
188 | + scope.filter = ngModelCtrl.$viewValue; | |
189 | + } else { | |
190 | + scope.filter = null; | |
191 | + } | |
192 | + } | |
193 | + | |
194 | + $compile(element.contents())(scope); | |
195 | + | |
196 | + } | |
197 | + | |
198 | + return { | |
199 | + restrict: "E", | |
200 | + require: "^ngModel", | |
201 | + link: linker, | |
202 | + scope: true | |
203 | + }; | |
204 | + | |
205 | +} | ... | ... |
ui/src/app/entity/entity-filter-view.scss
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +.tb-entity-filter-view { | |
18 | + .entity-filter-empty { | |
19 | + color: rgba(221, 44, 0, 0.87); | |
20 | + font-size: 14px; | |
21 | + line-height: 16px; | |
22 | + } | |
23 | + .entity-filter-type { | |
24 | + font-size: 14px; | |
25 | + line-height: 16px; | |
26 | + color: rgba(0, 0, 0, 0.570588); | |
27 | + } | |
28 | + .entity-filter-value { | |
29 | + font-size: 14px; | |
30 | + line-height: 16px; | |
31 | + color: rgba(0, 0, 0, 0.570588); | |
32 | + } | |
33 | +} | |
\ No newline at end of file | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2017 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | + | |
19 | +<div layout='column' class="tb-entity-filter-view"> | |
20 | + <div ng-if="!filter || !filter.type" class="entity-filter-empty" translate>alias.no-entity-filter-specified</div> | |
21 | + <div ng-if="filter && filter.type" layout="column"> | |
22 | + <div class="entity-filter-value">{{ filterDisplayValue }}</div> | |
23 | + </div> | |
24 | +</div> | ... | ... |
... | ... | @@ -23,7 +23,7 @@ import entityFilterTemplate from './entity-filter.tpl.html'; |
23 | 23 | import './entity-filter.scss'; |
24 | 24 | |
25 | 25 | /*@ngInject*/ |
26 | -export default function EntityFilterDirective($compile, $templateCache, $q, entityService) { | |
26 | +export default function EntityFilterDirective($compile, $templateCache, $q, $document, $mdDialog, types, entityService) { | |
27 | 27 | |
28 | 28 | var linker = function (scope, element, attrs, ngModelCtrl) { |
29 | 29 | |
... | ... | @@ -31,181 +31,76 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti |
31 | 31 | element.html(template); |
32 | 32 | |
33 | 33 | scope.ngModelCtrl = ngModelCtrl; |
34 | + scope.types = types; | |
35 | + scope.aliasFilterTypes = entityService.getAliasFilterTypesByEntityTypes(scope.allowedEntityTypes); | |
34 | 36 | |
35 | - scope.fetchEntities = function(searchText, limit) { | |
36 | - var deferred = $q.defer(); | |
37 | - entityService.getEntitiesByNameFilter(scope.entityType, searchText, limit).then(function success(result) { | |
38 | - if (result) { | |
39 | - deferred.resolve(result); | |
40 | - } else { | |
41 | - deferred.resolve([]); | |
42 | - } | |
43 | - }, function fail() { | |
44 | - deferred.reject(); | |
45 | - }); | |
46 | - return deferred.promise; | |
47 | - } | |
48 | - | |
49 | - scope.updateValidity = function() { | |
50 | - if (ngModelCtrl.$viewValue) { | |
51 | - var value = ngModelCtrl.$viewValue; | |
52 | - var valid; | |
53 | - if (value.useFilter) { | |
54 | - ngModelCtrl.$setValidity('entityList', true); | |
55 | - if (angular.isDefined(value.entityNameFilter) && value.entityNameFilter.length > 0) { | |
56 | - ngModelCtrl.$setValidity('entityNameFilter', true); | |
57 | - valid = angular.isDefined(scope.model.matchingFilterEntity) && scope.model.matchingFilterEntity != null; | |
58 | - ngModelCtrl.$setValidity('entityNameFilterEntityMatch', valid); | |
59 | - } else { | |
60 | - ngModelCtrl.$setValidity('entityNameFilter', false); | |
61 | - } | |
62 | - } else { | |
63 | - ngModelCtrl.$setValidity('entityNameFilter', true); | |
64 | - ngModelCtrl.$setValidity('entityNameFilterDeviceMatch', true); | |
65 | - valid = angular.isDefined(value.entityList) && value.entityList.length > 0; | |
66 | - ngModelCtrl.$setValidity('entityList', valid); | |
67 | - } | |
68 | - } | |
69 | - } | |
70 | - | |
71 | - ngModelCtrl.$render = function () { | |
72 | - destroyWatchers(); | |
73 | - scope.model = { | |
74 | - useFilter: false, | |
75 | - entityList: [], | |
76 | - entityNameFilter: '' | |
37 | + scope.$watch('filter.type', function (newType, prevType) { | |
38 | + if (newType && newType != prevType) { | |
39 | + updateFilter(); | |
77 | 40 | } |
78 | - if (ngModelCtrl.$viewValue) { | |
79 | - var value = ngModelCtrl.$viewValue; | |
80 | - var model = scope.model; | |
81 | - model.useFilter = value.useFilter === true ? true: false; | |
82 | - model.entityList = []; | |
83 | - model.entityNameFilter = value.entityNameFilter || ''; | |
84 | - processEntityNameFilter(model.entityNameFilter).then( | |
85 | - function(entity) { | |
86 | - scope.model.matchingFilterEntity = entity; | |
87 | - if (value.entityList && value.entityList.length > 0) { | |
88 | - entityService.getEntities(scope.entityType, value.entityList).then(function (entities) { | |
89 | - model.entityList = entities; | |
90 | - updateMatchingEntity(); | |
91 | - initWatchers(); | |
92 | - }); | |
93 | - } else { | |
94 | - updateMatchingEntity(); | |
95 | - initWatchers(); | |
96 | - } | |
41 | + }); | |
42 | + | |
43 | + function updateFilter() { | |
44 | + var filter = {}; | |
45 | + filter.type = scope.filter.type; | |
46 | + filter.resolveMultiple = scope.filter.resolveMultiple; | |
47 | + switch (filter.type) { | |
48 | + case types.aliasFilterType.entityList.value: | |
49 | + filter.entityType = null; | |
50 | + filter.entityList = []; | |
51 | + break; | |
52 | + case types.aliasFilterType.entityName.value: | |
53 | + filter.entityType = null; | |
54 | + filter.entityNameFilter = ''; | |
55 | + break; | |
56 | + case types.aliasFilterType.stateEntity.value: | |
57 | + break; | |
58 | + case types.aliasFilterType.assetType.value: | |
59 | + filter.assetType = null; | |
60 | + filter.assetNameFilter = ''; | |
61 | + break; | |
62 | + case types.aliasFilterType.deviceType.value: | |
63 | + filter.deviceType = null; | |
64 | + filter.deviceNameFilter = ''; | |
65 | + break; | |
66 | + case types.aliasFilterType.relationsQuery.value: | |
67 | + case types.aliasFilterType.assetSearchQuery.value: | |
68 | + case types.aliasFilterType.deviceSearchQuery.value: | |
69 | + filter.rootStateEntity = false; | |
70 | + filter.rootEntity = null; | |
71 | + filter.direction = types.entitySearchDirection.from; | |
72 | + filter.maxLevel = 1; | |
73 | + if (filter.type === types.aliasFilterType.relationsQuery.value) { | |
74 | + filter.filters = []; | |
75 | + } else if (filter.type === types.aliasFilterType.assetSearchQuery.value) { | |
76 | + filter.relationType = null; | |
77 | + filter.assetTypes = []; | |
78 | + } else if (filter.type === types.aliasFilterType.deviceSearchQuery.value) { | |
79 | + filter.relationType = null; | |
80 | + filter.deviceTypes = []; | |
97 | 81 | } |
98 | - ) | |
82 | + break; | |
99 | 83 | } |
84 | + scope.filter = filter; | |
100 | 85 | } |
101 | 86 | |
102 | - function updateMatchingEntity() { | |
103 | - if (scope.model.useFilter) { | |
104 | - scope.model.matchingEntity = scope.model.matchingFilterEntity; | |
105 | - } else { | |
106 | - if (scope.model.entityList && scope.model.entityList.length > 0) { | |
107 | - scope.model.matchingEntity = scope.model.entityList[0]; | |
108 | - } else { | |
109 | - scope.model.matchingEntity = null; | |
110 | - } | |
111 | - } | |
112 | - } | |
113 | - | |
114 | - function processEntityNameFilter(entityNameFilter) { | |
115 | - var deferred = $q.defer(); | |
116 | - if (angular.isDefined(entityNameFilter) && entityNameFilter.length > 0) { | |
117 | - scope.fetchEntities(entityNameFilter, 1).then(function (entities) { | |
118 | - if (entities && entities.length > 0) { | |
119 | - deferred.resolve(entities[0]); | |
120 | - } else { | |
121 | - deferred.resolve(null); | |
122 | - } | |
123 | - }); | |
124 | - } else { | |
125 | - deferred.resolve(null); | |
126 | - } | |
127 | - return deferred.promise; | |
128 | - } | |
87 | + scope.$watch('filter', function () { | |
88 | + scope.updateView(); | |
89 | + }); | |
129 | 90 | |
130 | - function destroyWatchers() { | |
131 | - if (scope.entityTypeDeregistration) { | |
132 | - scope.entityTypeDeregistration(); | |
133 | - scope.entityTypeDeregistration = null; | |
134 | - } | |
135 | - if (scope.entityListDeregistration) { | |
136 | - scope.entityListDeregistration(); | |
137 | - scope.entityListDeregistration = null; | |
138 | - } | |
139 | - if (scope.useFilterDeregistration) { | |
140 | - scope.useFilterDeregistration(); | |
141 | - scope.useFilterDeregistration = null; | |
142 | - } | |
143 | - if (scope.entityNameFilterDeregistration) { | |
144 | - scope.entityNameFilterDeregistration(); | |
145 | - scope.entityNameFilterDeregistration = null; | |
146 | - } | |
147 | - if (scope.matchingEntityDeregistration) { | |
148 | - scope.matchingEntityDeregistration(); | |
149 | - scope.matchingEntityDeregistration = null; | |
150 | - } | |
91 | + scope.updateView = function() { | |
92 | + ngModelCtrl.$setViewValue(scope.filter); | |
151 | 93 | } |
152 | 94 | |
153 | - function initWatchers() { | |
154 | - | |
155 | - scope.entityTypeDeregistration = scope.$watch('entityType', function (newEntityType, prevEntityType) { | |
156 | - if (!angular.equals(newEntityType, prevEntityType)) { | |
157 | - scope.model.entityList = []; | |
158 | - scope.model.entityNameFilter = ''; | |
159 | - } | |
160 | - }); | |
161 | - | |
162 | - scope.entityListDeregistration = scope.$watch('model.entityList', function () { | |
163 | - if (ngModelCtrl.$viewValue) { | |
164 | - var value = ngModelCtrl.$viewValue; | |
165 | - value.entityList = []; | |
166 | - if (scope.model.entityList && scope.model.entityList.length > 0) { | |
167 | - for (var i=0;i<scope.model.entityList.length;i++) { | |
168 | - value.entityList.push(scope.model.entityList[i].id.id); | |
169 | - } | |
170 | - } | |
171 | - updateMatchingEntity(); | |
172 | - ngModelCtrl.$setViewValue(value); | |
173 | - scope.updateValidity(); | |
174 | - } | |
175 | - }, true); | |
176 | - scope.useFilterDeregistration = scope.$watch('model.useFilter', function () { | |
177 | - if (ngModelCtrl.$viewValue) { | |
178 | - var value = ngModelCtrl.$viewValue; | |
179 | - value.useFilter = scope.model.useFilter; | |
180 | - updateMatchingEntity(); | |
181 | - ngModelCtrl.$setViewValue(value); | |
182 | - scope.updateValidity(); | |
183 | - } | |
184 | - }); | |
185 | - scope.entityNameFilterDeregistration = scope.$watch('model.entityNameFilter', function (newNameFilter, prevNameFilter) { | |
186 | - if (ngModelCtrl.$viewValue) { | |
187 | - if (!angular.equals(newNameFilter, prevNameFilter)) { | |
188 | - var value = ngModelCtrl.$viewValue; | |
189 | - value.entityNameFilter = scope.model.entityNameFilter; | |
190 | - processEntityNameFilter(value.entityNameFilter).then( | |
191 | - function(entity) { | |
192 | - scope.model.matchingFilterEntity = entity; | |
193 | - updateMatchingEntity(); | |
194 | - ngModelCtrl.$setViewValue(value); | |
195 | - scope.updateValidity(); | |
196 | - } | |
197 | - ); | |
198 | - } | |
199 | - } | |
200 | - }); | |
201 | - | |
202 | - scope.matchingEntityDeregistration = scope.$watch('model.matchingEntity', function (newMatchingEntity, prevMatchingEntity) { | |
203 | - if (!angular.equals(newMatchingEntity, prevMatchingEntity)) { | |
204 | - if (scope.onMatchingEntityChange) { | |
205 | - scope.onMatchingEntityChange({entity: newMatchingEntity}); | |
206 | - } | |
95 | + ngModelCtrl.$render = function () { | |
96 | + if (ngModelCtrl.$viewValue) { | |
97 | + scope.filter = ngModelCtrl.$viewValue; | |
98 | + } else { | |
99 | + scope.filter = { | |
100 | + type: null, | |
101 | + resolveMultiple: false | |
207 | 102 | } |
208 | - }); | |
103 | + } | |
209 | 104 | } |
210 | 105 | |
211 | 106 | $compile(element.contents())(scope); |
... | ... | @@ -217,9 +112,8 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti |
217 | 112 | require: "^ngModel", |
218 | 113 | link: linker, |
219 | 114 | scope: { |
220 | - entityType: '=', | |
221 | - isEdit: '=', | |
222 | - onMatchingEntityChange: '&' | |
115 | + theForm: '=', | |
116 | + allowedEntityTypes: '=?' | |
223 | 117 | } |
224 | 118 | }; |
225 | 119 | ... | ... |
... | ... | @@ -13,33 +13,24 @@ |
13 | 13 | * See the License for the specific language governing permissions and |
14 | 14 | * limitations under the License. |
15 | 15 | */ |
16 | + | |
16 | 17 | .tb-entity-filter { |
17 | - #entity_list_chips { | |
18 | - .md-chips { | |
19 | - padding-bottom: 1px; | |
18 | + | |
19 | + #relationsQueryFilter { | |
20 | + padding-top: 20px; | |
21 | + tb-entity-select { | |
22 | + min-height: 92px; | |
20 | 23 | } |
21 | 24 | } |
22 | - .entity-name-filter-input { | |
23 | - margin-top: 10px; | |
24 | - margin-bottom: 0px; | |
25 | - .md-errors-spacer { | |
26 | - min-height: 0px; | |
27 | - } | |
28 | - } | |
29 | - .tb-filter-switch { | |
25 | + | |
26 | + .tb-root-state-entity-switch { | |
30 | 27 | padding-left: 10px; |
31 | - .filter-switch { | |
28 | + .root-state-entity-switch { | |
32 | 29 | margin: 0; |
33 | 30 | } |
34 | - .filter-label { | |
31 | + .root-state-entity-label { | |
35 | 32 | margin: 5px 0; |
36 | 33 | } |
37 | 34 | } |
38 | - .tb-error-messages { | |
39 | - margin-top: -11px; | |
40 | - height: 35px; | |
41 | - .tb-error-message { | |
42 | - padding-left: 1px; | |
43 | - } | |
44 | - } | |
35 | + | |
45 | 36 | } |
\ No newline at end of file | ... | ... |
... | ... | @@ -15,53 +15,219 @@ |
15 | 15 | limitations under the License. |
16 | 16 | |
17 | 17 | --> |
18 | -<section layout='column' class="tb-entity-filter"> | |
19 | - <section layout='row'> | |
20 | - <section layout="column" flex ng-show="!model.useFilter"> | |
21 | - <md-chips flex | |
22 | - id="entity_list_chips" | |
23 | - ng-required="!useFilter" | |
24 | - ng-model="model.entityList" md-autocomplete-snap | |
25 | - md-require-match="true"> | |
26 | - <md-autocomplete | |
27 | - md-no-cache="true" | |
28 | - id="entity" | |
29 | - md-selected-item="selectedEntity" | |
30 | - md-search-text="entitySearchText" | |
31 | - md-items="item in fetchEntities(entitySearchText, 10)" | |
32 | - md-item-text="item.name" | |
33 | - md-min-length="0" | |
34 | - placeholder="{{ 'entity.entity-list' | translate }}"> | |
35 | - <md-item-template> | |
36 | - <span md-highlight-text="entitySearchText" md-highlight-flags="^i">{{item.name}}</span> | |
37 | - </md-item-template> | |
38 | - <md-not-found> | |
39 | - <span translate translate-values='{ entity: entitySearchText }'>entity.no-entities-matching</span> | |
40 | - </md-not-found> | |
41 | - </md-autocomplete> | |
42 | - <md-chip-template> | |
43 | - <span> | |
44 | - <strong>{{$chip.name}}</strong> | |
45 | - </span> | |
46 | - </md-chip-template> | |
47 | - </md-chips> | |
48 | - </section> | |
49 | - <section layout="row" flex ng-show="model.useFilter"> | |
50 | - <md-input-container flex class="entity-name-filter-input"> | |
51 | - <label translate>entity.name-starts-with</label> | |
52 | - <input ng-model="model.entityNameFilter" aria-label="{{ 'entity.name-starts-with' | translate }}"> | |
18 | +<div layout='column' class="tb-entity-filter"> | |
19 | + <md-input-container class="md-block"> | |
20 | + <label>{{ 'alias.filter-type' | translate }}</label> | |
21 | + <md-select required name="filterType" | |
22 | + ng-model="filter.type" aria-label="{{ 'alias.filter-type' | translate }}"> | |
23 | + <md-option ng-repeat="type in aliasFilterTypes" ng-value="type.value"> | |
24 | + {{type.name | translate}} | |
25 | + </md-option> | |
26 | + </md-select> | |
27 | + <div ng-messages="theForm.filterType.$error"> | |
28 | + <div ng-message="required" translate>alias.filter-type-required</div> | |
29 | + </div> | |
30 | + </md-input-container> | |
31 | + <section layout="column" ng-if="filter.type == types.aliasFilterType.entityList.value" id="entityListFilter"> | |
32 | + <tb-entity-type-select | |
33 | + ng-model="filter.entityType" | |
34 | + the-form="theForm" | |
35 | + tb-required="true" | |
36 | + allowed-entity-types="allowedEntityTypes"> | |
37 | + </tb-entity-type-select> | |
38 | + <tb-entity-list | |
39 | + ng-model="filter.entityList" | |
40 | + tb-required="true" | |
41 | + entity-type="filter.entityType"> | |
42 | + </tb-entity-list> | |
43 | + </section> | |
44 | + <section flex layout="column" ng-if="filter.type == types.aliasFilterType.entityName.value" id="entityNameFilter"> | |
45 | + <tb-entity-type-select | |
46 | + ng-model="filter.entityType" | |
47 | + the-form="theForm" | |
48 | + tb-required="true" | |
49 | + allowed-entity-types="allowedEntityTypes"> | |
50 | + </tb-entity-type-select> | |
51 | + <md-input-container class="md-block"> | |
52 | + <label translate>entity.name-starts-with</label> | |
53 | + <input required name="entityNameFilter" | |
54 | + ng-model="filter.entityNameFilter" | |
55 | + aria-label="{{ 'entity.name-starts-with' | translate }}"> | |
56 | + <div ng-messages="theForm.entityNameFilter.$error"> | |
57 | + <div ng-message="required" translate>entity.entity-name-filter-required</div> | |
58 | + </div> | |
59 | + </md-input-container> | |
60 | + </section> | |
61 | + <section layout="column" ng-if="filter.type == types.aliasFilterType.stateEntity.value" id="stateEntityFilter"> | |
62 | + </section> | |
63 | + <section layout="column" ng-if="filter.type == types.aliasFilterType.assetType.value" id="assetTypeFilter"> | |
64 | + <tb-entity-subtype-autocomplete | |
65 | + tb-required="true" | |
66 | + the-form="theForm" | |
67 | + ng-model="filter.assetType" | |
68 | + entity-type="types.entityType.asset"> | |
69 | + </tb-entity-subtype-autocomplete> | |
70 | + <md-input-container class="md-block"> | |
71 | + <label translate>asset.name-starts-with</label> | |
72 | + <input name="assetNameFilter" | |
73 | + ng-model="filter.assetNameFilter" | |
74 | + aria-label="{{ 'asset.name-starts-with' | translate }}"> | |
75 | + </md-input-container> | |
76 | + </section> | |
77 | + <section layout="column" ng-if="filter.type == types.aliasFilterType.deviceType.value" id="deviceTypeFilter"> | |
78 | + <tb-entity-subtype-autocomplete | |
79 | + tb-required="true" | |
80 | + the-form="theForm" | |
81 | + ng-model="filter.deviceType" | |
82 | + entity-type="types.entityType.device"> | |
83 | + </tb-entity-subtype-autocomplete> | |
84 | + <md-input-container class="md-block"> | |
85 | + <label translate>device.name-starts-with</label> | |
86 | + <input name="deviceNameFilter" | |
87 | + ng-model="filter.deviceNameFilter" | |
88 | + aria-label="{{ 'device.name-starts-with' | translate }}"> | |
89 | + </md-input-container> | |
90 | + </section> | |
91 | + <section layout="column" ng-if="filter.type == types.aliasFilterType.relationsQuery.value" id="relationsQueryFilter"> | |
92 | + <label class="tb-small">{{ 'alias.root-entity' | translate }}</label> | |
93 | + <div flex layout="row"> | |
94 | + <tb-entity-select flex | |
95 | + the-form="theForm" | |
96 | + tb-required="!filter.rootStateEntity" | |
97 | + ng-disabled="filter.rootStateEntity" | |
98 | + ng-model="filter.rootEntity"> | |
99 | + </tb-entity-select> | |
100 | + <section class="tb-root-state-entity-switch" layout="column" layout-align="start center"> | |
101 | + <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label> | |
102 | + <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity" | |
103 | + aria-label="{{ 'alias.root-state-entity' | translate }}"> | |
104 | + </md-switch> | |
105 | + </section> | |
106 | + </div> | |
107 | + <div flex layout="row"> | |
108 | + <md-input-container class="md-block" style="min-width: 100px;"> | |
109 | + <label translate>relation.direction</label> | |
110 | + <md-select required ng-model="filter.direction"> | |
111 | + <md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction"> | |
112 | + {{ ('relation.search-direction.' + direction) | translate}} | |
113 | + </md-option> | |
114 | + </md-select> | |
115 | + </md-input-container> | |
116 | + <md-input-container flex class="md-block"> | |
117 | + <label translate>alias.max-relation-level</label> | |
118 | + <input name="maxRelationLevel" | |
119 | + type="number" | |
120 | + min="1" | |
121 | + step="1" | |
122 | + placeholder="{{ 'alias.unlimited-level' | translate }}" | |
123 | + ng-model="filter.maxLevel" | |
124 | + aria-label="{{ 'alias.max-relation-level' | translate }}"> | |
125 | + </md-input-container> | |
126 | + </div> | |
127 | + <div class="md-caption" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>relation.relation-filters</div> | |
128 | + <tb-relation-filters | |
129 | + ng-model="filter.filters" | |
130 | + allowed-entity-types="allowedEntityTypes"> | |
131 | + </tb-relation-filters> | |
132 | + </section> | |
133 | + <section layout="column" ng-if="filter.type == types.aliasFilterType.assetSearchQuery.value" id="assetSearchQueryFilter"> | |
134 | + <label class="tb-small">{{ 'alias.root-entity' | translate }}</label> | |
135 | + <div flex layout="row"> | |
136 | + <tb-entity-select flex | |
137 | + the-form="theForm" | |
138 | + tb-required="!filter.rootStateEntity" | |
139 | + ng-disabled="filter.rootStateEntity" | |
140 | + ng-model="filter.rootEntity"> | |
141 | + </tb-entity-select> | |
142 | + <section class="tb-root-state-entity-switch" layout="column" layout-align="start center"> | |
143 | + <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label> | |
144 | + <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity" | |
145 | + aria-label="{{ 'alias.root-state-entity' | translate }}"> | |
146 | + </md-switch> | |
147 | + </section> | |
148 | + </div> | |
149 | + <div flex layout="row"> | |
150 | + <md-input-container class="md-block" style="min-width: 100px;"> | |
151 | + <label translate>relation.direction</label> | |
152 | + <md-select required ng-model="filter.direction"> | |
153 | + <md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction"> | |
154 | + {{ ('relation.search-direction.' + direction) | translate}} | |
155 | + </md-option> | |
156 | + </md-select> | |
157 | + </md-input-container> | |
158 | + <md-input-container flex class="md-block"> | |
159 | + <label translate>alias.max-relation-level</label> | |
160 | + <input name="maxRelationLevel" | |
161 | + type="number" | |
162 | + min="1" | |
163 | + step="1" | |
164 | + placeholder="{{ 'alias.unlimited-level' | translate }}" | |
165 | + ng-model="filter.maxLevel" | |
166 | + aria-label="{{ 'alias.max-relation-level' | translate }}"> | |
167 | + </md-input-container> | |
168 | + </div> | |
169 | + <div class="md-caption" style="color: rgba(0,0,0,0.57);" translate>relation.relation-type</div> | |
170 | + <tb-relation-type-autocomplete flex | |
171 | + hide-label | |
172 | + the-form="theForm" | |
173 | + ng-model="filter.relationType" | |
174 | + tb-required="false"> | |
175 | + </tb-relation-type-autocomplete> | |
176 | + <div class="md-caption tb-required" style="color: rgba(0,0,0,0.57);" translate>asset.asset-types</div> | |
177 | + <tb-entity-subtype-list | |
178 | + tb-required="true" | |
179 | + entity-type="types.entityType.asset" | |
180 | + ng-model="filter.assetTypes"> | |
181 | + </tb-entity-subtype-list> | |
182 | + </section> | |
183 | + <section layout="column" ng-if="filter.type == types.aliasFilterType.deviceSearchQuery.value" id="deviceSearchQueryFilter"> | |
184 | + <label class="tb-small">{{ 'alias.root-entity' | translate }}</label> | |
185 | + <div flex layout="row"> | |
186 | + <tb-entity-select flex | |
187 | + the-form="theForm" | |
188 | + tb-required="!filter.rootStateEntity" | |
189 | + ng-disabled="filter.rootStateEntity" | |
190 | + ng-model="filter.rootEntity"> | |
191 | + </tb-entity-select> | |
192 | + <section class="tb-root-state-entity-switch" layout="column" layout-align="start center"> | |
193 | + <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label> | |
194 | + <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity" | |
195 | + aria-label="{{ 'alias.root-state-entity' | translate }}"> | |
196 | + </md-switch> | |
197 | + </section> | |
198 | + </div> | |
199 | + <div flex layout="row"> | |
200 | + <md-input-container class="md-block" style="min-width: 100px;"> | |
201 | + <label translate>relation.direction</label> | |
202 | + <md-select required ng-model="filter.direction"> | |
203 | + <md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction"> | |
204 | + {{ ('relation.search-direction.' + direction) | translate}} | |
205 | + </md-option> | |
206 | + </md-select> | |
207 | + </md-input-container> | |
208 | + <md-input-container flex class="md-block"> | |
209 | + <label translate>alias.max-relation-level</label> | |
210 | + <input name="maxRelationLevel" | |
211 | + type="number" | |
212 | + min="1" | |
213 | + step="1" | |
214 | + placeholder="{{ 'alias.unlimited-level' | translate }}" | |
215 | + ng-model="filter.maxLevel" | |
216 | + aria-label="{{ 'alias.max-relation-level' | translate }}"> | |
53 | 217 | </md-input-container> |
54 | - </section> | |
55 | - <section class="tb-filter-switch" layout="column" layout-align="center center"> | |
56 | - <label class="tb-small filter-label" translate>entity.use-entity-name-filter</label> | |
57 | - <md-switch class="filter-switch" ng-model="model.useFilter" aria-label="use-filter-switcher"> | |
58 | - </md-switch> | |
59 | - </section> | |
218 | + </div> | |
219 | + <div class="md-caption" style="color: rgba(0,0,0,0.57);" translate>relation.relation-type</div> | |
220 | + <tb-relation-type-autocomplete flex | |
221 | + hide-label | |
222 | + the-form="theForm" | |
223 | + ng-model="filter.relationType" | |
224 | + tb-required="false"> | |
225 | + </tb-relation-type-autocomplete> | |
226 | + <div class="md-caption tb-required" style="color: rgba(0,0,0,0.57);" translate>device.device-types</div> | |
227 | + <tb-entity-subtype-list | |
228 | + tb-required="true" | |
229 | + entity-type="types.entityType.device" | |
230 | + ng-model="filter.deviceTypes"> | |
231 | + </tb-entity-subtype-list> | |
60 | 232 | </section> |
61 | - <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert"> | |
62 | - <div translate ng-message="entityList" class="tb-error-message">entity.entity-list-empty</div> | |
63 | - <div translate ng-message="entityNameFilter" class="tb-error-message">entity.entity-name-filter-required</div> | |
64 | - <div translate translate-values='{ entity: model.entityNameFilter }' ng-message="entityNameFilterEntityMatch" | |
65 | - class="tb-error-message">entity.entity-name-filter-no-entity-matched</div> | |
66 | - </div> | |
67 | -</section> | |
\ No newline at end of file | ||
233 | +</div> | ... | ... |