Commit f806c392b2f7128878b2847135ffb1fa9676e2cc
Merge remote-tracking branch 'upstream/master' into dao-refactoring-vs
Showing
56 changed files
with
2081 additions
and
284 deletions
@@ -143,4 +143,30 @@ public class AlarmController extends BaseController { | @@ -143,4 +143,30 @@ public class AlarmController extends BaseController { | ||
143 | } | 143 | } |
144 | } | 144 | } |
145 | 145 | ||
146 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | ||
147 | + @RequestMapping(value = "/alarm/highestSeverity/{entityType}/{entityId}", method = RequestMethod.GET) | ||
148 | + @ResponseBody | ||
149 | + public AlarmSeverity getHighestAlarmSeverity( | ||
150 | + @PathVariable("entityType") String strEntityType, | ||
151 | + @PathVariable("entityId") String strEntityId, | ||
152 | + @RequestParam(required = false) String searchStatus, | ||
153 | + @RequestParam(required = false) String status | ||
154 | + ) throws ThingsboardException { | ||
155 | + checkParameter("EntityId", strEntityId); | ||
156 | + checkParameter("EntityType", strEntityType); | ||
157 | + EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId); | ||
158 | + AlarmSearchStatus alarmSearchStatus = StringUtils.isEmpty(searchStatus) ? null : AlarmSearchStatus.valueOf(searchStatus); | ||
159 | + AlarmStatus alarmStatus = StringUtils.isEmpty(status) ? null : AlarmStatus.valueOf(status); | ||
160 | + if (alarmSearchStatus != null && alarmStatus != null) { | ||
161 | + throw new ThingsboardException("Invalid alarms search query: Both parameters 'searchStatus' " + | ||
162 | + "and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); | ||
163 | + } | ||
164 | + checkEntityId(entityId); | ||
165 | + try { | ||
166 | + return alarmService.findHighestAlarmSeverity(entityId, alarmSearchStatus, alarmStatus); | ||
167 | + } catch (Exception e) { | ||
168 | + throw handleException(e); | ||
169 | + } | ||
170 | + } | ||
171 | + | ||
146 | } | 172 | } |
@@ -16,10 +16,8 @@ | @@ -16,10 +16,8 @@ | ||
16 | package org.thingsboard.server.dao.alarm; | 16 | package org.thingsboard.server.dao.alarm; |
17 | 17 | ||
18 | import com.google.common.util.concurrent.ListenableFuture; | 18 | import com.google.common.util.concurrent.ListenableFuture; |
19 | -import org.thingsboard.server.common.data.alarm.Alarm; | ||
20 | -import org.thingsboard.server.common.data.alarm.AlarmId; | ||
21 | -import org.thingsboard.server.common.data.alarm.AlarmInfo; | ||
22 | -import org.thingsboard.server.common.data.alarm.AlarmQuery; | 19 | +import org.thingsboard.server.common.data.alarm.*; |
20 | +import org.thingsboard.server.common.data.id.EntityId; | ||
23 | import org.thingsboard.server.common.data.page.TimePageData; | 21 | import org.thingsboard.server.common.data.page.TimePageData; |
24 | 22 | ||
25 | /** | 23 | /** |
@@ -39,4 +37,7 @@ public interface AlarmService { | @@ -39,4 +37,7 @@ public interface AlarmService { | ||
39 | 37 | ||
40 | ListenableFuture<TimePageData<AlarmInfo>> findAlarms(AlarmQuery query); | 38 | ListenableFuture<TimePageData<AlarmInfo>> findAlarms(AlarmQuery query); |
41 | 39 | ||
40 | + AlarmSeverity findHighestAlarmSeverity(EntityId entityId, AlarmSearchStatus alarmSearchStatus, | ||
41 | + AlarmStatus alarmStatus); | ||
42 | + | ||
42 | } | 43 | } |
@@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.Tenant; | @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.Tenant; | ||
28 | import org.thingsboard.server.common.data.alarm.*; | 28 | import org.thingsboard.server.common.data.alarm.*; |
29 | import org.thingsboard.server.common.data.id.EntityId; | 29 | import org.thingsboard.server.common.data.id.EntityId; |
30 | import org.thingsboard.server.common.data.page.TimePageData; | 30 | import org.thingsboard.server.common.data.page.TimePageData; |
31 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
31 | import org.thingsboard.server.common.data.relation.EntityRelation; | 32 | import org.thingsboard.server.common.data.relation.EntityRelation; |
32 | import org.thingsboard.server.common.data.relation.RelationTypeGroup; | 33 | import org.thingsboard.server.common.data.relation.RelationTypeGroup; |
33 | import org.thingsboard.server.dao.entity.AbstractEntityService; | 34 | import org.thingsboard.server.dao.entity.AbstractEntityService; |
@@ -45,6 +46,7 @@ import javax.annotation.PostConstruct; | @@ -45,6 +46,7 @@ import javax.annotation.PostConstruct; | ||
45 | import javax.annotation.PreDestroy; | 46 | import javax.annotation.PreDestroy; |
46 | import java.util.ArrayList; | 47 | import java.util.ArrayList; |
47 | import java.util.List; | 48 | import java.util.List; |
49 | +import java.util.UUID; | ||
48 | import java.util.concurrent.ExecutionException; | 50 | import java.util.concurrent.ExecutionException; |
49 | import java.util.concurrent.ExecutorService; | 51 | import java.util.concurrent.ExecutorService; |
50 | import java.util.concurrent.Executors; | 52 | import java.util.concurrent.Executors; |
@@ -240,6 +242,46 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ | @@ -240,6 +242,46 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ | ||
240 | }); | 242 | }); |
241 | } | 243 | } |
242 | 244 | ||
245 | + @Override | ||
246 | + public AlarmSeverity findHighestAlarmSeverity(EntityId entityId, AlarmSearchStatus alarmSearchStatus, | ||
247 | + AlarmStatus alarmStatus) { | ||
248 | + TimePageLink nextPageLink = new TimePageLink(100); | ||
249 | + boolean hasNext = true; | ||
250 | + AlarmSeverity highestSeverity = null; | ||
251 | + AlarmQuery query; | ||
252 | + while (hasNext) { | ||
253 | + query = new AlarmQuery(entityId, nextPageLink, alarmSearchStatus, alarmStatus, false); | ||
254 | + List<AlarmInfo> alarms; | ||
255 | + try { | ||
256 | + alarms = alarmDao.findAlarms(query).get(); | ||
257 | + } catch (ExecutionException | InterruptedException e) { | ||
258 | + log.warn("Failed to find highest alarm severity. EntityId: [{}], AlarmSearchStatus: [{}], AlarmStatus: [{}]", | ||
259 | + entityId, alarmSearchStatus, alarmStatus); | ||
260 | + throw new RuntimeException(e); | ||
261 | + } | ||
262 | + hasNext = alarms.size() == nextPageLink.getLimit(); | ||
263 | + if (hasNext) { | ||
264 | + nextPageLink = new TimePageData<>(alarms, nextPageLink).getNextPageLink(); | ||
265 | + } | ||
266 | + if (alarms.isEmpty()) { | ||
267 | + continue; | ||
268 | + } else { | ||
269 | + List<AlarmInfo> sorted = new ArrayList(alarms); | ||
270 | + sorted.sort((p1, p2) -> p1.getSeverity().compareTo(p2.getSeverity())); | ||
271 | + AlarmSeverity severity = sorted.get(0).getSeverity(); | ||
272 | + if (severity == AlarmSeverity.CRITICAL) { | ||
273 | + highestSeverity = severity; | ||
274 | + break; | ||
275 | + } else if (highestSeverity == null) { | ||
276 | + highestSeverity = severity; | ||
277 | + } else { | ||
278 | + highestSeverity = highestSeverity.compareTo(severity) < 0 ? highestSeverity : severity; | ||
279 | + } | ||
280 | + } | ||
281 | + } | ||
282 | + return highestSeverity; | ||
283 | + } | ||
284 | + | ||
243 | private void deleteRelation(EntityRelation alarmRelation) throws ExecutionException, InterruptedException { | 285 | private void deleteRelation(EntityRelation alarmRelation) throws ExecutionException, InterruptedException { |
244 | log.debug("Deleting Alarm relation: {}", alarmRelation); | 286 | log.debug("Deleting Alarm relation: {}", alarmRelation); |
245 | relationService.deleteRelation(alarmRelation).get(); | 287 | relationService.deleteRelation(alarmRelation).get(); |
@@ -23,11 +23,14 @@ import './alarm-details-dialog.scss'; | @@ -23,11 +23,14 @@ import './alarm-details-dialog.scss'; | ||
23 | const js_beautify = beautify.js; | 23 | const js_beautify = beautify.js; |
24 | 24 | ||
25 | /*@ngInject*/ | 25 | /*@ngInject*/ |
26 | -export default function AlarmDetailsDialogController($mdDialog, $filter, $translate, types, alarmService, alarmId, showingCallback) { | 26 | +export default function AlarmDetailsDialogController($mdDialog, $filter, $translate, types, |
27 | + alarmService, alarmId, allowAcknowledgment, allowClear, showingCallback) { | ||
27 | 28 | ||
28 | var vm = this; | 29 | var vm = this; |
29 | 30 | ||
30 | vm.alarmId = alarmId; | 31 | vm.alarmId = alarmId; |
32 | + vm.allowAcknowledgment = allowAcknowledgment; | ||
33 | + vm.allowClear = allowClear; | ||
31 | vm.types = types; | 34 | vm.types = types; |
32 | vm.alarm = null; | 35 | vm.alarm = null; |
33 | 36 |
@@ -84,16 +84,16 @@ | @@ -84,16 +84,16 @@ | ||
84 | </div> | 84 | </div> |
85 | </md-dialog-content> | 85 | </md-dialog-content> |
86 | <md-dialog-actions layout="row"> | 86 | <md-dialog-actions layout="row"> |
87 | - <md-button ng-if="vm.alarm.status==vm.types.alarmStatus.activeUnack || | ||
88 | - vm.alarm.status==vm.types.alarmStatus.clearedUnack" | 87 | + <md-button ng-if="vm.allowAcknowledgment && (vm.alarm.status==vm.types.alarmStatus.activeUnack || |
88 | + vm.alarm.status==vm.types.alarmStatus.clearedUnack)" | ||
89 | class="md-raised md-primary" | 89 | class="md-raised md-primary" |
90 | ng-disabled="loading" | 90 | ng-disabled="loading" |
91 | ng-click="vm.acknowledge()" | 91 | ng-click="vm.acknowledge()" |
92 | style="margin-right:20px;">{{ 'alarm.acknowledge' | | 92 | style="margin-right:20px;">{{ 'alarm.acknowledge' | |
93 | translate }} | 93 | translate }} |
94 | </md-button> | 94 | </md-button> |
95 | - <md-button ng-if="vm.alarm.status==vm.types.alarmStatus.activeAck || | ||
96 | - vm.alarm.status==vm.types.alarmStatus.activeUnack" | 95 | + <md-button ng-if="vm.allowClear && (vm.alarm.status==vm.types.alarmStatus.activeAck || |
96 | + vm.alarm.status==vm.types.alarmStatus.activeUnack)" | ||
97 | class="md-raised md-primary" | 97 | class="md-raised md-primary" |
98 | ng-disabled="loading" | 98 | ng-disabled="loading" |
99 | ng-click="vm.clear()">{{ 'alarm.clear' | | 99 | ng-click="vm.clear()">{{ 'alarm.clear' | |
@@ -40,7 +40,12 @@ export default function AlarmRowDirective($compile, $templateCache, types, $mdDi | @@ -40,7 +40,12 @@ export default function AlarmRowDirective($compile, $templateCache, types, $mdDi | ||
40 | controller: 'AlarmDetailsDialogController', | 40 | controller: 'AlarmDetailsDialogController', |
41 | controllerAs: 'vm', | 41 | controllerAs: 'vm', |
42 | templateUrl: alarmDetailsDialogTemplate, | 42 | templateUrl: alarmDetailsDialogTemplate, |
43 | - locals: {alarmId: scope.alarm.id.id, showingCallback: onShowingCallback}, | 43 | + locals: { |
44 | + alarmId: scope.alarm.id.id, | ||
45 | + allowAcknowledgment: true, | ||
46 | + allowClear: true, | ||
47 | + showingCallback: onShowingCallback | ||
48 | + }, | ||
44 | parent: angular.element($document[0].body), | 49 | parent: angular.element($document[0].body), |
45 | targetEvent: $event, | 50 | targetEvent: $event, |
46 | fullscreen: true, | 51 | fullscreen: true, |
@@ -18,7 +18,29 @@ export default angular.module('thingsboard.api.alarm', []) | @@ -18,7 +18,29 @@ export default angular.module('thingsboard.api.alarm', []) | ||
18 | .name; | 18 | .name; |
19 | 19 | ||
20 | /*@ngInject*/ | 20 | /*@ngInject*/ |
21 | -function AlarmService($http, $q, $interval, $filter) { | 21 | +function AlarmService($http, $q, $interval, $filter, $timeout, utils, types) { |
22 | + | ||
23 | + var alarmSourceListeners = {}; | ||
24 | + | ||
25 | + var simulatedAlarm = { | ||
26 | + createdTime: (new Date).getTime(), | ||
27 | + startTs: (new Date).getTime(), | ||
28 | + endTs: 0, | ||
29 | + ackTs: 0, | ||
30 | + clearTs: 0, | ||
31 | + originatorName: 'Simulated', | ||
32 | + originator: { | ||
33 | + entityType: "DEVICE", | ||
34 | + id: "1" | ||
35 | + }, | ||
36 | + type: 'TEMPERATURE', | ||
37 | + severity: "MAJOR", | ||
38 | + status: types.alarmStatus.activeUnack, | ||
39 | + details: { | ||
40 | + message: "Temperature is high!" | ||
41 | + } | ||
42 | + }; | ||
43 | + | ||
22 | var service = { | 44 | var service = { |
23 | getAlarm: getAlarm, | 45 | getAlarm: getAlarm, |
24 | getAlarmInfo: getAlarmInfo, | 46 | getAlarmInfo: getAlarmInfo, |
@@ -26,8 +48,11 @@ function AlarmService($http, $q, $interval, $filter) { | @@ -26,8 +48,11 @@ function AlarmService($http, $q, $interval, $filter) { | ||
26 | ackAlarm: ackAlarm, | 48 | ackAlarm: ackAlarm, |
27 | clearAlarm: clearAlarm, | 49 | clearAlarm: clearAlarm, |
28 | getAlarms: getAlarms, | 50 | getAlarms: getAlarms, |
51 | + getHighestAlarmSeverity: getHighestAlarmSeverity, | ||
29 | pollAlarms: pollAlarms, | 52 | pollAlarms: pollAlarms, |
30 | - cancelPollAlarms: cancelPollAlarms | 53 | + cancelPollAlarms: cancelPollAlarms, |
54 | + subscribeForAlarms: subscribeForAlarms, | ||
55 | + unsubscribeFromAlarms: unsubscribeFromAlarms | ||
31 | } | 56 | } |
32 | 57 | ||
33 | return service; | 58 | return service; |
@@ -141,6 +166,23 @@ function AlarmService($http, $q, $interval, $filter) { | @@ -141,6 +166,23 @@ function AlarmService($http, $q, $interval, $filter) { | ||
141 | return deferred.promise; | 166 | return deferred.promise; |
142 | } | 167 | } |
143 | 168 | ||
169 | + function getHighestAlarmSeverity(entityType, entityId, alarmSearchStatus, alarmStatus, config) { | ||
170 | + var deferred = $q.defer(); | ||
171 | + var url = '/api/alarm/highestSeverity/' + entityType + '/' + entityId; | ||
172 | + | ||
173 | + if (alarmSearchStatus) { | ||
174 | + url += '?searchStatus=' + alarmSearchStatus; | ||
175 | + } else if (alarmStatus) { | ||
176 | + url += '?status=' + alarmStatus; | ||
177 | + } | ||
178 | + $http.get(url, config).then(function success(response) { | ||
179 | + deferred.resolve(response.data); | ||
180 | + }, function fail() { | ||
181 | + deferred.reject(); | ||
182 | + }); | ||
183 | + return deferred.promise; | ||
184 | + } | ||
185 | + | ||
144 | function fetchAlarms(alarmsQuery, pageLink, deferred, alarmsList) { | 186 | function fetchAlarms(alarmsQuery, pageLink, deferred, alarmsList) { |
145 | getAlarms(alarmsQuery.entityType, alarmsQuery.entityId, | 187 | getAlarms(alarmsQuery.entityType, alarmsQuery.entityId, |
146 | pageLink, alarmsQuery.alarmSearchStatus, alarmsQuery.alarmStatus, | 188 | pageLink, alarmsQuery.alarmSearchStatus, alarmsQuery.alarmStatus, |
@@ -171,12 +213,21 @@ function AlarmService($http, $q, $interval, $filter) { | @@ -171,12 +213,21 @@ function AlarmService($http, $q, $interval, $filter) { | ||
171 | pageLink = { | 213 | pageLink = { |
172 | limit: alarmsQuery.limit | 214 | limit: alarmsQuery.limit |
173 | }; | 215 | }; |
174 | - } else { | 216 | + } else if (alarmsQuery.interval) { |
175 | pageLink = { | 217 | pageLink = { |
176 | limit: 100, | 218 | limit: 100, |
177 | startTime: time - alarmsQuery.interval | 219 | startTime: time - alarmsQuery.interval |
178 | }; | 220 | }; |
221 | + } else if (alarmsQuery.startTime) { | ||
222 | + pageLink = { | ||
223 | + limit: 100, | ||
224 | + startTime: alarmsQuery.startTime | ||
225 | + } | ||
226 | + if (alarmsQuery.endTime) { | ||
227 | + pageLink.endTime = alarmsQuery.endTime; | ||
228 | + } | ||
179 | } | 229 | } |
230 | + | ||
180 | fetchAlarms(alarmsQuery, pageLink, deferred); | 231 | fetchAlarms(alarmsQuery, pageLink, deferred); |
181 | return deferred.promise; | 232 | return deferred.promise; |
182 | } | 233 | } |
@@ -211,4 +262,59 @@ function AlarmService($http, $q, $interval, $filter) { | @@ -211,4 +262,59 @@ function AlarmService($http, $q, $interval, $filter) { | ||
211 | } | 262 | } |
212 | } | 263 | } |
213 | 264 | ||
265 | + function subscribeForAlarms(alarmSourceListener) { | ||
266 | + alarmSourceListener.id = utils.guid(); | ||
267 | + alarmSourceListeners[alarmSourceListener.id] = alarmSourceListener; | ||
268 | + var alarmSource = alarmSourceListener.alarmSource; | ||
269 | + if (alarmSource.type == types.datasourceType.function) { | ||
270 | + $timeout(function() { | ||
271 | + alarmSourceListener.alarmsUpdated([simulatedAlarm], false); | ||
272 | + }); | ||
273 | + } else if (alarmSource.entityType && alarmSource.entityId) { | ||
274 | + var pollingInterval = alarmSourceListener.alarmsPollingInterval; | ||
275 | + alarmSourceListener.alarmsQuery = { | ||
276 | + entityType: alarmSource.entityType, | ||
277 | + entityId: alarmSource.entityId, | ||
278 | + alarmSearchStatus: alarmSourceListener.alarmSearchStatus, | ||
279 | + alarmStatus: null | ||
280 | + } | ||
281 | + var originatorKeys = $filter('filter')(alarmSource.dataKeys, {name: 'originator'}); | ||
282 | + if (originatorKeys && originatorKeys.length) { | ||
283 | + alarmSourceListener.alarmsQuery.fetchOriginator = true; | ||
284 | + } | ||
285 | + var subscriptionTimewindow = alarmSourceListener.subscriptionTimewindow; | ||
286 | + if (subscriptionTimewindow.realtimeWindowMs) { | ||
287 | + alarmSourceListener.alarmsQuery.startTime = subscriptionTimewindow.startTs; | ||
288 | + } else { | ||
289 | + alarmSourceListener.alarmsQuery.startTime = subscriptionTimewindow.fixedWindow.startTimeMs; | ||
290 | + alarmSourceListener.alarmsQuery.endTime = subscriptionTimewindow.fixedWindow.endTimeMs; | ||
291 | + } | ||
292 | + alarmSourceListener.alarmsQuery.onAlarms = function(alarms) { | ||
293 | + if (subscriptionTimewindow.realtimeWindowMs) { | ||
294 | + var now = Date.now(); | ||
295 | + if (alarmSourceListener.lastUpdateTs) { | ||
296 | + var interval = now - alarmSourceListener.lastUpdateTs; | ||
297 | + alarmSourceListener.alarmsQuery.startTime += interval; | ||
298 | + } else { | ||
299 | + alarmSourceListener.lastUpdateTs = now; | ||
300 | + } | ||
301 | + } | ||
302 | + alarmSourceListener.alarmsUpdated(alarms, false); | ||
303 | + } | ||
304 | + onPollAlarms(alarmSourceListener.alarmsQuery); | ||
305 | + alarmSourceListener.pollPromise = $interval(onPollAlarms, pollingInterval, | ||
306 | + 0, false, alarmSourceListener.alarmsQuery); | ||
307 | + } | ||
308 | + | ||
309 | + } | ||
310 | + | ||
311 | + function unsubscribeFromAlarms(alarmSourceListener) { | ||
312 | + if (alarmSourceListener && alarmSourceListener.id) { | ||
313 | + if (alarmSourceListener.pollPromise) { | ||
314 | + $interval.cancel(alarmSourceListener.pollPromise); | ||
315 | + alarmSourceListener.pollPromise = null; | ||
316 | + } | ||
317 | + delete alarmSourceListeners[alarmSourceListener.id]; | ||
318 | + } | ||
319 | + } | ||
214 | } | 320 | } |
@@ -14,8 +14,6 @@ | @@ -14,8 +14,6 @@ | ||
14 | * limitations under the License. | 14 | * limitations under the License. |
15 | */ | 15 | */ |
16 | 16 | ||
17 | -const varsRegex = /\$\{([^\}]*)\}/g; | ||
18 | - | ||
19 | export default class AliasController { | 17 | export default class AliasController { |
20 | 18 | ||
21 | constructor($scope, $q, $filter, utils, types, entityService, stateController, entityAliases) { | 19 | constructor($scope, $q, $filter, utils, types, entityService, stateController, entityAliases) { |
@@ -113,14 +111,14 @@ export default class AliasController { | @@ -113,14 +111,14 @@ export default class AliasController { | ||
113 | } | 111 | } |
114 | } | 112 | } |
115 | 113 | ||
116 | - resolveDatasource(datasource) { | 114 | + resolveDatasource(datasource, isSingle) { |
117 | var deferred = this.$q.defer(); | 115 | var deferred = this.$q.defer(); |
118 | if (datasource.type === this.types.datasourceType.entity) { | 116 | if (datasource.type === this.types.datasourceType.entity) { |
119 | if (datasource.entityAliasId) { | 117 | if (datasource.entityAliasId) { |
120 | this.getAliasInfo(datasource.entityAliasId).then( | 118 | this.getAliasInfo(datasource.entityAliasId).then( |
121 | function success(aliasInfo) { | 119 | function success(aliasInfo) { |
122 | datasource.aliasName = aliasInfo.alias; | 120 | datasource.aliasName = aliasInfo.alias; |
123 | - if (aliasInfo.resolveMultiple) { | 121 | + if (aliasInfo.resolveMultiple && !isSingle) { |
124 | var newDatasource; | 122 | var newDatasource; |
125 | var resolvedEntities = aliasInfo.resolvedEntities; | 123 | var resolvedEntities = aliasInfo.resolvedEntities; |
126 | if (resolvedEntities && resolvedEntities.length) { | 124 | if (resolvedEntities && resolvedEntities.length) { |
@@ -178,30 +176,44 @@ export default class AliasController { | @@ -178,30 +176,44 @@ export default class AliasController { | ||
178 | return deferred.promise; | 176 | return deferred.promise; |
179 | } | 177 | } |
180 | 178 | ||
179 | + resolveAlarmSource(alarmSource) { | ||
180 | + var deferred = this.$q.defer(); | ||
181 | + var aliasCtrl = this; | ||
182 | + this.resolveDatasource(alarmSource, true).then( | ||
183 | + function success(datasources) { | ||
184 | + var datasource = datasources[0]; | ||
185 | + if (datasource.type === aliasCtrl.types.datasourceType.function) { | ||
186 | + var name; | ||
187 | + if (datasource.name && datasource.name.length) { | ||
188 | + name = datasource.name; | ||
189 | + } else { | ||
190 | + name = aliasCtrl.types.datasourceType.function; | ||
191 | + } | ||
192 | + datasource.name = name; | ||
193 | + datasource.aliasName = name; | ||
194 | + datasource.entityName = name; | ||
195 | + } else if (datasource.unresolvedStateEntity) { | ||
196 | + datasource.name = "Unresolved"; | ||
197 | + datasource.entityName = "Unresolved"; | ||
198 | + } | ||
199 | + deferred.resolve(datasource); | ||
200 | + }, | ||
201 | + function fail() { | ||
202 | + deferred.reject(); | ||
203 | + } | ||
204 | + ); | ||
205 | + return deferred.promise; | ||
206 | + } | ||
207 | + | ||
181 | resolveDatasources(datasources) { | 208 | resolveDatasources(datasources) { |
182 | 209 | ||
210 | + var aliasCtrl = this; | ||
211 | + | ||
183 | function updateDataKeyLabel(dataKey, datasource) { | 212 | function updateDataKeyLabel(dataKey, datasource) { |
184 | if (!dataKey.pattern) { | 213 | if (!dataKey.pattern) { |
185 | dataKey.pattern = angular.copy(dataKey.label); | 214 | dataKey.pattern = angular.copy(dataKey.label); |
186 | } | 215 | } |
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; | 216 | + dataKey.label = aliasCtrl.utils.createLabelFromDatasource(datasource, dataKey.pattern); |
205 | } | 217 | } |
206 | 218 | ||
207 | function updateDatasourceKeyLabels(datasource) { | 219 | function updateDatasourceKeyLabels(datasource) { |
@@ -213,7 +225,7 @@ export default class AliasController { | @@ -213,7 +225,7 @@ export default class AliasController { | ||
213 | var deferred = this.$q.defer(); | 225 | var deferred = this.$q.defer(); |
214 | var newDatasources = angular.copy(datasources); | 226 | var newDatasources = angular.copy(datasources); |
215 | var datasorceResolveTasks = []; | 227 | var datasorceResolveTasks = []; |
216 | - var aliasCtrl = this; | 228 | + |
217 | newDatasources.forEach(function (datasource) { | 229 | newDatasources.forEach(function (datasource) { |
218 | var resolveDatasourceTask = aliasCtrl.resolveDatasource(datasource); | 230 | var resolveDatasourceTask = aliasCtrl.resolveDatasource(datasource); |
219 | datasorceResolveTasks.push(resolveDatasourceTask); | 231 | datasorceResolveTasks.push(resolveDatasourceTask); |
@@ -64,6 +64,45 @@ export default class Subscription { | @@ -64,6 +64,45 @@ export default class Subscription { | ||
64 | deferred.resolve(subscription); | 64 | deferred.resolve(subscription); |
65 | } | 65 | } |
66 | ); | 66 | ); |
67 | + } else if (this.type === this.ctx.types.widgetType.alarm.value) { | ||
68 | + this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || function(){}; | ||
69 | + this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || function(){}; | ||
70 | + this.callbacks.dataLoading = this.callbacks.dataLoading || function(){}; | ||
71 | + this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || function(){}; | ||
72 | + this.alarmSource = options.alarmSource; | ||
73 | + | ||
74 | + this.alarmSearchStatus = angular.isDefined(options.alarmSearchStatus) ? | ||
75 | + options.alarmSearchStatus : this.ctx.types.alarmSearchStatus.any; | ||
76 | + this.alarmsPollingInterval = angular.isDefined(options.alarmsPollingInterval) ? | ||
77 | + options.alarmsPollingInterval : 5000; | ||
78 | + | ||
79 | + this.alarmSourceListener = null; | ||
80 | + this.alarms = []; | ||
81 | + | ||
82 | + this.originalTimewindow = null; | ||
83 | + this.timeWindow = { | ||
84 | + stDiff: this.ctx.stDiff | ||
85 | + } | ||
86 | + this.useDashboardTimewindow = options.useDashboardTimewindow; | ||
87 | + | ||
88 | + if (this.useDashboardTimewindow) { | ||
89 | + this.timeWindowConfig = angular.copy(options.dashboardTimewindow); | ||
90 | + } else { | ||
91 | + this.timeWindowConfig = angular.copy(options.timeWindowConfig); | ||
92 | + } | ||
93 | + | ||
94 | + this.subscriptionTimewindow = null; | ||
95 | + | ||
96 | + this.loadingData = false; | ||
97 | + this.displayLegend = false; | ||
98 | + this.initAlarmSubscription().then( | ||
99 | + function success() { | ||
100 | + deferred.resolve(subscription); | ||
101 | + }, | ||
102 | + function fail() { | ||
103 | + deferred.reject(); | ||
104 | + } | ||
105 | + ); | ||
67 | } else { | 106 | } else { |
68 | this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || function(){}; | 107 | this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || function(){}; |
69 | this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || function(){}; | 108 | this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || function(){}; |
@@ -132,6 +171,43 @@ export default class Subscription { | @@ -132,6 +171,43 @@ export default class Subscription { | ||
132 | return deferred.promise; | 171 | return deferred.promise; |
133 | } | 172 | } |
134 | 173 | ||
174 | + initAlarmSubscription() { | ||
175 | + var deferred = this.ctx.$q.defer(); | ||
176 | + if (!this.ctx.aliasController) { | ||
177 | + this.configureAlarmsData(); | ||
178 | + deferred.resolve(); | ||
179 | + } else { | ||
180 | + var subscription = this; | ||
181 | + this.ctx.aliasController.resolveAlarmSource(this.alarmSource).then( | ||
182 | + function success(alarmSource) { | ||
183 | + subscription.alarmSource = alarmSource; | ||
184 | + subscription.configureAlarmsData(); | ||
185 | + deferred.resolve(); | ||
186 | + }, | ||
187 | + function fail() { | ||
188 | + deferred.reject(); | ||
189 | + } | ||
190 | + ); | ||
191 | + } | ||
192 | + return deferred.promise; | ||
193 | + } | ||
194 | + | ||
195 | + configureAlarmsData() { | ||
196 | + var subscription = this; | ||
197 | + var registration; | ||
198 | + if (this.useDashboardTimewindow) { | ||
199 | + registration = this.ctx.$scope.$on('dashboardTimewindowChanged', function (event, newDashboardTimewindow) { | ||
200 | + if (!angular.equals(subscription.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) { | ||
201 | + subscription.timeWindowConfig = angular.copy(newDashboardTimewindow); | ||
202 | + subscription.update(); | ||
203 | + } | ||
204 | + }); | ||
205 | + this.registrations.push(registration); | ||
206 | + } else { | ||
207 | + this.startWatchingTimewindow(); | ||
208 | + } | ||
209 | + } | ||
210 | + | ||
135 | initDataSubscription() { | 211 | initDataSubscription() { |
136 | var deferred = this.ctx.$q.defer(); | 212 | var deferred = this.ctx.$q.defer(); |
137 | if (!this.ctx.aliasController) { | 213 | if (!this.ctx.aliasController) { |
@@ -210,8 +286,7 @@ export default class Subscription { | @@ -210,8 +286,7 @@ export default class Subscription { | ||
210 | registration = this.ctx.$scope.$on('dashboardTimewindowChanged', function (event, newDashboardTimewindow) { | 286 | registration = this.ctx.$scope.$on('dashboardTimewindowChanged', function (event, newDashboardTimewindow) { |
211 | if (!angular.equals(subscription.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) { | 287 | if (!angular.equals(subscription.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) { |
212 | subscription.timeWindowConfig = angular.copy(newDashboardTimewindow); | 288 | subscription.timeWindowConfig = angular.copy(newDashboardTimewindow); |
213 | - subscription.unsubscribe(); | ||
214 | - subscription.subscribe(); | 289 | + subscription.update(); |
215 | } | 290 | } |
216 | }); | 291 | }); |
217 | this.registrations.push(registration); | 292 | this.registrations.push(registration); |
@@ -227,8 +302,7 @@ export default class Subscription { | @@ -227,8 +302,7 @@ export default class Subscription { | ||
227 | return subscription.timeWindowConfig; | 302 | return subscription.timeWindowConfig; |
228 | }, function (newTimewindow, prevTimewindow) { | 303 | }, function (newTimewindow, prevTimewindow) { |
229 | if (!angular.equals(newTimewindow, prevTimewindow)) { | 304 | if (!angular.equals(newTimewindow, prevTimewindow)) { |
230 | - subscription.unsubscribe(); | ||
231 | - subscription.subscribe(); | 305 | + subscription.update(); |
232 | } | 306 | } |
233 | }, true); | 307 | }, true); |
234 | this.registrations.push(this.timeWindowWatchRegistration); | 308 | this.registrations.push(this.timeWindowWatchRegistration); |
@@ -393,6 +467,8 @@ export default class Subscription { | @@ -393,6 +467,8 @@ export default class Subscription { | ||
393 | onAliasesChanged(aliasIds) { | 467 | onAliasesChanged(aliasIds) { |
394 | if (this.type === this.ctx.types.widgetType.rpc.value) { | 468 | if (this.type === this.ctx.types.widgetType.rpc.value) { |
395 | return this.checkRpcTarget(aliasIds); | 469 | return this.checkRpcTarget(aliasIds); |
470 | + } else if (this.type === this.ctx.types.widgetType.alarm.value) { | ||
471 | + return this.checkAlarmSource(aliasIds); | ||
396 | } else { | 472 | } else { |
397 | return this.checkSubscriptions(aliasIds); | 473 | return this.checkSubscriptions(aliasIds); |
398 | } | 474 | } |
@@ -429,8 +505,7 @@ export default class Subscription { | @@ -429,8 +505,7 @@ export default class Subscription { | ||
429 | this.timeWindowConfig = angular.copy(this.originalTimewindow); | 505 | this.timeWindowConfig = angular.copy(this.originalTimewindow); |
430 | this.originalTimewindow = null; | 506 | this.originalTimewindow = null; |
431 | this.callbacks.timeWindowUpdated(this, this.timeWindowConfig); | 507 | this.callbacks.timeWindowUpdated(this, this.timeWindowConfig); |
432 | - this.unsubscribe(); | ||
433 | - this.subscribe(); | 508 | + this.update(); |
434 | this.startWatchingTimewindow(); | 509 | this.startWatchingTimewindow(); |
435 | } | 510 | } |
436 | } | 511 | } |
@@ -446,8 +521,7 @@ export default class Subscription { | @@ -446,8 +521,7 @@ export default class Subscription { | ||
446 | } | 521 | } |
447 | this.timeWindowConfig = this.ctx.timeService.toHistoryTimewindow(this.timeWindowConfig, startTimeMs, endTimeMs); | 522 | this.timeWindowConfig = this.ctx.timeService.toHistoryTimewindow(this.timeWindowConfig, startTimeMs, endTimeMs); |
448 | this.callbacks.timeWindowUpdated(this, this.timeWindowConfig); | 523 | this.callbacks.timeWindowUpdated(this, this.timeWindowConfig); |
449 | - this.unsubscribe(); | ||
450 | - this.subscribe(); | 524 | + this.update(); |
451 | this.startWatchingTimewindow(); | 525 | this.startWatchingTimewindow(); |
452 | } | 526 | } |
453 | } | 527 | } |
@@ -516,6 +590,15 @@ export default class Subscription { | @@ -516,6 +590,15 @@ export default class Subscription { | ||
516 | } | 590 | } |
517 | } | 591 | } |
518 | 592 | ||
593 | + alarmsUpdated(alarms, apply) { | ||
594 | + this.notifyDataLoaded(); | ||
595 | + this.alarms = alarms; | ||
596 | + if (this.subscriptionTimewindow && this.subscriptionTimewindow.realtimeWindowMs) { | ||
597 | + this.updateTimewindow(); | ||
598 | + } | ||
599 | + this.onDataUpdated(apply); | ||
600 | + } | ||
601 | + | ||
519 | updateLegend(dataIndex, data, apply) { | 602 | updateLegend(dataIndex, data, apply) { |
520 | var dataKey = this.legendData.keys[dataIndex].dataKey; | 603 | var dataKey = this.legendData.keys[dataIndex].dataKey; |
521 | var decimals = angular.isDefined(dataKey.decimals) ? dataKey.decimals : this.decimals; | 604 | var decimals = angular.isDefined(dataKey.decimals) ? dataKey.decimals : this.decimals; |
@@ -536,66 +619,115 @@ export default class Subscription { | @@ -536,66 +619,115 @@ export default class Subscription { | ||
536 | this.callbacks.legendDataUpdated(this, apply !== false); | 619 | this.callbacks.legendDataUpdated(this, apply !== false); |
537 | } | 620 | } |
538 | 621 | ||
622 | + update() { | ||
623 | + this.unsubscribe(); | ||
624 | + this.subscribe(); | ||
625 | + } | ||
626 | + | ||
539 | subscribe() { | 627 | subscribe() { |
540 | if (this.type === this.ctx.types.widgetType.rpc.value) { | 628 | if (this.type === this.ctx.types.widgetType.rpc.value) { |
541 | return; | 629 | return; |
542 | } | 630 | } |
631 | + if (this.type === this.ctx.types.widgetType.alarm.value) { | ||
632 | + this.alarmsSubscribe(); | ||
633 | + } else { | ||
634 | + this.notifyDataLoading(); | ||
635 | + if (this.type === this.ctx.types.widgetType.timeseries.value && this.timeWindowConfig) { | ||
636 | + this.updateRealtimeSubscription(); | ||
637 | + if (this.subscriptionTimewindow.fixedWindow) { | ||
638 | + this.onDataUpdated(); | ||
639 | + } | ||
640 | + } | ||
641 | + var index = 0; | ||
642 | + for (var i = 0; i < this.datasources.length; i++) { | ||
643 | + var datasource = this.datasources[i]; | ||
644 | + if (angular.isFunction(datasource)) | ||
645 | + continue; | ||
646 | + | ||
647 | + var subscription = this; | ||
648 | + | ||
649 | + var listener = { | ||
650 | + subscriptionType: this.type, | ||
651 | + subscriptionTimewindow: this.subscriptionTimewindow, | ||
652 | + datasource: datasource, | ||
653 | + entityType: datasource.entityType, | ||
654 | + entityId: datasource.entityId, | ||
655 | + dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) { | ||
656 | + subscription.dataUpdated(data, datasourceIndex, dataKeyIndex, apply); | ||
657 | + }, | ||
658 | + updateRealtimeSubscription: function () { | ||
659 | + this.subscriptionTimewindow = subscription.updateRealtimeSubscription(); | ||
660 | + return this.subscriptionTimewindow; | ||
661 | + }, | ||
662 | + setRealtimeSubscription: function (subscriptionTimewindow) { | ||
663 | + subscription.updateRealtimeSubscription(angular.copy(subscriptionTimewindow)); | ||
664 | + }, | ||
665 | + datasourceIndex: index | ||
666 | + }; | ||
667 | + | ||
668 | + for (var a = 0; a < datasource.dataKeys.length; a++) { | ||
669 | + this.data[index + a].data = []; | ||
670 | + } | ||
671 | + | ||
672 | + index += datasource.dataKeys.length; | ||
673 | + | ||
674 | + this.datasourceListeners.push(listener); | ||
675 | + this.ctx.datasourceService.subscribeToDatasource(listener); | ||
676 | + if (datasource.unresolvedStateEntity) { | ||
677 | + this.notifyDataLoaded(); | ||
678 | + this.onDataUpdated(); | ||
679 | + } | ||
680 | + | ||
681 | + } | ||
682 | + } | ||
683 | + } | ||
684 | + | ||
685 | + alarmsSubscribe() { | ||
543 | this.notifyDataLoading(); | 686 | this.notifyDataLoading(); |
544 | - if (this.type === this.ctx.types.widgetType.timeseries.value && this.timeWindowConfig) { | 687 | + if (this.timeWindowConfig) { |
545 | this.updateRealtimeSubscription(); | 688 | this.updateRealtimeSubscription(); |
546 | if (this.subscriptionTimewindow.fixedWindow) { | 689 | if (this.subscriptionTimewindow.fixedWindow) { |
547 | this.onDataUpdated(); | 690 | this.onDataUpdated(); |
548 | } | 691 | } |
549 | } | 692 | } |
550 | - var index = 0; | ||
551 | - for (var i = 0; i < this.datasources.length; i++) { | ||
552 | - var datasource = this.datasources[i]; | ||
553 | - if (angular.isFunction(datasource)) | ||
554 | - continue; | ||
555 | - | ||
556 | - var subscription = this; | ||
557 | - | ||
558 | - var listener = { | ||
559 | - subscriptionType: this.type, | ||
560 | - subscriptionTimewindow: this.subscriptionTimewindow, | ||
561 | - datasource: datasource, | ||
562 | - entityType: datasource.entityType, | ||
563 | - entityId: datasource.entityId, | ||
564 | - dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) { | ||
565 | - subscription.dataUpdated(data, datasourceIndex, dataKeyIndex, apply); | ||
566 | - }, | ||
567 | - updateRealtimeSubscription: function () { | ||
568 | - this.subscriptionTimewindow = subscription.updateRealtimeSubscription(); | ||
569 | - return this.subscriptionTimewindow; | ||
570 | - }, | ||
571 | - setRealtimeSubscription: function (subscriptionTimewindow) { | ||
572 | - subscription.updateRealtimeSubscription(angular.copy(subscriptionTimewindow)); | ||
573 | - }, | ||
574 | - datasourceIndex: index | ||
575 | - }; | ||
576 | - | ||
577 | - for (var a = 0; a < datasource.dataKeys.length; a++) { | ||
578 | - this.data[index + a].data = []; | 693 | + var subscription = this; |
694 | + this.alarmSourceListener = { | ||
695 | + subscriptionTimewindow: this.subscriptionTimewindow, | ||
696 | + alarmSource: this.alarmSource, | ||
697 | + alarmSearchStatus: this.alarmSearchStatus, | ||
698 | + alarmsPollingInterval: this.alarmsPollingInterval, | ||
699 | + alarmsUpdated: function(alarms, apply) { | ||
700 | + subscription.alarmsUpdated(alarms, apply); | ||
579 | } | 701 | } |
702 | + } | ||
703 | + this.alarms = []; | ||
580 | 704 | ||
581 | - index += datasource.dataKeys.length; | 705 | + this.ctx.alarmService.subscribeForAlarms(this.alarmSourceListener); |
582 | 706 | ||
583 | - this.datasourceListeners.push(listener); | ||
584 | - this.ctx.datasourceService.subscribeToDatasource(listener); | ||
585 | - if (datasource.unresolvedStateEntity) { | ||
586 | - this.notifyDataLoaded(); | ||
587 | - this.onDataUpdated(); | ||
588 | - } | 707 | + if (this.alarmSource.unresolvedStateEntity) { |
708 | + this.notifyDataLoaded(); | ||
709 | + this.onDataUpdated(); | ||
589 | } | 710 | } |
590 | } | 711 | } |
591 | 712 | ||
592 | unsubscribe() { | 713 | unsubscribe() { |
593 | if (this.type !== this.ctx.types.widgetType.rpc.value) { | 714 | if (this.type !== this.ctx.types.widgetType.rpc.value) { |
594 | - for (var i = 0; i < this.datasourceListeners.length; i++) { | ||
595 | - var listener = this.datasourceListeners[i]; | ||
596 | - this.ctx.datasourceService.unsubscribeFromDatasource(listener); | 715 | + if (this.type == this.ctx.types.widgetType.alarm.value) { |
716 | + this.alarmsUnsubscribe(); | ||
717 | + } else { | ||
718 | + for (var i = 0; i < this.datasourceListeners.length; i++) { | ||
719 | + var listener = this.datasourceListeners[i]; | ||
720 | + this.ctx.datasourceService.unsubscribeFromDatasource(listener); | ||
721 | + } | ||
722 | + this.datasourceListeners = []; | ||
597 | } | 723 | } |
598 | - this.datasourceListeners = []; | 724 | + } |
725 | + } | ||
726 | + | ||
727 | + alarmsUnsubscribe() { | ||
728 | + if (this.alarmSourceListener) { | ||
729 | + this.ctx.alarmService.unsubscribeFromAlarms(this.alarmSourceListener); | ||
730 | + this.alarmSourceListener = null; | ||
599 | } | 731 | } |
600 | } | 732 | } |
601 | 733 | ||
@@ -607,6 +739,14 @@ export default class Subscription { | @@ -607,6 +739,14 @@ export default class Subscription { | ||
607 | } | 739 | } |
608 | } | 740 | } |
609 | 741 | ||
742 | + checkAlarmSource(aliasIds) { | ||
743 | + if (this.alarmSource && this.alarmSource.entityAliasId) { | ||
744 | + return aliasIds.indexOf(this.alarmSource.entityAliasId) > -1; | ||
745 | + } else { | ||
746 | + return false; | ||
747 | + } | ||
748 | + } | ||
749 | + | ||
610 | checkSubscriptions(aliasIds) { | 750 | checkSubscriptions(aliasIds) { |
611 | var subscriptionsChanged = false; | 751 | var subscriptionsChanged = false; |
612 | for (var i = 0; i < this.datasourceListeners.length; i++) { | 752 | for (var i = 0; i < this.datasourceListeners.length; i++) { |
@@ -19,6 +19,7 @@ import tinycolor from 'tinycolor2'; | @@ -19,6 +19,7 @@ import tinycolor from 'tinycolor2'; | ||
19 | 19 | ||
20 | import thingsboardLedLight from '../components/led-light.directive'; | 20 | import thingsboardLedLight from '../components/led-light.directive'; |
21 | import thingsboardTimeseriesTableWidget from '../widget/lib/timeseries-table-widget'; | 21 | import thingsboardTimeseriesTableWidget from '../widget/lib/timeseries-table-widget'; |
22 | +import thingsboardAlarmsTableWidget from '../widget/lib/alarms-table-widget'; | ||
22 | 23 | ||
23 | import TbFlot from '../widget/lib/flot-widget'; | 24 | import TbFlot from '../widget/lib/flot-widget'; |
24 | import TbAnalogueLinearGauge from '../widget/lib/analogue-linear-gauge'; | 25 | import TbAnalogueLinearGauge from '../widget/lib/analogue-linear-gauge'; |
@@ -33,7 +34,7 @@ import thingsboardTypes from '../common/types.constant'; | @@ -33,7 +34,7 @@ import thingsboardTypes from '../common/types.constant'; | ||
33 | import thingsboardUtils from '../common/utils.service'; | 34 | import thingsboardUtils from '../common/utils.service'; |
34 | 35 | ||
35 | export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, thingsboardTimeseriesTableWidget, | 36 | export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, thingsboardTimeseriesTableWidget, |
36 | - thingsboardTypes, thingsboardUtils]) | 37 | + thingsboardAlarmsTableWidget, thingsboardTypes, thingsboardUtils]) |
37 | .factory('widgetService', WidgetService) | 38 | .factory('widgetService', WidgetService) |
38 | .name; | 39 | .name; |
39 | 40 |
@@ -31,7 +31,7 @@ | @@ -31,7 +31,7 @@ | ||
31 | on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, isPublic)" | 31 | on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, isPublic)" |
32 | on-delete-asset="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-asset> | 32 | on-delete-asset="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-asset> |
33 | </md-tab> | 33 | </md-tab> |
34 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'attribute.attributes' | translate }}"> | 34 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}"> |
35 | <tb-attribute-table flex | 35 | <tb-attribute-table flex |
36 | entity-id="vm.grid.operatingItem().id.id" | 36 | entity-id="vm.grid.operatingItem().id.id" |
37 | entity-type="{{vm.types.entityType.asset}}" | 37 | entity-type="{{vm.types.entityType.asset}}" |
@@ -39,7 +39,7 @@ | @@ -39,7 +39,7 @@ | ||
39 | default-attribute-scope="{{vm.types.attributesScope.server.value}}"> | 39 | default-attribute-scope="{{vm.types.attributesScope.server.value}}"> |
40 | </tb-attribute-table> | 40 | </tb-attribute-table> |
41 | </md-tab> | 41 | </md-tab> |
42 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'attribute.latest-telemetry' | translate }}"> | 42 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.latest-telemetry' | translate }}"> |
43 | <tb-attribute-table flex | 43 | <tb-attribute-table flex |
44 | entity-id="vm.grid.operatingItem().id.id" | 44 | entity-id="vm.grid.operatingItem().id.id" |
45 | entity-type="{{vm.types.entityType.asset}}" | 45 | entity-type="{{vm.types.entityType.asset}}" |
@@ -48,19 +48,19 @@ | @@ -48,19 +48,19 @@ | ||
48 | disable-attribute-scope-selection="true"> | 48 | disable-attribute-scope-selection="true"> |
49 | </tb-attribute-table> | 49 | </tb-attribute-table> |
50 | </md-tab> | 50 | </md-tab> |
51 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'alarm.alarms' | translate }}"> | 51 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}"> |
52 | <tb-alarm-table flex entity-type="vm.types.entityType.asset" | 52 | <tb-alarm-table flex entity-type="vm.types.entityType.asset" |
53 | entity-id="vm.grid.operatingItem().id.id"> | 53 | entity-id="vm.grid.operatingItem().id.id"> |
54 | </tb-alarm-table> | 54 | </tb-alarm-table> |
55 | </md-tab> | 55 | </md-tab> |
56 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'asset.events' | translate }}"> | 56 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'asset.events' | translate }}"> |
57 | <tb-event-table flex entity-type="vm.types.entityType.asset" | 57 | <tb-event-table flex entity-type="vm.types.entityType.asset" |
58 | entity-id="vm.grid.operatingItem().id.id" | 58 | entity-id="vm.grid.operatingItem().id.id" |
59 | tenant-id="vm.grid.operatingItem().tenantId.id" | 59 | tenant-id="vm.grid.operatingItem().tenantId.id" |
60 | default-event-type="{{vm.types.eventType.error.value}}"> | 60 | default-event-type="{{vm.types.eventType.error.value}}"> |
61 | </tb-event-table> | 61 | </tb-event-table> |
62 | </md-tab> | 62 | </md-tab> |
63 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}"> | 63 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'relation.relations' | translate }}"> |
64 | <tb-relation-table flex | 64 | <tb-relation-table flex |
65 | entity-id="vm.grid.operatingItem().id.id" | 65 | entity-id="vm.grid.operatingItem().id.id" |
66 | entity-type="{{vm.types.entityType.asset}}"> | 66 | entity-type="{{vm.types.entityType.asset}}"> |
@@ -59,6 +59,53 @@ export default angular.module('thingsboard.types', []) | @@ -59,6 +59,53 @@ export default angular.module('thingsboard.types', []) | ||
59 | name: "aggregation.none" | 59 | name: "aggregation.none" |
60 | } | 60 | } |
61 | }, | 61 | }, |
62 | + alarmFields: { | ||
63 | + createdTime: { | ||
64 | + value: "createdTime", | ||
65 | + name: "alarm.created-time", | ||
66 | + time: true | ||
67 | + }, | ||
68 | + startTime: { | ||
69 | + value: "startTs", | ||
70 | + name: "alarm.start-time", | ||
71 | + time: true | ||
72 | + }, | ||
73 | + endTime: { | ||
74 | + value: "endTs", | ||
75 | + name: "alarm.end-time", | ||
76 | + time: true | ||
77 | + }, | ||
78 | + ackTime: { | ||
79 | + value: "ackTs", | ||
80 | + name: "alarm.ack-time", | ||
81 | + time: true | ||
82 | + }, | ||
83 | + clearTime: { | ||
84 | + value: "clearTs", | ||
85 | + name: "alarm.clear-time", | ||
86 | + time: true | ||
87 | + }, | ||
88 | + originator: { | ||
89 | + value: "originatorName", | ||
90 | + name: "alarm.originator" | ||
91 | + }, | ||
92 | + originatorType: { | ||
93 | + value: "originator.entityType", | ||
94 | + name: "alarm.originator-type" | ||
95 | + }, | ||
96 | + type: { | ||
97 | + value: "type", | ||
98 | + name: "alarm.type" | ||
99 | + }, | ||
100 | + severity: { | ||
101 | + value: "severity", | ||
102 | + name: "alarm.severity" | ||
103 | + }, | ||
104 | + status: { | ||
105 | + value: "status", | ||
106 | + name: "alarm.status" | ||
107 | + } | ||
108 | + }, | ||
62 | alarmStatus: { | 109 | alarmStatus: { |
63 | activeUnack: "ACTIVE_UNACK", | 110 | activeUnack: "ACTIVE_UNACK", |
64 | activeAck: "ACTIVE_ACK", | 111 | activeAck: "ACTIVE_ACK", |
@@ -75,23 +122,28 @@ export default angular.module('thingsboard.types', []) | @@ -75,23 +122,28 @@ export default angular.module('thingsboard.types', []) | ||
75 | alarmSeverity: { | 122 | alarmSeverity: { |
76 | "CRITICAL": { | 123 | "CRITICAL": { |
77 | name: "alarm.severity-critical", | 124 | name: "alarm.severity-critical", |
78 | - class: "tb-critical" | 125 | + class: "tb-critical", |
126 | + color: "red" | ||
79 | }, | 127 | }, |
80 | "MAJOR": { | 128 | "MAJOR": { |
81 | name: "alarm.severity-major", | 129 | name: "alarm.severity-major", |
82 | - class: "tb-major" | 130 | + class: "tb-major", |
131 | + color: "orange" | ||
83 | }, | 132 | }, |
84 | "MINOR": { | 133 | "MINOR": { |
85 | name: "alarm.severity-minor", | 134 | name: "alarm.severity-minor", |
86 | - class: "tb-minor" | 135 | + class: "tb-minor", |
136 | + color: "#ffca3d" | ||
87 | }, | 137 | }, |
88 | "WARNING": { | 138 | "WARNING": { |
89 | name: "alarm.severity-warning", | 139 | name: "alarm.severity-warning", |
90 | - class: "tb-warning" | 140 | + class: "tb-warning", |
141 | + color: "#abab00" | ||
91 | }, | 142 | }, |
92 | "INDETERMINATE": { | 143 | "INDETERMINATE": { |
93 | name: "alarm.severity-indeterminate", | 144 | name: "alarm.severity-indeterminate", |
94 | - class: "tb-indeterminate" | 145 | + class: "tb-indeterminate", |
146 | + color: "green" | ||
95 | } | 147 | } |
96 | }, | 148 | }, |
97 | aliasFilterType: { | 149 | aliasFilterType: { |
@@ -153,7 +205,8 @@ export default angular.module('thingsboard.types', []) | @@ -153,7 +205,8 @@ export default angular.module('thingsboard.types', []) | ||
153 | dataKeyType: { | 205 | dataKeyType: { |
154 | timeseries: "timeseries", | 206 | timeseries: "timeseries", |
155 | attribute: "attribute", | 207 | attribute: "attribute", |
156 | - function: "function" | 208 | + function: "function", |
209 | + alarm: "alarm" | ||
157 | }, | 210 | }, |
158 | componentType: { | 211 | componentType: { |
159 | filter: "FILTER", | 212 | filter: "FILTER", |
@@ -319,6 +372,14 @@ export default angular.module('thingsboard.types', []) | @@ -319,6 +372,14 @@ export default angular.module('thingsboard.types', []) | ||
319 | alias: "basic_gpio_control" | 372 | alias: "basic_gpio_control" |
320 | } | 373 | } |
321 | }, | 374 | }, |
375 | + alarm: { | ||
376 | + value: "alarm", | ||
377 | + name: "widget.alarm", | ||
378 | + template: { | ||
379 | + bundleAlias: "alarm_widgets", | ||
380 | + alias: "alarms_table" | ||
381 | + } | ||
382 | + }, | ||
322 | static: { | 383 | static: { |
323 | value: "static", | 384 | value: "static", |
324 | name: "widget.static", | 385 | name: "widget.static", |
@@ -333,7 +394,7 @@ export default angular.module('thingsboard.types', []) | @@ -333,7 +394,7 @@ export default angular.module('thingsboard.types', []) | ||
333 | cards: "cards" | 394 | cards: "cards" |
334 | }, | 395 | }, |
335 | translate: { | 396 | translate: { |
336 | - dashboardStatePrefix: "dashboardState.state." | 397 | + customTranslationsPrefix: "custom." |
337 | } | 398 | } |
338 | } | 399 | } |
339 | ).name; | 400 | ).name; |
@@ -21,8 +21,10 @@ export default angular.module('thingsboard.utils', [thingsboardTypes]) | @@ -21,8 +21,10 @@ export default angular.module('thingsboard.utils', [thingsboardTypes]) | ||
21 | .factory('utils', Utils) | 21 | .factory('utils', Utils) |
22 | .name; | 22 | .name; |
23 | 23 | ||
24 | +const varsRegex = /\$\{([^\}]*)\}/g; | ||
25 | + | ||
24 | /*@ngInject*/ | 26 | /*@ngInject*/ |
25 | -function Utils($mdColorPalette, $rootScope, $window, types) { | 27 | +function Utils($mdColorPalette, $rootScope, $window, $translate, types) { |
26 | 28 | ||
27 | var predefinedFunctions = {}, | 29 | var predefinedFunctions = {}, |
28 | predefinedFunctionsList = [], | 30 | predefinedFunctionsList = [], |
@@ -93,9 +95,32 @@ function Utils($mdColorPalette, $rootScope, $window, types) { | @@ -93,9 +95,32 @@ function Utils($mdColorPalette, $rootScope, $window, types) { | ||
93 | dataKeys: [angular.copy(defaultDataKey)] | 95 | dataKeys: [angular.copy(defaultDataKey)] |
94 | }; | 96 | }; |
95 | 97 | ||
98 | + var defaultAlarmFields = [ | ||
99 | + 'createdTime', | ||
100 | + 'originator', | ||
101 | + 'type', | ||
102 | + 'severity', | ||
103 | + 'status' | ||
104 | + ]; | ||
105 | + | ||
106 | + var defaultAlarmDataKeys = []; | ||
107 | + for (var i=0;i<defaultAlarmFields.length;i++) { | ||
108 | + var name = defaultAlarmFields[i]; | ||
109 | + var dataKey = { | ||
110 | + name: name, | ||
111 | + type: types.dataKeyType.alarm, | ||
112 | + label: $translate.instant(types.alarmFields[name].name)+'', | ||
113 | + color: getMaterialColor(i), | ||
114 | + settings: {}, | ||
115 | + _hash: Math.random() | ||
116 | + }; | ||
117 | + defaultAlarmDataKeys.push(dataKey); | ||
118 | + } | ||
119 | + | ||
96 | var service = { | 120 | var service = { |
97 | getDefaultDatasource: getDefaultDatasource, | 121 | getDefaultDatasource: getDefaultDatasource, |
98 | getDefaultDatasourceJson: getDefaultDatasourceJson, | 122 | getDefaultDatasourceJson: getDefaultDatasourceJson, |
123 | + getDefaultAlarmDataKeys: getDefaultAlarmDataKeys, | ||
99 | getMaterialColor: getMaterialColor, | 124 | getMaterialColor: getMaterialColor, |
100 | getPredefinedFunctionBody: getPredefinedFunctionBody, | 125 | getPredefinedFunctionBody: getPredefinedFunctionBody, |
101 | getPredefinedFunctionsList: getPredefinedFunctionsList, | 126 | getPredefinedFunctionsList: getPredefinedFunctionsList, |
@@ -109,7 +134,9 @@ function Utils($mdColorPalette, $rootScope, $window, types) { | @@ -109,7 +134,9 @@ function Utils($mdColorPalette, $rootScope, $window, types) { | ||
109 | cleanCopy: cleanCopy, | 134 | cleanCopy: cleanCopy, |
110 | isLocalUrl: isLocalUrl, | 135 | isLocalUrl: isLocalUrl, |
111 | validateDatasources: validateDatasources, | 136 | validateDatasources: validateDatasources, |
112 | - createKey: createKey | 137 | + createKey: createKey, |
138 | + createLabelFromDatasource: createLabelFromDatasource, | ||
139 | + insertVariable: insertVariable | ||
113 | } | 140 | } |
114 | 141 | ||
115 | return service; | 142 | return service; |
@@ -212,6 +239,10 @@ function Utils($mdColorPalette, $rootScope, $window, types) { | @@ -212,6 +239,10 @@ function Utils($mdColorPalette, $rootScope, $window, types) { | ||
212 | return angular.toJson(getDefaultDatasource(dataKeySchema)); | 239 | return angular.toJson(getDefaultDatasource(dataKeySchema)); |
213 | } | 240 | } |
214 | 241 | ||
242 | + function getDefaultAlarmDataKeys() { | ||
243 | + return angular.copy(defaultAlarmDataKeys); | ||
244 | + } | ||
245 | + | ||
215 | function isDescriptorSchemaNotEmpty(descriptor) { | 246 | function isDescriptorSchemaNotEmpty(descriptor) { |
216 | if (descriptor && descriptor.schema && descriptor.schema.properties) { | 247 | if (descriptor && descriptor.schema && descriptor.schema.properties) { |
217 | for(var prop in descriptor.schema.properties) { | 248 | for(var prop in descriptor.schema.properties) { |
@@ -357,4 +388,38 @@ function Utils($mdColorPalette, $rootScope, $window, types) { | @@ -357,4 +388,38 @@ function Utils($mdColorPalette, $rootScope, $window, types) { | ||
357 | return dataKey; | 388 | return dataKey; |
358 | } | 389 | } |
359 | 390 | ||
391 | + function createLabelFromDatasource(datasource, pattern) { | ||
392 | + var label = angular.copy(pattern); | ||
393 | + var match = varsRegex.exec(pattern); | ||
394 | + while (match !== null) { | ||
395 | + var variable = match[0]; | ||
396 | + var variableName = match[1]; | ||
397 | + if (variableName === 'dsName') { | ||
398 | + label = label.split(variable).join(datasource.name); | ||
399 | + } else if (variableName === 'entityName') { | ||
400 | + label = label.split(variable).join(datasource.entityName); | ||
401 | + } else if (variableName === 'deviceName') { | ||
402 | + label = label.split(variable).join(datasource.entityName); | ||
403 | + } else if (variableName === 'aliasName') { | ||
404 | + label = label.split(variable).join(datasource.aliasName); | ||
405 | + } | ||
406 | + match = varsRegex.exec(pattern); | ||
407 | + } | ||
408 | + return label; | ||
409 | + } | ||
410 | + | ||
411 | + function insertVariable(pattern, name, value) { | ||
412 | + var result = angular.copy(pattern); | ||
413 | + var match = varsRegex.exec(pattern); | ||
414 | + while (match !== null) { | ||
415 | + var variable = match[0]; | ||
416 | + var variableName = match[1]; | ||
417 | + if (variableName === name) { | ||
418 | + result = result.split(variable).join(value); | ||
419 | + } | ||
420 | + match = varsRegex.exec(pattern); | ||
421 | + } | ||
422 | + return result; | ||
423 | + } | ||
424 | + | ||
360 | } | 425 | } |
@@ -58,6 +58,7 @@ function Dashboard() { | @@ -58,6 +58,7 @@ function Dashboard() { | ||
58 | columns: '=', | 58 | columns: '=', |
59 | margins: '=', | 59 | margins: '=', |
60 | isEdit: '=', | 60 | isEdit: '=', |
61 | + autofillHeight: '=', | ||
61 | isMobile: '=', | 62 | isMobile: '=', |
62 | isMobileDisabled: '=?', | 63 | isMobileDisabled: '=?', |
63 | isEditActionEnabled: '=', | 64 | isEditActionEnabled: '=', |
@@ -102,6 +103,8 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -102,6 +103,8 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
102 | 103 | ||
103 | vm.isMobileDisabled = angular.isDefined(vm.isMobileDisabled) ? vm.isMobileDisabled : false; | 104 | vm.isMobileDisabled = angular.isDefined(vm.isMobileDisabled) ? vm.isMobileDisabled : false; |
104 | 105 | ||
106 | + vm.isMobileSize = false; | ||
107 | + | ||
105 | if (!('dashboardTimewindow' in vm)) { | 108 | if (!('dashboardTimewindow' in vm)) { |
106 | vm.dashboardTimewindow = timeService.defaultTimewindow(); | 109 | vm.dashboardTimewindow = timeService.defaultTimewindow(); |
107 | } | 110 | } |
@@ -177,10 +180,15 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -177,10 +180,15 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
177 | vm.widgetBackgroundColor = widgetBackgroundColor; | 180 | vm.widgetBackgroundColor = widgetBackgroundColor; |
178 | vm.widgetPadding = widgetPadding; | 181 | vm.widgetPadding = widgetPadding; |
179 | vm.showWidgetTitle = showWidgetTitle; | 182 | vm.showWidgetTitle = showWidgetTitle; |
183 | + vm.showWidgetTitlePanel = showWidgetTitlePanel; | ||
184 | + vm.showWidgetActions = showWidgetActions; | ||
180 | vm.widgetTitleStyle = widgetTitleStyle; | 185 | vm.widgetTitleStyle = widgetTitleStyle; |
186 | + vm.widgetTitle = widgetTitle; | ||
187 | + vm.widgetActions = widgetActions; | ||
181 | vm.dropWidgetShadow = dropWidgetShadow; | 188 | vm.dropWidgetShadow = dropWidgetShadow; |
182 | vm.enableWidgetFullscreen = enableWidgetFullscreen; | 189 | vm.enableWidgetFullscreen = enableWidgetFullscreen; |
183 | vm.hasTimewindow = hasTimewindow; | 190 | vm.hasTimewindow = hasTimewindow; |
191 | + vm.hasAggregation = hasAggregation; | ||
184 | vm.editWidget = editWidget; | 192 | vm.editWidget = editWidget; |
185 | vm.exportWidget = exportWidget; | 193 | vm.exportWidget = exportWidget; |
186 | vm.removeWidget = removeWidget; | 194 | vm.removeWidget = removeWidget; |
@@ -267,12 +275,15 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -267,12 +275,15 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
267 | 275 | ||
268 | function updateMobileOpts() { | 276 | function updateMobileOpts() { |
269 | var isMobileDisabled = vm.isMobileDisabled === true; | 277 | var isMobileDisabled = vm.isMobileDisabled === true; |
270 | - var isMobile = vm.isMobile === true && !isMobileDisabled; | 278 | + var isMobile = vm.isMobile === true && !isMobileDisabled || vm.autofillHeight; |
271 | var mobileBreakPoint = isMobileDisabled ? 0 : (isMobile ? 20000 : 960); | 279 | var mobileBreakPoint = isMobileDisabled ? 0 : (isMobile ? 20000 : 960); |
280 | + | ||
272 | if (!isMobile && !isMobileDisabled) { | 281 | if (!isMobile && !isMobileDisabled) { |
273 | isMobile = !$mdMedia('gt-sm'); | 282 | isMobile = !$mdMedia('gt-sm'); |
274 | } | 283 | } |
275 | - var rowHeight = isMobile ? 70 : 'match'; | 284 | + |
285 | + var rowHeight = detectRowSize(isMobile); | ||
286 | + | ||
276 | if (vm.gridsterOpts.isMobile != isMobile) { | 287 | if (vm.gridsterOpts.isMobile != isMobile) { |
277 | vm.gridsterOpts.isMobile = isMobile; | 288 | vm.gridsterOpts.isMobile = isMobile; |
278 | vm.gridsterOpts.mobileModeEnabled = isMobile; | 289 | vm.gridsterOpts.mobileModeEnabled = isMobile; |
@@ -283,6 +294,17 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -283,6 +294,17 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
283 | if (vm.gridsterOpts.rowHeight != rowHeight) { | 294 | if (vm.gridsterOpts.rowHeight != rowHeight) { |
284 | vm.gridsterOpts.rowHeight = rowHeight; | 295 | vm.gridsterOpts.rowHeight = rowHeight; |
285 | } | 296 | } |
297 | + | ||
298 | + vm.isMobileSize = checkIsMobileSize(); | ||
299 | + } | ||
300 | + | ||
301 | + function checkIsMobileSize() { | ||
302 | + var isMobileDisabled = vm.isMobileDisabled === true; | ||
303 | + var isMobileSize = vm.isMobile === true && !isMobileDisabled; | ||
304 | + if (!isMobileSize && !isMobileDisabled) { | ||
305 | + isMobileSize = !$mdMedia('gt-sm'); | ||
306 | + } | ||
307 | + return isMobileSize; | ||
286 | } | 308 | } |
287 | 309 | ||
288 | $scope.$watch(function() { return $mdMedia('gt-sm'); }, function() { | 310 | $scope.$watch(function() { return $mdMedia('gt-sm'); }, function() { |
@@ -293,6 +315,34 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -293,6 +315,34 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
293 | updateMobileOpts(); | 315 | updateMobileOpts(); |
294 | }); | 316 | }); |
295 | 317 | ||
318 | + $scope.$watch('vm.autofillHeight', function () { | ||
319 | + if (vm.autofillHeight) { | ||
320 | + //if (gridsterParent.height()) { | ||
321 | + // updateMobileOpts(); | ||
322 | + //} else { | ||
323 | + if ($scope.parentHeighWatcher) { | ||
324 | + $scope.parentHeighWatcher(); | ||
325 | + } | ||
326 | + if (gridsterParent.height()) { | ||
327 | + updateMobileOpts(); | ||
328 | + } | ||
329 | + $scope.parentHeighWatcher = $scope.$watch(function() { return gridsterParent.height(); }, | ||
330 | + function(newHeight) { | ||
331 | + if (newHeight) { | ||
332 | + updateMobileOpts(); | ||
333 | + } | ||
334 | + } | ||
335 | + ); | ||
336 | + } else { | ||
337 | + if ($scope.parentHeighWatcher) { | ||
338 | + $scope.parentHeighWatcher(); | ||
339 | + $scope.parentHeighWatcher = null; | ||
340 | + } | ||
341 | + | ||
342 | + updateMobileOpts(); | ||
343 | + } | ||
344 | + }); | ||
345 | + | ||
296 | $scope.$watch('vm.isMobileDisabled', function () { | 346 | $scope.$watch('vm.isMobileDisabled', function () { |
297 | updateMobileOpts(); | 347 | updateMobileOpts(); |
298 | }); | 348 | }); |
@@ -329,6 +379,12 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -329,6 +379,12 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
329 | $scope.$broadcast('toggleDashboardEditMode', vm.isEdit); | 379 | $scope.$broadcast('toggleDashboardEditMode', vm.isEdit); |
330 | }); | 380 | }); |
331 | 381 | ||
382 | + $scope.$watch('vm.isMobileSize', function (newVal, prevVal) { | ||
383 | + if (!angular.equals(newVal, prevVal)) { | ||
384 | + $scope.$broadcast('mobileModeChanged', vm.isMobileSize); | ||
385 | + } | ||
386 | + }); | ||
387 | + | ||
332 | $scope.$on('gridster-resized', function (event, sizes, theGridster) { | 388 | $scope.$on('gridster-resized', function (event, sizes, theGridster) { |
333 | if (checkIsLocalGridsterElement(theGridster)) { | 389 | if (checkIsLocalGridsterElement(theGridster)) { |
334 | vm.gridster = theGridster; | 390 | vm.gridster = theGridster; |
@@ -341,13 +397,12 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -341,13 +397,12 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
341 | $scope.$on('gridster-mobile-changed', function (event, theGridster) { | 397 | $scope.$on('gridster-mobile-changed', function (event, theGridster) { |
342 | if (checkIsLocalGridsterElement(theGridster)) { | 398 | if (checkIsLocalGridsterElement(theGridster)) { |
343 | vm.gridster = theGridster; | 399 | vm.gridster = theGridster; |
344 | - var rowHeight = vm.gridster.isMobile ? 70 : 'match'; | 400 | + var rowHeight = detectRowSize(vm.gridster.isMobile); |
345 | if (vm.gridsterOpts.rowHeight != rowHeight) { | 401 | if (vm.gridsterOpts.rowHeight != rowHeight) { |
346 | vm.gridsterOpts.rowHeight = rowHeight; | 402 | vm.gridsterOpts.rowHeight = rowHeight; |
347 | updateGridsterParams(); | 403 | updateGridsterParams(); |
348 | } | 404 | } |
349 | - | ||
350 | - $scope.$broadcast('mobileModeChanged', vm.gridster.isMobile); | 405 | + vm.isMobileSize = checkIsMobileSize(); |
351 | 406 | ||
352 | //TODO: widgets visibility | 407 | //TODO: widgets visibility |
353 | /*$timeout(function () { | 408 | /*$timeout(function () { |
@@ -356,6 +411,21 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -356,6 +411,21 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
356 | } | 411 | } |
357 | }); | 412 | }); |
358 | 413 | ||
414 | + function detectRowSize(isMobile) { | ||
415 | + var rowHeight = isMobile ? 70 : 'match'; | ||
416 | + if (vm.autofillHeight) { | ||
417 | + var viewportHeight = gridsterParent.height(); | ||
418 | + var totalRows = 0; | ||
419 | + for (var i = 0; i < vm.widgets.length; i++) { | ||
420 | + var w = vm.widgets[i]; | ||
421 | + var sizeY = widgetSizeY(w); | ||
422 | + totalRows += sizeY; | ||
423 | + } | ||
424 | + rowHeight = (viewportHeight - (vm.gridsterOpts.margins[1])) / totalRows; | ||
425 | + } | ||
426 | + return rowHeight; | ||
427 | + } | ||
428 | + | ||
359 | function widgetOrder(widget) { | 429 | function widgetOrder(widget) { |
360 | var order; | 430 | var order; |
361 | if (vm.widgetLayouts && vm.widgetLayouts[widget.id]) { | 431 | if (vm.widgetLayouts && vm.widgetLayouts[widget.id]) { |
@@ -646,7 +716,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -646,7 +716,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
646 | } | 716 | } |
647 | 717 | ||
648 | function widgetSizeY(widget) { | 718 | function widgetSizeY(widget) { |
649 | - if (vm.gridsterOpts.isMobile) { | 719 | + if (vm.gridsterOpts.isMobile && !vm.autofillHeight) { |
650 | var mobileHeight; | 720 | var mobileHeight; |
651 | if (vm.widgetLayouts && vm.widgetLayouts[widget.id]) { | 721 | if (vm.widgetLayouts && vm.widgetLayouts[widget.id]) { |
652 | mobileHeight = vm.widgetLayouts[widget.id].mobileHeight; | 722 | mobileHeight = vm.widgetLayouts[widget.id].mobileHeight; |
@@ -669,7 +739,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -669,7 +739,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
669 | } | 739 | } |
670 | 740 | ||
671 | function setWidgetSizeY(widget, sizeY) { | 741 | function setWidgetSizeY(widget, sizeY) { |
672 | - if (!vm.gridsterOpts.isMobile) { | 742 | + if (!vm.gridsterOpts.isMobile && !vm.autofillHeight) { |
673 | if (vm.widgetLayouts && vm.widgetLayouts[widget.id]) { | 743 | if (vm.widgetLayouts && vm.widgetLayouts[widget.id]) { |
674 | vm.widgetLayouts[widget.id].sizeY = sizeY; | 744 | vm.widgetLayouts[widget.id].sizeY = sizeY; |
675 | } else { | 745 | } else { |
@@ -746,6 +816,24 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -746,6 +816,24 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
746 | } | 816 | } |
747 | } | 817 | } |
748 | 818 | ||
819 | + function showWidgetTitlePanel(widget) { | ||
820 | + var ctx = widgetContext(widget); | ||
821 | + if (ctx && ctx.hideTitlePanel) { | ||
822 | + return false; | ||
823 | + } else { | ||
824 | + return showWidgetTitle(widget) || hasTimewindow(widget); | ||
825 | + } | ||
826 | + } | ||
827 | + | ||
828 | + function showWidgetActions(widget) { | ||
829 | + var ctx = widgetContext(widget); | ||
830 | + if (ctx && ctx.hideTitlePanel) { | ||
831 | + return false; | ||
832 | + } else { | ||
833 | + return true; | ||
834 | + } | ||
835 | + } | ||
836 | + | ||
749 | function widgetTitleStyle(widget) { | 837 | function widgetTitleStyle(widget) { |
750 | if (angular.isDefined(widget.config.titleStyle)) { | 838 | if (angular.isDefined(widget.config.titleStyle)) { |
751 | return widget.config.titleStyle; | 839 | return widget.config.titleStyle; |
@@ -754,6 +842,33 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -754,6 +842,33 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
754 | } | 842 | } |
755 | } | 843 | } |
756 | 844 | ||
845 | + function widgetTitle(widget) { | ||
846 | + var ctx = widgetContext(widget); | ||
847 | + if (ctx && ctx.widgetTitle | ||
848 | + && ctx.widgetTitle.length) { | ||
849 | + return ctx.widgetTitle; | ||
850 | + } else { | ||
851 | + return widget.config.title; | ||
852 | + } | ||
853 | + } | ||
854 | + | ||
855 | + function widgetActions(widget) { | ||
856 | + var ctx = widgetContext(widget); | ||
857 | + if (ctx && ctx.widgetActions && ctx.widgetActions.length) { | ||
858 | + return ctx.widgetActions; | ||
859 | + } else { | ||
860 | + return []; | ||
861 | + } | ||
862 | + } | ||
863 | + | ||
864 | + function widgetContext(widget) { | ||
865 | + var context; | ||
866 | + if (widget.$ctx) { | ||
867 | + context = widget.$ctx(); | ||
868 | + } | ||
869 | + return context; | ||
870 | + } | ||
871 | + | ||
757 | function dropWidgetShadow(widget) { | 872 | function dropWidgetShadow(widget) { |
758 | if (angular.isDefined(widget.config.dropShadow)) { | 873 | if (angular.isDefined(widget.config.dropShadow)) { |
759 | return widget.config.dropShadow; | 874 | return widget.config.dropShadow; |
@@ -771,7 +886,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -771,7 +886,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
771 | } | 886 | } |
772 | 887 | ||
773 | function hasTimewindow(widget) { | 888 | function hasTimewindow(widget) { |
774 | - if (widget.type === types.widgetType.timeseries.value) { | 889 | + if (widget.type === types.widgetType.timeseries.value || widget.type === types.widgetType.alarm.value) { |
775 | return angular.isDefined(widget.config.useDashboardTimewindow) ? | 890 | return angular.isDefined(widget.config.useDashboardTimewindow) ? |
776 | !widget.config.useDashboardTimewindow : false; | 891 | !widget.config.useDashboardTimewindow : false; |
777 | } else { | 892 | } else { |
@@ -779,6 +894,10 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -779,6 +894,10 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
779 | } | 894 | } |
780 | } | 895 | } |
781 | 896 | ||
897 | + function hasAggregation(widget) { | ||
898 | + return widget.type === types.widgetType.timeseries.value; | ||
899 | + } | ||
900 | + | ||
782 | function adoptMaxRows() { | 901 | function adoptMaxRows() { |
783 | if (vm.widgets) { | 902 | if (vm.widgets) { |
784 | var maxRows = vm.gridsterOpts.maxRows; | 903 | var maxRows = vm.gridsterOpts.maxRows; |
@@ -23,7 +23,7 @@ | @@ -23,7 +23,7 @@ | ||
23 | </md-content> | 23 | </md-content> |
24 | <md-menu md-position-mode="target target" tb-mousepoint-menu> | 24 | <md-menu md-position-mode="target target" tb-mousepoint-menu> |
25 | <md-content id="gridster-parent" class="tb-dashboard-content" flex layout-wrap ng-click="" tb-contextmenu="vm.openDashboardContextMenu($event, $mdOpenMousepointMenu)"> | 25 | <md-content id="gridster-parent" class="tb-dashboard-content" flex layout-wrap ng-click="" tb-contextmenu="vm.openDashboardContextMenu($event, $mdOpenMousepointMenu)"> |
26 | - <div ng-class="vm.dashboardClass" id="gridster-background" style="height: auto; min-height: 100%;"> | 26 | + <div ng-class="vm.dashboardClass" id="gridster-background" style="height: auto; min-height: 100%; display: inline;"> |
27 | <div id="gridster-child" gridster="vm.gridsterOpts"> | 27 | <div id="gridster-child" gridster="vm.gridsterOpts"> |
28 | <ul> | 28 | <ul> |
29 | <li gridster-item="vm.widgetItemMap" class="tb-noselect" ng-repeat="widget in vm.widgets"> | 29 | <li gridster-item="vm.widgetItemMap" class="tb-noselect" ng-repeat="widget in vm.widgets"> |
@@ -37,7 +37,8 @@ | @@ -37,7 +37,8 @@ | ||
37 | class="tb-widget" | 37 | class="tb-widget" |
38 | ng-class="{'tb-highlighted': vm.isHighlighted(widget), | 38 | ng-class="{'tb-highlighted': vm.isHighlighted(widget), |
39 | 'tb-not-highlighted': vm.isNotHighlighted(widget), | 39 | 'tb-not-highlighted': vm.isNotHighlighted(widget), |
40 | - 'md-whiteframe-4dp': vm.dropWidgetShadow(widget)}" | 40 | + 'md-whiteframe-4dp': vm.dropWidgetShadow(widget), |
41 | + 'tb-has-timewindow': vm.hasTimewindow(widget)}" | ||
41 | tb-mousedown="vm.widgetMouseDown($event, widget)" | 42 | tb-mousedown="vm.widgetMouseDown($event, widget)" |
42 | ng-click="vm.widgetClicked($event, widget)" | 43 | ng-click="vm.widgetClicked($event, widget)" |
43 | tb-contextmenu="vm.openWidgetContextMenu($event, widget, $mdOpenMousepointMenu)" | 44 | tb-contextmenu="vm.openWidgetContextMenu($event, widget, $mdOpenMousepointMenu)" |
@@ -45,11 +46,21 @@ | @@ -45,11 +46,21 @@ | ||
45 | color: vm.widgetColor(widget), | 46 | color: vm.widgetColor(widget), |
46 | backgroundColor: vm.widgetBackgroundColor(widget), | 47 | backgroundColor: vm.widgetBackgroundColor(widget), |
47 | padding: vm.widgetPadding(widget)}"> | 48 | padding: vm.widgetPadding(widget)}"> |
48 | - <div class="tb-widget-title" layout="column" layout-align="center start" ng-show="vm.showWidgetTitle(widget) || vm.hasTimewindow(widget)"> | ||
49 | - <span ng-show="vm.showWidgetTitle(widget)" ng-style="vm.widgetTitleStyle(widget)" class="md-subhead">{{widget.config.title}}</span> | ||
50 | - <tb-timewindow aggregation ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow> | 49 | + <div class="tb-widget-title" layout="column" layout-align="center start" ng-show="vm.showWidgetTitlePanel(widget)"> |
50 | + <span ng-show="vm.showWidgetTitle(widget)" ng-style="vm.widgetTitleStyle(widget)" class="md-subhead">{{vm.widgetTitle(widget)}}</span> | ||
51 | + <tb-timewindow aggregation="{{vm.hasAggregation(widget)}}" ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow> | ||
51 | </div> | 52 | </div> |
52 | - <div class="tb-widget-actions" layout="row" layout-align="start center" tb-mousedown="$event.stopPropagation()"> | 53 | + <div class="tb-widget-actions" layout="row" layout-align="start center" ng-show="vm.showWidgetActions(widget)" tb-mousedown="$event.stopPropagation()"> |
54 | + <md-button ng-repeat="action in vm.widgetActions(widget)" | ||
55 | + aria-label="{{ action.name | translate }}" | ||
56 | + ng-show="!vm.isEdit && action.show" | ||
57 | + ng-click="action.onAction($event)" | ||
58 | + class="md-icon-button"> | ||
59 | + <md-tooltip md-direction="top"> | ||
60 | + {{ action.name | translate }} | ||
61 | + </md-tooltip> | ||
62 | + <ng-md-icon size="20" icon="{{action.icon}}"></ng-md-icon> | ||
63 | + </md-button> | ||
53 | <md-button id="expand-button" | 64 | <md-button id="expand-button" |
54 | ng-show="!vm.isEdit && vm.enableWidgetFullscreen(widget)" | 65 | ng-show="!vm.isEdit && vm.enableWidgetFullscreen(widget)" |
55 | aria-label="{{ 'fullscreen.fullscreen' | translate }}" | 66 | aria-label="{{ 'fullscreen.fullscreen' | translate }}" |
@@ -92,6 +103,7 @@ | @@ -92,6 +103,7 @@ | ||
92 | aliasController: vm.aliasController, | 103 | aliasController: vm.aliasController, |
93 | stateController: vm.stateController, | 104 | stateController: vm.stateController, |
94 | isEdit: vm.isEdit, | 105 | isEdit: vm.isEdit, |
106 | + isMobile: vm.isMobileSize, | ||
95 | stDiff: vm.stDiff, | 107 | stDiff: vm.stDiff, |
96 | dashboardTimewindow: vm.dashboardTimewindow, | 108 | dashboardTimewindow: vm.dashboardTimewindow, |
97 | dashboardTimewindowApi: vm.dashboardTimewindowApi }"> | 109 | dashboardTimewindowApi: vm.dashboardTimewindowApi }"> |
@@ -63,6 +63,12 @@ function DatakeyConfig($compile, $templateCache, $q, types) { | @@ -63,6 +63,12 @@ function DatakeyConfig($compile, $templateCache, $q, types) { | ||
63 | element.html(template); | 63 | element.html(template); |
64 | 64 | ||
65 | scope.types = types; | 65 | scope.types = types; |
66 | + | ||
67 | + scope.alarmFields = []; | ||
68 | + for (var alarmField in types.alarmFields) { | ||
69 | + scope.alarmFields.push(alarmField); | ||
70 | + } | ||
71 | + | ||
66 | scope.selectedKey = null; | 72 | scope.selectedKey = null; |
67 | scope.keySearchText = null; | 73 | scope.keySearchText = null; |
68 | scope.usePostProcessing = false; | 74 | scope.usePostProcessing = false; |
@@ -112,21 +118,39 @@ function DatakeyConfig($compile, $templateCache, $q, types) { | @@ -112,21 +118,39 @@ function DatakeyConfig($compile, $templateCache, $q, types) { | ||
112 | }, true); | 118 | }, true); |
113 | 119 | ||
114 | scope.keysSearch = function (searchText) { | 120 | scope.keysSearch = function (searchText) { |
115 | - if (scope.entityAlias) { | ||
116 | - var deferred = $q.defer(); | ||
117 | - scope.fetchEntityKeys({entityAliasId: scope.entityAlias.id, query: searchText, type: scope.model.type}) | ||
118 | - .then(function (keys) { | ||
119 | - keys.push(searchText); | ||
120 | - deferred.resolve(keys); | ||
121 | - }, function (e) { | ||
122 | - deferred.reject(e); | ||
123 | - }); | ||
124 | - return deferred.promise; | 121 | + if (scope.model.type === types.dataKeyType.alarm) { |
122 | + var dataKeys = searchText ? scope.alarmFields.filter( | ||
123 | + scope.createFilterForDataKey(searchText)) : scope.alarmFields; | ||
124 | + dataKeys.push(searchText); | ||
125 | + return dataKeys; | ||
125 | } else { | 126 | } else { |
126 | - return $q.when([]); | 127 | + if (scope.entityAlias) { |
128 | + var deferred = $q.defer(); | ||
129 | + scope.fetchEntityKeys({ | ||
130 | + entityAliasId: scope.entityAlias.id, | ||
131 | + query: searchText, | ||
132 | + type: scope.model.type | ||
133 | + }) | ||
134 | + .then(function (keys) { | ||
135 | + keys.push(searchText); | ||
136 | + deferred.resolve(keys); | ||
137 | + }, function (e) { | ||
138 | + deferred.reject(e); | ||
139 | + }); | ||
140 | + return deferred.promise; | ||
141 | + } else { | ||
142 | + return $q.when([]); | ||
143 | + } | ||
127 | } | 144 | } |
128 | }; | 145 | }; |
129 | 146 | ||
147 | + scope.createFilterForDataKey = function (query) { | ||
148 | + var lowercaseQuery = angular.lowercase(query); | ||
149 | + return function filterFn(dataKey) { | ||
150 | + return (angular.lowercase(dataKey).indexOf(lowercaseQuery) === 0); | ||
151 | + }; | ||
152 | + }; | ||
153 | + | ||
130 | $compile(element.contents())(scope); | 154 | $compile(element.contents())(scope); |
131 | } | 155 | } |
132 | 156 |
@@ -16,7 +16,9 @@ | @@ -16,7 +16,9 @@ | ||
16 | 16 | ||
17 | --> | 17 | --> |
18 | <md-content class="md-padding" layout="column"> | 18 | <md-content class="md-padding" layout="column"> |
19 | - <md-autocomplete ng-if="model.type === types.dataKeyType.timeseries || model.type === types.dataKeyType.attribute" | 19 | + <md-autocomplete ng-if="model.type === types.dataKeyType.timeseries || |
20 | + model.type === types.dataKeyType.attribute || | ||
21 | + model.type === types.dataKeyType.alarm" | ||
20 | style="padding-bottom: 8px;" | 22 | style="padding-bottom: 8px;" |
21 | ng-required="true" | 23 | ng-required="true" |
22 | md-no-cache="true" | 24 | md-no-cache="true" |
@@ -27,8 +29,8 @@ | @@ -27,8 +29,8 @@ | ||
27 | md-items="item in keysSearch(keySearchText)" | 29 | md-items="item in keysSearch(keySearchText)" |
28 | md-item-text="item" | 30 | md-item-text="item" |
29 | md-min-length="0" | 31 | md-min-length="0" |
30 | - placeholder="Key name" | ||
31 | - md-floating-label="Key"> | 32 | + placeholder="{{ 'entity.key-name' | translate }}" |
33 | + md-floating-label="{{ 'entity.key' | translate }}"> | ||
32 | <span md-highlight-text="keySearchText" md-highlight-flags="^i">{{item}}</span> | 34 | <span md-highlight-text="keySearchText" md-highlight-flags="^i">{{item}}</span> |
33 | </md-autocomplete> | 35 | </md-autocomplete> |
34 | <div layout="row" layout-align="start center"> | 36 | <div layout="row" layout-align="start center"> |
@@ -48,7 +50,7 @@ | @@ -48,7 +50,7 @@ | ||
48 | md-color-history="false"> | 50 | md-color-history="false"> |
49 | </div> | 51 | </div> |
50 | </div> | 52 | </div> |
51 | - <div layout="row" layout-align="start center"> | 53 | + <div layout="row" layout-align="start center" ng-if="model.type !== types.dataKeyType.alarm"> |
52 | <md-input-container flex> | 54 | <md-input-container flex> |
53 | <label translate>datakey.units</label> | 55 | <label translate>datakey.units</label> |
54 | <input name="units" ng-model="model.units"> | 56 | <input name="units" ng-model="model.units"> |
@@ -45,12 +45,20 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | @@ -45,12 +45,20 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | ||
45 | scope.ngModelCtrl = ngModelCtrl; | 45 | scope.ngModelCtrl = ngModelCtrl; |
46 | scope.types = types; | 46 | scope.types = types; |
47 | 47 | ||
48 | + scope.alarmFields = []; | ||
49 | + for (var alarmField in types.alarmFields) { | ||
50 | + scope.alarmFields.push(alarmField); | ||
51 | + } | ||
52 | + | ||
48 | scope.selectedTimeseriesDataKey = null; | 53 | scope.selectedTimeseriesDataKey = null; |
49 | scope.timeseriesDataKeySearchText = null; | 54 | scope.timeseriesDataKeySearchText = null; |
50 | 55 | ||
51 | scope.selectedAttributeDataKey = null; | 56 | scope.selectedAttributeDataKey = null; |
52 | scope.attributeDataKeySearchText = null; | 57 | scope.attributeDataKeySearchText = null; |
53 | 58 | ||
59 | + scope.selectedAlarmDataKey = null; | ||
60 | + scope.alarmDataKeySearchText = null; | ||
61 | + | ||
54 | scope.updateValidity = function () { | 62 | scope.updateValidity = function () { |
55 | if (ngModelCtrl.$viewValue) { | 63 | if (ngModelCtrl.$viewValue) { |
56 | var value = ngModelCtrl.$viewValue; | 64 | var value = ngModelCtrl.$viewValue; |
@@ -81,24 +89,27 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | @@ -81,24 +89,27 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | ||
81 | }); | 89 | }); |
82 | 90 | ||
83 | scope.$watch('timeseriesDataKeys', function () { | 91 | scope.$watch('timeseriesDataKeys', function () { |
84 | - if (ngModelCtrl.$viewValue) { | ||
85 | - var dataKeys = []; | ||
86 | - dataKeys = dataKeys.concat(scope.timeseriesDataKeys); | ||
87 | - dataKeys = dataKeys.concat(scope.attributeDataKeys); | ||
88 | - ngModelCtrl.$viewValue.dataKeys = dataKeys; | ||
89 | - scope.updateValidity(); | ||
90 | - } | 92 | + updateDataKeys(); |
91 | }, true); | 93 | }, true); |
92 | 94 | ||
93 | scope.$watch('attributeDataKeys', function () { | 95 | scope.$watch('attributeDataKeys', function () { |
96 | + updateDataKeys(); | ||
97 | + }, true); | ||
98 | + | ||
99 | + scope.$watch('alarmDataKeys', function () { | ||
100 | + updateDataKeys(); | ||
101 | + }, true); | ||
102 | + | ||
103 | + function updateDataKeys() { | ||
94 | if (ngModelCtrl.$viewValue) { | 104 | if (ngModelCtrl.$viewValue) { |
95 | var dataKeys = []; | 105 | var dataKeys = []; |
96 | dataKeys = dataKeys.concat(scope.timeseriesDataKeys); | 106 | dataKeys = dataKeys.concat(scope.timeseriesDataKeys); |
97 | dataKeys = dataKeys.concat(scope.attributeDataKeys); | 107 | dataKeys = dataKeys.concat(scope.attributeDataKeys); |
108 | + dataKeys = dataKeys.concat(scope.alarmDataKeys); | ||
98 | ngModelCtrl.$viewValue.dataKeys = dataKeys; | 109 | ngModelCtrl.$viewValue.dataKeys = dataKeys; |
99 | scope.updateValidity(); | 110 | scope.updateValidity(); |
100 | } | 111 | } |
101 | - }, true); | 112 | + } |
102 | 113 | ||
103 | ngModelCtrl.$render = function () { | 114 | ngModelCtrl.$render = function () { |
104 | if (ngModelCtrl.$viewValue) { | 115 | if (ngModelCtrl.$viewValue) { |
@@ -111,16 +122,20 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | @@ -111,16 +122,20 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | ||
111 | } | 122 | } |
112 | var timeseriesDataKeys = []; | 123 | var timeseriesDataKeys = []; |
113 | var attributeDataKeys = []; | 124 | var attributeDataKeys = []; |
125 | + var alarmDataKeys = []; | ||
114 | for (var d in ngModelCtrl.$viewValue.dataKeys) { | 126 | for (var d in ngModelCtrl.$viewValue.dataKeys) { |
115 | var dataKey = ngModelCtrl.$viewValue.dataKeys[d]; | 127 | var dataKey = ngModelCtrl.$viewValue.dataKeys[d]; |
116 | if (dataKey.type === types.dataKeyType.timeseries) { | 128 | if (dataKey.type === types.dataKeyType.timeseries) { |
117 | timeseriesDataKeys.push(dataKey); | 129 | timeseriesDataKeys.push(dataKey); |
118 | } else if (dataKey.type === types.dataKeyType.attribute) { | 130 | } else if (dataKey.type === types.dataKeyType.attribute) { |
119 | attributeDataKeys.push(dataKey); | 131 | attributeDataKeys.push(dataKey); |
132 | + } else if (dataKey.type === types.dataKeyType.alarm) { | ||
133 | + alarmDataKeys.push(dataKey); | ||
120 | } | 134 | } |
121 | } | 135 | } |
122 | scope.timeseriesDataKeys = timeseriesDataKeys; | 136 | scope.timeseriesDataKeys = timeseriesDataKeys; |
123 | scope.attributeDataKeys = attributeDataKeys; | 137 | scope.attributeDataKeys = attributeDataKeys; |
138 | + scope.alarmDataKeys = alarmDataKeys; | ||
124 | } | 139 | } |
125 | }; | 140 | }; |
126 | 141 | ||
@@ -135,6 +150,9 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | @@ -135,6 +150,9 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | ||
135 | if (!scope.attributeDataKeySearchText || scope.attributeDataKeySearchText === '') { | 150 | if (!scope.attributeDataKeySearchText || scope.attributeDataKeySearchText === '') { |
136 | scope.attributeDataKeySearchText = scope.attributeDataKeySearchText === '' ? null : ''; | 151 | scope.attributeDataKeySearchText = scope.attributeDataKeySearchText === '' ? null : ''; |
137 | } | 152 | } |
153 | + if (!scope.alarmDataKeySearchText || scope.alarmDataKeySearchText === '') { | ||
154 | + scope.alarmDataKeySearchText = scope.alarmDataKeySearchText === '' ? null : ''; | ||
155 | + } | ||
138 | }; | 156 | }; |
139 | 157 | ||
140 | scope.transformTimeseriesDataKeyChip = function (chip) { | 158 | scope.transformTimeseriesDataKeyChip = function (chip) { |
@@ -145,6 +163,10 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | @@ -145,6 +163,10 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | ||
145 | return scope.generateDataKey({chip: chip, type: types.dataKeyType.attribute}); | 163 | return scope.generateDataKey({chip: chip, type: types.dataKeyType.attribute}); |
146 | }; | 164 | }; |
147 | 165 | ||
166 | + scope.transformAlarmDataKeyChip = function (chip) { | ||
167 | + return scope.generateDataKey({chip: chip, type: types.dataKeyType.alarm}); | ||
168 | + }; | ||
169 | + | ||
148 | scope.showColorPicker = function (event, dataKey) { | 170 | scope.showColorPicker = function (event, dataKey) { |
149 | $mdColorPicker.show({ | 171 | $mdColorPicker.show({ |
150 | value: dataKey.color, | 172 | value: dataKey.color, |
@@ -196,6 +218,8 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | @@ -196,6 +218,8 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | ||
196 | scope.timeseriesDataKeys[index] = dataKey; | 218 | scope.timeseriesDataKeys[index] = dataKey; |
197 | } else if (dataKey.type === types.dataKeyType.attribute) { | 219 | } else if (dataKey.type === types.dataKeyType.attribute) { |
198 | scope.attributeDataKeys[index] = dataKey; | 220 | scope.attributeDataKeys[index] = dataKey; |
221 | + } else if (dataKey.type === types.dataKeyType.alarm) { | ||
222 | + scope.alarmDataKeys[index] = dataKey; | ||
199 | } | 223 | } |
200 | ngModelCtrl.$setDirty(); | 224 | ngModelCtrl.$setDirty(); |
201 | }, function () { | 225 | }, function () { |
@@ -203,20 +227,33 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | @@ -203,20 +227,33 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc | ||
203 | }; | 227 | }; |
204 | 228 | ||
205 | scope.dataKeysSearch = function (searchText, type) { | 229 | scope.dataKeysSearch = function (searchText, type) { |
206 | - if (scope.entityAlias) { | ||
207 | - var deferred = $q.defer(); | ||
208 | - scope.fetchEntityKeys({entityAliasId: scope.entityAlias.id, query: searchText, type: type}) | ||
209 | - .then(function (dataKeys) { | ||
210 | - deferred.resolve(dataKeys); | ||
211 | - }, function (e) { | ||
212 | - deferred.reject(e); | ||
213 | - }); | ||
214 | - return deferred.promise; | 230 | + if (scope.widgetType == types.widgetType.alarm.value) { |
231 | + var dataKeys = searchText ? scope.alarmFields.filter( | ||
232 | + scope.createFilterForDataKey(searchText)) : scope.alarmFields; | ||
233 | + return dataKeys; | ||
215 | } else { | 234 | } else { |
216 | - return $q.when([]); | 235 | + if (scope.entityAlias) { |
236 | + var deferred = $q.defer(); | ||
237 | + scope.fetchEntityKeys({entityAliasId: scope.entityAlias.id, query: searchText, type: type}) | ||
238 | + .then(function (dataKeys) { | ||
239 | + deferred.resolve(dataKeys); | ||
240 | + }, function (e) { | ||
241 | + deferred.reject(e); | ||
242 | + }); | ||
243 | + return deferred.promise; | ||
244 | + } else { | ||
245 | + return $q.when([]); | ||
246 | + } | ||
217 | } | 247 | } |
218 | }; | 248 | }; |
219 | 249 | ||
250 | + scope.createFilterForDataKey = function (query) { | ||
251 | + var lowercaseQuery = angular.lowercase(query); | ||
252 | + return function filterFn(dataKey) { | ||
253 | + return (angular.lowercase(dataKey).indexOf(lowercaseQuery) === 0); | ||
254 | + }; | ||
255 | + }; | ||
256 | + | ||
220 | scope.createKey = function (event, chipsId) { | 257 | scope.createKey = function (event, chipsId) { |
221 | var chipsChild = $(chipsId, element)[0].firstElementChild; | 258 | var chipsChild = $(chipsId, element)[0].firstElementChild; |
222 | var el = angular.element(chipsChild); | 259 | var el = angular.element(chipsChild); |
@@ -15,7 +15,7 @@ | @@ -15,7 +15,7 @@ | ||
15 | */ | 15 | */ |
16 | @import '../../scss/constants'; | 16 | @import '../../scss/constants'; |
17 | 17 | ||
18 | -.tb-entity-alias-autocomplete, .tb-timeseries-datakey-autocomplete, .tb-attribute-datakey-autocomplete { | 18 | +.tb-entity-alias-autocomplete, .tb-timeseries-datakey-autocomplete, .tb-attribute-datakey-autocomplete, .tb-alarm-datakey-autocomplete { |
19 | .tb-not-found { | 19 | .tb-not-found { |
20 | display: block; | 20 | display: block; |
21 | line-height: 1.5; | 21 | line-height: 1.5; |
@@ -24,7 +24,7 @@ | @@ -24,7 +24,7 @@ | ||
24 | </tb-entity-alias-select> | 24 | </tb-entity-alias-select> |
25 | <section flex layout='column'> | 25 | <section flex layout='column'> |
26 | <section flex layout='column' layout-align="center" style="padding-left: 4px;"> | 26 | <section flex layout='column' layout-align="center" style="padding-left: 4px;"> |
27 | - <md-chips flex | 27 | + <md-chips flex ng-if="widgetType != types.widgetType.alarm.value" |
28 | id="timeseries_datakey_chips" | 28 | id="timeseries_datakey_chips" |
29 | ng-required="true" | 29 | ng-required="true" |
30 | ng-model="timeseriesDataKeys" md-autocomplete-snap | 30 | ng-model="timeseriesDataKeys" md-autocomplete-snap |
@@ -128,10 +128,63 @@ | @@ -128,10 +128,63 @@ | ||
128 | </div> | 128 | </div> |
129 | </md-chip-template> | 129 | </md-chip-template> |
130 | </md-chips> | 130 | </md-chips> |
131 | + <md-chips flex ng-if="widgetType == types.widgetType.alarm.value" | ||
132 | + id="alarm_datakey_chips" | ||
133 | + ng-required="true" | ||
134 | + ng-model="alarmDataKeys" md-autocomplete-snap | ||
135 | + md-transform-chip="transformAlarmDataKeyChip($chip)" | ||
136 | + md-require-match="false"> | ||
137 | + <md-autocomplete | ||
138 | + md-no-cache="true" | ||
139 | + id="alarm_datakey" | ||
140 | + md-selected-item="selectedAlarmDataKey" | ||
141 | + md-search-text="alarmDataKeySearchText" | ||
142 | + md-items="item in dataKeysSearch(alarmDataKeySearchText, types.dataKeyType.alarm)" | ||
143 | + md-item-text="item.name" | ||
144 | + md-min-length="0" | ||
145 | + placeholder="{{'datakey.alarm' | translate }}" | ||
146 | + md-menu-class="tb-alarm-datakey-autocomplete"> | ||
147 | + <span md-highlight-text="alarmDataKeySearchText" md-highlight-flags="^i">{{item}}</span> | ||
148 | + <md-not-found> | ||
149 | + <div class="tb-not-found"> | ||
150 | + <div class="tb-no-entries" ng-if="!textIsNotEmpty(alarmDataKeySearchText)"> | ||
151 | + <span translate>entity.no-keys-found</span> | ||
152 | + </div> | ||
153 | + <div ng-if="textIsNotEmpty(alarmDataKeySearchText)"> | ||
154 | + <span translate translate-values='{ key: "{{alarmDataKeySearchText | truncate:true:6:'...'}}" }'>entity.no-key-matching</span> | ||
155 | + <span> | ||
156 | + <a translate ng-click="createKey($event, '#alarm_datakey_chips')">entity.create-new-key</a> | ||
157 | + </span> | ||
158 | + </div> | ||
159 | + </div> | ||
160 | + </md-not-found> | ||
161 | + </md-autocomplete> | ||
162 | + <md-chip-template> | ||
163 | + <div layout="row" layout-align="start center" class="tb-attribute-chip"> | ||
164 | + <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;"> | ||
165 | + <div class="tb-color-result" ng-style="{background: $chip.color}"></div> | ||
166 | + </div> | ||
167 | + <div layout="row" flex> | ||
168 | + <div class="tb-chip-label"> | ||
169 | + {{$chip.label}} | ||
170 | + </div> | ||
171 | + <div class="tb-chip-separator">: </div> | ||
172 | + <div class="tb-chip-label"> | ||
173 | + <strong ng-if="!$chip.postFuncBody">{{$chip.name}}</strong> | ||
174 | + <strong ng-if="$chip.postFuncBody">f({{$chip.name}})</strong> | ||
175 | + </div> | ||
176 | + </div> | ||
177 | + <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32"> | ||
178 | + <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon> | ||
179 | + </md-button> | ||
180 | + </div> | ||
181 | + </md-chip-template> | ||
182 | + </md-chips> | ||
131 | </section> | 183 | </section> |
132 | <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert"> | 184 | <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert"> |
133 | <div translate ng-message="entityKeys" ng-if="widgetType === types.widgetType.timeseries.value" class="tb-error-message">datakey.timeseries-required</div> | 185 | <div translate ng-message="entityKeys" ng-if="widgetType === types.widgetType.timeseries.value" class="tb-error-message">datakey.timeseries-required</div> |
134 | <div translate ng-message="entityKeys" ng-if="widgetType === types.widgetType.latest.value" class="tb-error-message">datakey.timeseries-or-attributes-required</div> | 186 | <div translate ng-message="entityKeys" ng-if="widgetType === types.widgetType.latest.value" class="tb-error-message">datakey.timeseries-or-attributes-required</div> |
187 | + <div translate ng-message="entityKeys" ng-if="widgetType === types.widgetType.alarm.value" class="tb-error-message">datakey.alarm-fields-required</div> | ||
135 | </div> | 188 | </div> |
136 | </section> | 189 | </section> |
137 | </section> | 190 | </section> |
@@ -43,18 +43,27 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | @@ -43,18 +43,27 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | ||
43 | element.html(template); | 43 | element.html(template); |
44 | 44 | ||
45 | scope.ngModelCtrl = ngModelCtrl; | 45 | scope.ngModelCtrl = ngModelCtrl; |
46 | + scope.types = types; | ||
47 | + | ||
46 | scope.functionTypes = utils.getPredefinedFunctionsList(); | 48 | scope.functionTypes = utils.getPredefinedFunctionsList(); |
49 | + scope.alarmFields = []; | ||
50 | + for (var alarmField in types.alarmFields) { | ||
51 | + scope.alarmFields.push(alarmField); | ||
52 | + } | ||
47 | 53 | ||
48 | scope.selectedDataKey = null; | 54 | scope.selectedDataKey = null; |
49 | scope.dataKeySearchText = null; | 55 | scope.dataKeySearchText = null; |
50 | 56 | ||
57 | + scope.selectedAlarmDataKey = null; | ||
58 | + scope.alarmDataKeySearchText = null; | ||
59 | + | ||
51 | scope.updateValidity = function () { | 60 | scope.updateValidity = function () { |
52 | if (ngModelCtrl.$viewValue) { | 61 | if (ngModelCtrl.$viewValue) { |
53 | var value = ngModelCtrl.$viewValue; | 62 | var value = ngModelCtrl.$viewValue; |
54 | var dataValid = angular.isDefined(value) && value != null; | 63 | var dataValid = angular.isDefined(value) && value != null; |
55 | ngModelCtrl.$setValidity('deviceData', dataValid); | 64 | ngModelCtrl.$setValidity('deviceData', dataValid); |
56 | if (dataValid) { | 65 | if (dataValid) { |
57 | - ngModelCtrl.$setValidity('funcTypes', | 66 | + ngModelCtrl.$setValidity('datasourceKeys', |
58 | angular.isDefined(value.dataKeys) && | 67 | angular.isDefined(value.dataKeys) && |
59 | value.dataKeys != null && | 68 | value.dataKeys != null && |
60 | value.dataKeys.length > 0); | 69 | value.dataKeys.length > 0); |
@@ -63,13 +72,22 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | @@ -63,13 +72,22 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | ||
63 | }; | 72 | }; |
64 | 73 | ||
65 | scope.$watch('funcDataKeys', function () { | 74 | scope.$watch('funcDataKeys', function () { |
75 | + updateDataKeys(); | ||
76 | + }, true); | ||
77 | + | ||
78 | + scope.$watch('alarmDataKeys', function () { | ||
79 | + updateDataKeys(); | ||
80 | + }, true); | ||
81 | + | ||
82 | + function updateDataKeys() { | ||
66 | if (ngModelCtrl.$viewValue) { | 83 | if (ngModelCtrl.$viewValue) { |
67 | var dataKeys = []; | 84 | var dataKeys = []; |
68 | dataKeys = dataKeys.concat(scope.funcDataKeys); | 85 | dataKeys = dataKeys.concat(scope.funcDataKeys); |
86 | + dataKeys = dataKeys.concat(scope.alarmDataKeys); | ||
69 | ngModelCtrl.$viewValue.dataKeys = dataKeys; | 87 | ngModelCtrl.$viewValue.dataKeys = dataKeys; |
70 | scope.updateValidity(); | 88 | scope.updateValidity(); |
71 | } | 89 | } |
72 | - }, true); | 90 | + } |
73 | 91 | ||
74 | scope.$watch('datasourceName', function () { | 92 | scope.$watch('datasourceName', function () { |
75 | if (ngModelCtrl.$viewValue) { | 93 | if (ngModelCtrl.$viewValue) { |
@@ -81,18 +99,31 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | @@ -81,18 +99,31 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | ||
81 | ngModelCtrl.$render = function () { | 99 | ngModelCtrl.$render = function () { |
82 | if (ngModelCtrl.$viewValue) { | 100 | if (ngModelCtrl.$viewValue) { |
83 | var funcDataKeys = []; | 101 | var funcDataKeys = []; |
102 | + var alarmDataKeys = []; | ||
84 | if (ngModelCtrl.$viewValue.dataKeys) { | 103 | if (ngModelCtrl.$viewValue.dataKeys) { |
85 | - funcDataKeys = funcDataKeys.concat(ngModelCtrl.$viewValue.dataKeys); | 104 | + for (var d=0;d<ngModelCtrl.$viewValue.dataKeys.length;d++) { |
105 | + var dataKey = ngModelCtrl.$viewValue.dataKeys[d]; | ||
106 | + if (dataKey.type === types.dataKeyType.function) { | ||
107 | + funcDataKeys.push(dataKey); | ||
108 | + } else if (dataKey.type === types.dataKeyType.alarm) { | ||
109 | + alarmDataKeys.push(dataKey); | ||
110 | + } | ||
111 | + } | ||
86 | } | 112 | } |
87 | scope.funcDataKeys = funcDataKeys; | 113 | scope.funcDataKeys = funcDataKeys; |
114 | + scope.alarmDataKeys = alarmDataKeys; | ||
88 | scope.datasourceName = ngModelCtrl.$viewValue.name; | 115 | scope.datasourceName = ngModelCtrl.$viewValue.name; |
89 | } | 116 | } |
90 | }; | 117 | }; |
91 | 118 | ||
92 | - scope.transformDataKeyChip = function (chip) { | 119 | + scope.transformFuncDataKeyChip = function (chip) { |
93 | return scope.generateDataKey({chip: chip, type: types.dataKeyType.function}); | 120 | return scope.generateDataKey({chip: chip, type: types.dataKeyType.function}); |
94 | }; | 121 | }; |
95 | 122 | ||
123 | + scope.transformAlarmDataKeyChip = function (chip) { | ||
124 | + return scope.generateDataKey({chip: chip, type: types.dataKeyType.alarm}); | ||
125 | + }; | ||
126 | + | ||
96 | scope.showColorPicker = function (event, dataKey) { | 127 | scope.showColorPicker = function (event, dataKey) { |
97 | $mdColorPicker.show({ | 128 | $mdColorPicker.show({ |
98 | value: dataKey.color, | 129 | value: dataKey.color, |
@@ -129,7 +160,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | @@ -129,7 +160,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | ||
129 | dataKey: angular.copy(dataKey), | 160 | dataKey: angular.copy(dataKey), |
130 | dataKeySettingsSchema: scope.datakeySettingsSchema, | 161 | dataKeySettingsSchema: scope.datakeySettingsSchema, |
131 | entityAlias: null, | 162 | entityAlias: null, |
132 | - entityAliases: null | 163 | + aliasController: null |
133 | }, | 164 | }, |
134 | parent: angular.element($document[0].body), | 165 | parent: angular.element($document[0].body), |
135 | fullscreen: true, | 166 | fullscreen: true, |
@@ -140,7 +171,11 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | @@ -140,7 +171,11 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | ||
140 | w.triggerHandler('resize'); | 171 | w.triggerHandler('resize'); |
141 | } | 172 | } |
142 | }).then(function (dataKey) { | 173 | }).then(function (dataKey) { |
143 | - scope.funcDataKeys[index] = dataKey; | 174 | + if (dataKey.type === types.dataKeyType.function) { |
175 | + scope.funcDataKeys[index] = dataKey; | ||
176 | + } else if (dataKey.type === types.dataKeyType.alarm) { | ||
177 | + scope.alarmDataKeys[index] = dataKey; | ||
178 | + } | ||
144 | ngModelCtrl.$setDirty(); | 179 | ngModelCtrl.$setDirty(); |
145 | }, function () { | 180 | }, function () { |
146 | }); | 181 | }); |
@@ -151,8 +186,9 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | @@ -151,8 +186,9 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | ||
151 | } | 186 | } |
152 | 187 | ||
153 | scope.dataKeysSearch = function (dataKeySearchText) { | 188 | scope.dataKeysSearch = function (dataKeySearchText) { |
154 | - var dataKeys = dataKeySearchText ? scope.functionTypes.filter( | ||
155 | - scope.createFilterForDataKey(dataKeySearchText)) : scope.functionTypes; | 189 | + var targetKeys = scope.widgetType == types.widgetType.alarm.value ? scope.alarmFields : scope.functionTypes; |
190 | + var dataKeys = dataKeySearchText ? targetKeys.filter( | ||
191 | + scope.createFilterForDataKey(dataKeySearchText)) : targetKeys; | ||
156 | return dataKeys; | 192 | return dataKeys; |
157 | }; | 193 | }; |
158 | 194 | ||
@@ -180,6 +216,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | @@ -180,6 +216,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, | ||
180 | restrict: "E", | 216 | restrict: "E", |
181 | require: "^ngModel", | 217 | require: "^ngModel", |
182 | scope: { | 218 | scope: { |
219 | + widgetType: '=', | ||
183 | generateDataKey: '&', | 220 | generateDataKey: '&', |
184 | datakeySettingsSchema: '=' | 221 | datakeySettingsSchema: '=' |
185 | }, | 222 | }, |
@@ -17,18 +17,19 @@ | @@ -17,18 +17,19 @@ | ||
17 | --> | 17 | --> |
18 | <section class="tb-datasource-func" flex layout='column' | 18 | <section class="tb-datasource-func" flex layout='column' |
19 | layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center"> | 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;"> | 20 | + <md-input-container ng-if="widgetType != types.widgetType.alarm.value" |
21 | + class="tb-datasource-name" md-no-float style="min-width: 200px;"> | ||
21 | <input name="datasourceName" | 22 | <input name="datasourceName" |
22 | placeholder="{{ 'datasource.name' | translate }}" | 23 | placeholder="{{ 'datasource.name' | translate }}" |
23 | ng-model="datasourceName" | 24 | ng-model="datasourceName" |
24 | aria-label="{{ 'datasource.name' | translate }}"> | 25 | aria-label="{{ 'datasource.name' | translate }}"> |
25 | </md-input-container> | 26 | </md-input-container> |
26 | <section flex layout='column' style="padding-left: 4px;"> | 27 | <section flex layout='column' style="padding-left: 4px;"> |
27 | - <md-chips flex | 28 | + <md-chips flex ng-if="widgetType != types.widgetType.alarm.value" |
28 | id="function_datakey_chips" | 29 | id="function_datakey_chips" |
29 | ng-required="true" | 30 | ng-required="true" |
30 | ng-model="funcDataKeys" md-autocomplete-snap | 31 | ng-model="funcDataKeys" md-autocomplete-snap |
31 | - md-transform-chip="transformDataKeyChip($chip)" | 32 | + md-transform-chip="transformFuncDataKeyChip($chip)" |
32 | md-require-match="false"> | 33 | md-require-match="false"> |
33 | <md-autocomplete | 34 | <md-autocomplete |
34 | md-no-cache="false" | 35 | md-no-cache="false" |
@@ -47,9 +48,9 @@ | @@ -47,9 +48,9 @@ | ||
47 | <span translate>device.no-keys-found</span> | 48 | <span translate>device.no-keys-found</span> |
48 | </div> | 49 | </div> |
49 | <div ng-if="textIsNotEmpty(dataKeySearchText)"> | 50 | <div ng-if="textIsNotEmpty(dataKeySearchText)"> |
50 | - <span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:'...'}}" }'>device.no-key-matching</span> | 51 | + <span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:'...'}}" }'>entity.no-key-matching</span> |
51 | <span> | 52 | <span> |
52 | - <a translate ng-click="createKey($event, '#function_datakey_chips')">device.create-new-key</a> | 53 | + <a translate ng-click="createKey($event, '#function_datakey_chips')">entity.create-new-key</a> |
53 | </span> | 54 | </span> |
54 | </div> | 55 | </div> |
55 | </div> | 56 | </div> |
@@ -75,8 +76,61 @@ | @@ -75,8 +76,61 @@ | ||
75 | </div> | 76 | </div> |
76 | </md-chip-template> | 77 | </md-chip-template> |
77 | </md-chips> | 78 | </md-chips> |
79 | + <md-chips flex ng-if="widgetType == types.widgetType.alarm.value" | ||
80 | + id="alarm_datakey_chips" | ||
81 | + ng-required="true" | ||
82 | + ng-model="alarmDataKeys" md-autocomplete-snap | ||
83 | + md-transform-chip="transformAlarmDataKeyChip($chip)" | ||
84 | + md-require-match="false"> | ||
85 | + <md-autocomplete | ||
86 | + md-no-cache="true" | ||
87 | + id="alarm_datakey" | ||
88 | + md-selected-item="selectedAlarmDataKey" | ||
89 | + md-search-text="alarmDataKeySearchText" | ||
90 | + md-items="item in dataKeysSearch(alarmDataKeySearchText, types.dataKeyType.alarm)" | ||
91 | + md-item-text="item.name" | ||
92 | + md-min-length="0" | ||
93 | + placeholder="{{'datakey.alarm' | translate }}" | ||
94 | + md-menu-class="tb-alarm-datakey-autocomplete"> | ||
95 | + <span md-highlight-text="alarmDataKeySearchText" md-highlight-flags="^i">{{item}}</span> | ||
96 | + <md-not-found> | ||
97 | + <div class="tb-not-found"> | ||
98 | + <div class="tb-no-entries" ng-if="!textIsNotEmpty(alarmDataKeySearchText)"> | ||
99 | + <span translate>entity.no-keys-found</span> | ||
100 | + </div> | ||
101 | + <div ng-if="textIsNotEmpty(alarmDataKeySearchText)"> | ||
102 | + <span translate translate-values='{ key: "{{alarmDataKeySearchText | truncate:true:6:'...'}}" }'>entity.no-key-matching</span> | ||
103 | + <span> | ||
104 | + <a translate ng-click="createKey($event, '#alarm_datakey_chips')">entity.create-new-key</a> | ||
105 | + </span> | ||
106 | + </div> | ||
107 | + </div> | ||
108 | + </md-not-found> | ||
109 | + </md-autocomplete> | ||
110 | + <md-chip-template> | ||
111 | + <div layout="row" layout-align="start center" class="tb-attribute-chip"> | ||
112 | + <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;"> | ||
113 | + <div class="tb-color-result" ng-style="{background: $chip.color}"></div> | ||
114 | + </div> | ||
115 | + <div layout="row" flex> | ||
116 | + <div class="tb-chip-label"> | ||
117 | + {{$chip.label}} | ||
118 | + </div> | ||
119 | + <div class="tb-chip-separator">: </div> | ||
120 | + <div class="tb-chip-label"> | ||
121 | + <strong ng-if="!$chip.postFuncBody">{{$chip.name}}</strong> | ||
122 | + <strong ng-if="$chip.postFuncBody">f({{$chip.name}})</strong> | ||
123 | + </div> | ||
124 | + </div> | ||
125 | + <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32"> | ||
126 | + <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon> | ||
127 | + </md-button> | ||
128 | + </div> | ||
129 | + </md-chip-template> | ||
130 | + </md-chips> | ||
78 | <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert"> | 131 | <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> | 132 | + <div translate ng-message="datasourceKeys" ng-if="widgetType !== types.widgetType.alarm.value" class="tb-error-message">datakey.function-types-required</div> |
133 | + <div translate ng-message="datasourceKeys" ng-if="widgetType === types.widgetType.alarm.value" class="tb-error-message">datakey.alarm-fields-required</div> | ||
80 | </div> | 134 | </div> |
81 | </section> | 135 | </section> |
82 | </section> | 136 | </section> |
@@ -30,7 +30,7 @@ export default angular.module('thingsboard.directives.datasource', [thingsboardT | @@ -30,7 +30,7 @@ export default angular.module('thingsboard.directives.datasource', [thingsboardT | ||
30 | .name; | 30 | .name; |
31 | 31 | ||
32 | /*@ngInject*/ | 32 | /*@ngInject*/ |
33 | -function Datasource($compile, $templateCache, types) { | 33 | +function Datasource($compile, $templateCache, utils, types) { |
34 | 34 | ||
35 | var linker = function (scope, element, attrs, ngModelCtrl) { | 35 | var linker = function (scope, element, attrs, ngModelCtrl) { |
36 | 36 | ||
@@ -53,8 +53,12 @@ function Datasource($compile, $templateCache, types) { | @@ -53,8 +53,12 @@ function Datasource($compile, $templateCache, types) { | ||
53 | } | 53 | } |
54 | 54 | ||
55 | scope.$watch('model.type', function (newType, prevType) { | 55 | scope.$watch('model.type', function (newType, prevType) { |
56 | - if (newType != prevType) { | ||
57 | - scope.model.dataKeys = []; | 56 | + if (newType && prevType && newType != prevType) { |
57 | + if (scope.widgetType == types.widgetType.alarm.value) { | ||
58 | + scope.model.dataKeys = utils.getDefaultAlarmDataKeys(); | ||
59 | + } else { | ||
60 | + scope.model.dataKeys = []; | ||
61 | + } | ||
58 | } | 62 | } |
59 | }); | 63 | }); |
60 | 64 | ||
@@ -63,9 +67,10 @@ function Datasource($compile, $templateCache, types) { | @@ -63,9 +67,10 @@ function Datasource($compile, $templateCache, types) { | ||
63 | }, true); | 67 | }, true); |
64 | 68 | ||
65 | ngModelCtrl.$render = function () { | 69 | ngModelCtrl.$render = function () { |
66 | - scope.model = {}; | ||
67 | if (ngModelCtrl.$viewValue) { | 70 | if (ngModelCtrl.$viewValue) { |
68 | scope.model = ngModelCtrl.$viewValue; | 71 | scope.model = ngModelCtrl.$viewValue; |
72 | + } else { | ||
73 | + scope.model = {}; | ||
69 | } | 74 | } |
70 | }; | 75 | }; |
71 | 76 |
@@ -29,6 +29,7 @@ | @@ -29,6 +29,7 @@ | ||
29 | ng-model="model" | 29 | ng-model="model" |
30 | datakey-settings-schema="datakeySettingsSchema" | 30 | datakey-settings-schema="datakeySettingsSchema" |
31 | ng-required="model.type === types.datasourceType.function" | 31 | ng-required="model.type === types.datasourceType.function" |
32 | + widget-type="widgetType" | ||
32 | generate-data-key="generateDataKey({chip: chip, type: type})"> | 33 | generate-data-key="generateDataKey({chip: chip, type: type})"> |
33 | </tb-datasource-func> | 34 | </tb-datasource-func> |
34 | <tb-datasource-entity flex | 35 | <tb-datasource-entity flex |
@@ -124,7 +124,7 @@ function Grid() { | @@ -124,7 +124,7 @@ function Grid() { | ||
124 | } | 124 | } |
125 | 125 | ||
126 | /*@ngInject*/ | 126 | /*@ngInject*/ |
127 | -function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $translate, $mdMedia, $templateCache) { | 127 | +function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $translate, $mdMedia, $templateCache, $window) { |
128 | 128 | ||
129 | var vm = this; | 129 | var vm = this; |
130 | 130 | ||
@@ -155,6 +155,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra | @@ -155,6 +155,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra | ||
155 | vm.refreshList = refreshList; | 155 | vm.refreshList = refreshList; |
156 | vm.saveItem = saveItem; | 156 | vm.saveItem = saveItem; |
157 | vm.toggleItemSelection = toggleItemSelection; | 157 | vm.toggleItemSelection = toggleItemSelection; |
158 | + vm.triggerResize = triggerResize; | ||
158 | 159 | ||
159 | $scope.$watch(function () { | 160 | $scope.$watch(function () { |
160 | return $mdMedia('xs') || $mdMedia('sm'); | 161 | return $mdMedia('xs') || $mdMedia('sm'); |
@@ -600,6 +601,11 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra | @@ -600,6 +601,11 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra | ||
600 | } | 601 | } |
601 | } | 602 | } |
602 | 603 | ||
604 | + function triggerResize() { | ||
605 | + var w = angular.element($window); | ||
606 | + w.triggerHandler('resize'); | ||
607 | + } | ||
608 | + | ||
603 | function moveToTop() { | 609 | function moveToTop() { |
604 | moveToIndex(0, true); | 610 | moveToIndex(0, true); |
605 | } | 611 | } |
@@ -64,7 +64,7 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM | @@ -64,7 +64,7 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM | ||
64 | 64 | ||
65 | scope.historyOnly = angular.isDefined(attrs.historyOnly); | 65 | scope.historyOnly = angular.isDefined(attrs.historyOnly); |
66 | 66 | ||
67 | - scope.aggregation = angular.isDefined(attrs.aggregation); | 67 | + scope.aggregation = scope.$eval(attrs.aggregation); |
68 | 68 | ||
69 | scope.isToolbar = angular.isDefined(attrs.isToolbar); | 69 | scope.isToolbar = angular.isDefined(attrs.isToolbar); |
70 | 70 |
@@ -43,7 +43,7 @@ export default angular.module('thingsboard.directives.widgetConfig', [thingsboar | @@ -43,7 +43,7 @@ export default angular.module('thingsboard.directives.widgetConfig', [thingsboar | ||
43 | .name; | 43 | .name; |
44 | 44 | ||
45 | /*@ngInject*/ | 45 | /*@ngInject*/ |
46 | -function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, utils) { | 46 | +function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout, types, utils) { |
47 | 47 | ||
48 | var linker = function (scope, element, attrs, ngModelCtrl) { | 48 | var linker = function (scope, element, attrs, ngModelCtrl) { |
49 | 49 | ||
@@ -87,6 +87,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | @@ -87,6 +87,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | ||
87 | value: null | 87 | value: null |
88 | } | 88 | } |
89 | 89 | ||
90 | + scope.alarmSource = { | ||
91 | + value: null | ||
92 | + } | ||
93 | + | ||
90 | ngModelCtrl.$render = function () { | 94 | ngModelCtrl.$render = function () { |
91 | if (ngModelCtrl.$viewValue) { | 95 | if (ngModelCtrl.$viewValue) { |
92 | var config = ngModelCtrl.$viewValue.config; | 96 | var config = ngModelCtrl.$viewValue.config; |
@@ -113,7 +117,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | @@ -113,7 +117,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | ||
113 | scope.showLegend = angular.isDefined(config.showLegend) ? | 117 | scope.showLegend = angular.isDefined(config.showLegend) ? |
114 | config.showLegend : scope.widgetType === types.widgetType.timeseries.value; | 118 | config.showLegend : scope.widgetType === types.widgetType.timeseries.value; |
115 | scope.legendConfig = config.legendConfig; | 119 | scope.legendConfig = config.legendConfig; |
116 | - if (scope.widgetType !== types.widgetType.rpc.value && scope.widgetType !== types.widgetType.static.value | 120 | + if (scope.widgetType !== types.widgetType.rpc.value && |
121 | + scope.widgetType !== types.widgetType.alarm.value && | ||
122 | + scope.widgetType !== types.widgetType.static.value | ||
117 | && scope.isDataEnabled) { | 123 | && scope.isDataEnabled) { |
118 | if (scope.datasources) { | 124 | if (scope.datasources) { |
119 | scope.datasources.splice(0, scope.datasources.length); | 125 | scope.datasources.splice(0, scope.datasources.length); |
@@ -137,6 +143,16 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | @@ -137,6 +143,16 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | ||
137 | } else { | 143 | } else { |
138 | scope.targetDeviceAlias.value = null; | 144 | scope.targetDeviceAlias.value = null; |
139 | } | 145 | } |
146 | + } else if (scope.widgetType === types.widgetType.alarm.value && scope.isDataEnabled) { | ||
147 | + scope.alarmSearchStatus = angular.isDefined(config.alarmSearchStatus) ? | ||
148 | + config.alarmSearchStatus : types.alarmSearchStatus.any; | ||
149 | + scope.alarmsPollingInterval = angular.isDefined(config.alarmsPollingInterval) ? | ||
150 | + config.alarmsPollingInterval : 5; | ||
151 | + if (config.alarmSource) { | ||
152 | + scope.alarmSource.value = config.alarmSource; | ||
153 | + } else { | ||
154 | + scope.alarmSource.value = null; | ||
155 | + } | ||
140 | } | 156 | } |
141 | 157 | ||
142 | scope.settings = config.settings; | 158 | scope.settings = config.settings; |
@@ -175,6 +191,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | @@ -175,6 +191,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | ||
175 | if (scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) { | 191 | if (scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) { |
176 | valid = config && config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0; | 192 | valid = config && config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0; |
177 | ngModelCtrl.$setValidity('targetDeviceAliasIds', valid); | 193 | ngModelCtrl.$setValidity('targetDeviceAliasIds', valid); |
194 | + } else if (scope.widgetType === types.widgetType.alarm.value && scope.isDataEnabled) { | ||
195 | + valid = config && config.alarmSource; | ||
196 | + ngModelCtrl.$setValidity('alarmSource', valid); | ||
178 | } else if (scope.widgetType !== types.widgetType.static.value && scope.isDataEnabled) { | 197 | } else if (scope.widgetType !== types.widgetType.static.value && scope.isDataEnabled) { |
179 | valid = config && config.datasources && config.datasources.length > 0; | 198 | valid = config && config.datasources && config.datasources.length > 0; |
180 | ngModelCtrl.$setValidity('datasources', valid); | 199 | ngModelCtrl.$setValidity('datasources', valid); |
@@ -190,7 +209,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | @@ -190,7 +209,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | ||
190 | }; | 209 | }; |
191 | 210 | ||
192 | scope.$watch('title + showTitle + dropShadow + enableFullscreen + backgroundColor + color + ' + | 211 | scope.$watch('title + showTitle + dropShadow + enableFullscreen + backgroundColor + color + ' + |
193 | - 'padding + titleStyle + mobileOrder + mobileHeight + units + decimals + useDashboardTimewindow + showLegend', function () { | 212 | + 'padding + titleStyle + mobileOrder + mobileHeight + units + decimals + useDashboardTimewindow + ' + |
213 | + 'alarmSearchStatus + alarmsPollingInterval + showLegend', function () { | ||
194 | if (ngModelCtrl.$viewValue) { | 214 | if (ngModelCtrl.$viewValue) { |
195 | var value = ngModelCtrl.$viewValue; | 215 | var value = ngModelCtrl.$viewValue; |
196 | if (value.config) { | 216 | if (value.config) { |
@@ -210,6 +230,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | @@ -210,6 +230,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | ||
210 | config.units = scope.units; | 230 | config.units = scope.units; |
211 | config.decimals = scope.decimals; | 231 | config.decimals = scope.decimals; |
212 | config.useDashboardTimewindow = scope.useDashboardTimewindow; | 232 | config.useDashboardTimewindow = scope.useDashboardTimewindow; |
233 | + config.alarmSearchStatus = scope.alarmSearchStatus; | ||
234 | + config.alarmsPollingInterval = scope.alarmsPollingInterval; | ||
213 | config.showLegend = scope.showLegend; | 235 | config.showLegend = scope.showLegend; |
214 | } | 236 | } |
215 | if (value.layout) { | 237 | if (value.layout) { |
@@ -253,7 +275,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | @@ -253,7 +275,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | ||
253 | }, true); | 275 | }, true); |
254 | 276 | ||
255 | scope.$watch('datasources', function () { | 277 | scope.$watch('datasources', function () { |
256 | - if (ngModelCtrl.$viewValue && ngModelCtrl.$viewValue.config && scope.widgetType !== types.widgetType.rpc.value | 278 | + if (ngModelCtrl.$viewValue && ngModelCtrl.$viewValue.config |
279 | + && scope.widgetType !== types.widgetType.rpc.value | ||
280 | + && scope.widgetType !== types.widgetType.alarm.value | ||
257 | && scope.widgetType !== types.widgetType.static.value && scope.isDataEnabled) { | 281 | && scope.widgetType !== types.widgetType.static.value && scope.isDataEnabled) { |
258 | var value = ngModelCtrl.$viewValue; | 282 | var value = ngModelCtrl.$viewValue; |
259 | var config = value.config; | 283 | var config = value.config; |
@@ -286,6 +310,20 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | @@ -286,6 +310,20 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | ||
286 | } | 310 | } |
287 | }); | 311 | }); |
288 | 312 | ||
313 | + scope.$watch('alarmSource.value', function () { | ||
314 | + if (ngModelCtrl.$viewValue && ngModelCtrl.$viewValue.config && scope.widgetType === types.widgetType.alarm.value && scope.isDataEnabled) { | ||
315 | + var value = ngModelCtrl.$viewValue; | ||
316 | + var config = value.config; | ||
317 | + if (scope.alarmSource.value) { | ||
318 | + config.alarmSource = scope.alarmSource.value; | ||
319 | + } else { | ||
320 | + config.alarmSource = null; | ||
321 | + } | ||
322 | + ngModelCtrl.$setViewValue(value); | ||
323 | + scope.updateValidity(); | ||
324 | + } | ||
325 | + }); | ||
326 | + | ||
289 | scope.addDatasource = function () { | 327 | scope.addDatasource = function () { |
290 | var newDatasource; | 328 | var newDatasource; |
291 | if (scope.functionsOnly) { | 329 | if (scope.functionsOnly) { |
@@ -320,10 +358,19 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | @@ -320,10 +358,19 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | ||
320 | return chip; | 358 | return chip; |
321 | } | 359 | } |
322 | 360 | ||
361 | + var label = chip; | ||
362 | + if (type === types.dataKeyType.alarm) { | ||
363 | + var alarmField = types.alarmFields[chip]; | ||
364 | + if (alarmField) { | ||
365 | + label = $translate.instant(alarmField.name)+''; | ||
366 | + } | ||
367 | + } | ||
368 | + label = scope.genNextLabel(label); | ||
369 | + | ||
323 | var result = { | 370 | var result = { |
324 | name: chip, | 371 | name: chip, |
325 | type: type, | 372 | type: type, |
326 | - label: scope.genNextLabel(chip), | 373 | + label: label, |
327 | color: scope.genNextColor(), | 374 | color: scope.genNextColor(), |
328 | settings: {}, | 375 | settings: {}, |
329 | _hash: Math.random() | 376 | _hash: Math.random() |
@@ -351,15 +398,18 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | @@ -351,15 +398,18 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | ||
351 | var matches = false; | 398 | var matches = false; |
352 | do { | 399 | do { |
353 | matches = false; | 400 | matches = false; |
354 | - if (value.config.datasources) { | ||
355 | - for (var d=0;d<value.config.datasources.length;d++) { | ||
356 | - var datasource = value.config.datasources[d]; | ||
357 | - for (var k=0;k<datasource.dataKeys.length;k++) { | ||
358 | - var dataKey = datasource.dataKeys[k]; | ||
359 | - if (dataKey.label === label) { | ||
360 | - i++; | ||
361 | - label = name + ' ' + i; | ||
362 | - matches = true; | 401 | + var datasources = scope.widgetType == types.widgetType.alarm.value ? [value.config.alarmSource] : value.config.datasources; |
402 | + if (datasources) { | ||
403 | + for (var d=0;d<datasources.length;d++) { | ||
404 | + var datasource = datasources[d]; | ||
405 | + if (datasource && datasource.dataKeys) { | ||
406 | + for (var k = 0; k < datasource.dataKeys.length; k++) { | ||
407 | + var dataKey = datasource.dataKeys[k]; | ||
408 | + if (dataKey.label === label) { | ||
409 | + i++; | ||
410 | + label = name + ' ' + i; | ||
411 | + matches = true; | ||
412 | + } | ||
363 | } | 413 | } |
364 | } | 414 | } |
365 | } | 415 | } |
@@ -371,10 +421,13 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | @@ -371,10 +421,13 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti | ||
371 | scope.genNextColor = function () { | 421 | scope.genNextColor = function () { |
372 | var i = 0; | 422 | var i = 0; |
373 | var value = ngModelCtrl.$viewValue; | 423 | var value = ngModelCtrl.$viewValue; |
374 | - if (value.config.datasources) { | ||
375 | - for (var d=0;d<value.config.datasources.length;d++) { | ||
376 | - var datasource = value.config.datasources[d]; | ||
377 | - i += datasource.dataKeys.length; | 424 | + var datasources = scope.widgetType == types.widgetType.alarm.value ? [value.config.alarmSource] : value.config.datasources; |
425 | + if (datasources) { | ||
426 | + for (var d=0;d<datasources.length;d++) { | ||
427 | + var datasource = datasources[d]; | ||
428 | + if (datasource && datasource.dataKeys) { | ||
429 | + i += datasource.dataKeys.length; | ||
430 | + } | ||
378 | } | 431 | } |
379 | } | 432 | } |
380 | return utils.getMaterialColor(i); | 433 | return utils.getMaterialColor(i); |
@@ -20,18 +20,46 @@ | @@ -20,18 +20,46 @@ | ||
20 | <md-tab label="{{ 'widget-config.data' | translate }}" | 20 | <md-tab label="{{ 'widget-config.data' | translate }}" |
21 | ng-show="widgetType !== types.widgetType.static.value"> | 21 | ng-show="widgetType !== types.widgetType.static.value"> |
22 | <md-content class="md-padding" layout="column"> | 22 | <md-content class="md-padding" layout="column"> |
23 | - <div ng-show="widgetType === types.widgetType.timeseries.value" layout='column' layout-align="center" | 23 | + <div ng-show="widgetType === types.widgetType.timeseries.value || widgetType === types.widgetType.alarm.value" layout='column' layout-align="center" |
24 | layout-gt-sm='row' layout-align-gt-sm="start center"> | 24 | layout-gt-sm='row' layout-align-gt-sm="start center"> |
25 | <md-checkbox flex aria-label="{{ 'widget-config.use-dashboard-timewindow' | translate }}" | 25 | <md-checkbox flex aria-label="{{ 'widget-config.use-dashboard-timewindow' | translate }}" |
26 | ng-model="useDashboardTimewindow">{{ 'widget-config.use-dashboard-timewindow' | translate }} | 26 | ng-model="useDashboardTimewindow">{{ 'widget-config.use-dashboard-timewindow' | translate }} |
27 | </md-checkbox> | 27 | </md-checkbox> |
28 | <section flex layout="row" layout-align="start center" style="margin-bottom: 16px;"> | 28 | <section flex layout="row" layout-align="start center" style="margin-bottom: 16px;"> |
29 | <span ng-class="{'tb-disabled-label': useDashboardTimewindow}" translate style="padding-right: 8px;">widget-config.timewindow</span> | 29 | <span ng-class="{'tb-disabled-label': useDashboardTimewindow}" translate style="padding-right: 8px;">widget-config.timewindow</span> |
30 | - <tb-timewindow ng-disabled="useDashboardTimewindow" as-button="true" aggregation flex ng-model="timewindow"></tb-timewindow> | 30 | + <tb-timewindow ng-disabled="useDashboardTimewindow" as-button="true" aggregation="{{ widgetType === types.widgetType.timeseries.value }}" |
31 | + flex ng-model="timewindow"></tb-timewindow> | ||
31 | </section> | 32 | </section> |
32 | </div> | 33 | </div> |
34 | + <div ng-show="widgetType === types.widgetType.alarm.value" layout='column' layout-align="center" | ||
35 | + layout-gt-sm='row' layout-align-gt-sm="start center"> | ||
36 | + <md-input-container class="md-block" flex> | ||
37 | + <label translate>alarm.alarm-status</label> | ||
38 | + <md-select ng-model="alarmSearchStatus" style="padding-bottom: 24px;"> | ||
39 | + <md-option ng-repeat="searchStatus in types.alarmSearchStatus" ng-value="searchStatus"> | ||
40 | + {{ ('alarm.search-status.' + searchStatus) | translate }} | ||
41 | + </md-option> | ||
42 | + </md-select> | ||
43 | + </md-input-container> | ||
44 | + <md-input-container flex class="md-block"> | ||
45 | + <label translate>alarm.polling-interval</label> | ||
46 | + <input ng-required="widgetType === types.widgetType.alarm.value" | ||
47 | + type="number" | ||
48 | + step="1" | ||
49 | + min="1" | ||
50 | + name="alarmsPollingInterval" | ||
51 | + ng-model="alarmsPollingInterval"/> | ||
52 | + <div ng-messages="theForm.alarmsPollingInterval.$error" multiple md-auto-hide="false"> | ||
53 | + <div ng-message="required" translate>alarm.polling-interval-required</div> | ||
54 | + <div ng-message="min" translate>alarm.min-polling-interval-message</div> | ||
55 | + </div> | ||
56 | + </md-input-container> | ||
57 | + </div> | ||
33 | <v-accordion id="datasources-accordion" control="datasourcesAccordion" class="vAccordion--default" | 58 | <v-accordion id="datasources-accordion" control="datasourcesAccordion" class="vAccordion--default" |
34 | - ng-show="widgetType !== types.widgetType.rpc.value && widgetType !== types.widgetType.static.value && isDataEnabled"> | 59 | + ng-show="widgetType !== types.widgetType.rpc.value |
60 | + && widgetType !== types.widgetType.alarm.value | ||
61 | + && widgetType !== types.widgetType.static.value | ||
62 | + && isDataEnabled"> | ||
35 | <v-pane id="datasources-pane" expanded="true"> | 63 | <v-pane id="datasources-pane" expanded="true"> |
36 | <v-pane-header> | 64 | <v-pane-header> |
37 | {{ 'widget-config.datasources' | translate }} | 65 | {{ 'widget-config.datasources' | translate }} |
@@ -112,6 +140,24 @@ | @@ -112,6 +140,24 @@ | ||
112 | </v-pane-content> | 140 | </v-pane-content> |
113 | </v-pane> | 141 | </v-pane> |
114 | </v-accordion> | 142 | </v-accordion> |
143 | + <v-accordion id="alarn-source-accordion" control="alarmSourceAccordion" class="vAccordion--default" | ||
144 | + ng-if="widgetType === types.widgetType.alarm.value && isDataEnabled"> | ||
145 | + <v-pane id="alarm-source-pane" expanded="true"> | ||
146 | + <v-pane-header> | ||
147 | + {{ 'widget-config.alarm-source' | translate }} | ||
148 | + </v-pane-header> | ||
149 | + <v-pane-content style="padding: 0 5px;"> | ||
150 | + <tb-datasource flex | ||
151 | + ng-model="alarmSource.value" | ||
152 | + widget-type="widgetType" | ||
153 | + functions-only="functionsOnly" | ||
154 | + alias-controller="aliasController" | ||
155 | + datakey-settings-schema="datakeySettingsSchema" | ||
156 | + generate-data-key="generateDataKey(chip,type)" | ||
157 | + on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})"></tb-datasource> | ||
158 | + </v-pane-content> | ||
159 | + </v-pane> | ||
160 | + </v-accordion> | ||
115 | </md-content> | 161 | </md-content> |
116 | </md-tab> | 162 | </md-tab> |
117 | <md-tab label="{{ 'widget-config.settings' | translate }}"> | 163 | <md-tab label="{{ 'widget-config.settings' | translate }}"> |
@@ -21,7 +21,7 @@ import Subscription from '../api/subscription'; | @@ -21,7 +21,7 @@ import Subscription from '../api/subscription'; | ||
21 | 21 | ||
22 | /*@ngInject*/ | 22 | /*@ngInject*/ |
23 | export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, $compile, tbRaf, types, utils, timeService, | 23 | export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, $compile, tbRaf, types, utils, timeService, |
24 | - datasourceService, entityService, deviceService, visibleRect, isEdit, stDiff, dashboardTimewindow, | 24 | + datasourceService, alarmService, entityService, deviceService, visibleRect, isEdit, isMobile, stDiff, dashboardTimewindow, |
25 | dashboardTimewindowApi, widget, aliasController, stateController, widgetInfo, widgetType) { | 25 | dashboardTimewindowApi, widget, aliasController, stateController, widgetInfo, widgetType) { |
26 | 26 | ||
27 | var vm = this; | 27 | var vm = this; |
@@ -44,13 +44,13 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -44,13 +44,13 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
44 | 44 | ||
45 | var widgetContext = { | 45 | var widgetContext = { |
46 | inited: false, | 46 | inited: false, |
47 | - $scope: $scope, | ||
48 | $container: null, | 47 | $container: null, |
49 | $containerParent: null, | 48 | $containerParent: null, |
50 | width: 0, | 49 | width: 0, |
51 | height: 0, | 50 | height: 0, |
51 | + hideTitlePanel: false, | ||
52 | isEdit: isEdit, | 52 | isEdit: isEdit, |
53 | - isMobile: false, | 53 | + isMobile: isMobile, |
54 | widgetConfig: widget.config, | 54 | widgetConfig: widget.config, |
55 | settings: widget.config.settings, | 55 | settings: widget.config.settings, |
56 | units: widget.config.units || '', | 56 | units: widget.config.units || '', |
@@ -113,6 +113,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -113,6 +113,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
113 | timeService: timeService, | 113 | timeService: timeService, |
114 | deviceService: deviceService, | 114 | deviceService: deviceService, |
115 | datasourceService: datasourceService, | 115 | datasourceService: datasourceService, |
116 | + alarmService: alarmService, | ||
116 | utils: utils, | 117 | utils: utils, |
117 | widgetUtils: widgetContext.utils, | 118 | widgetUtils: widgetContext.utils, |
118 | dashboardTimewindowApi: dashboardTimewindowApi, | 119 | dashboardTimewindowApi: dashboardTimewindowApi, |
@@ -121,6 +122,10 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -121,6 +122,10 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
121 | aliasController: aliasController | 122 | aliasController: aliasController |
122 | }; | 123 | }; |
123 | 124 | ||
125 | + widget.$ctx = function() { | ||
126 | + return widgetContext; | ||
127 | + } | ||
128 | + | ||
124 | var widgetTypeInstance; | 129 | var widgetTypeInstance; |
125 | 130 | ||
126 | vm.useCustomDatasources = false; | 131 | vm.useCustomDatasources = false; |
@@ -285,9 +290,18 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -285,9 +290,18 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
285 | var deferred = $q.defer(); | 290 | var deferred = $q.defer(); |
286 | if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) { | 291 | if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) { |
287 | options = { | 292 | options = { |
288 | - type: widget.type, | ||
289 | - datasources: angular.copy(widget.config.datasources) | ||
290 | - }; | 293 | + type: widget.type |
294 | + } | ||
295 | + if (widget.type == types.widgetType.alarm.value) { | ||
296 | + options.alarmSource = angular.copy(widget.config.alarmSource); | ||
297 | + options.alarmSearchStatus = angular.isDefined(widget.config.alarmSearchStatus) ? | ||
298 | + widget.config.alarmSearchStatus : types.alarmSearchStatus.any; | ||
299 | + options.alarmsPollingInterval = angular.isDefined(widget.config.alarmsPollingInterval) ? | ||
300 | + widget.config.alarmsPollingInterval * 1000 : 5000; | ||
301 | + } else { | ||
302 | + options.datasources = angular.copy(widget.config.datasources) | ||
303 | + } | ||
304 | + | ||
291 | defaultComponentsOptions(options); | 305 | defaultComponentsOptions(options); |
292 | 306 | ||
293 | createSubscription(options).then( | 307 | createSubscription(options).then( |
@@ -320,7 +334,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -320,7 +334,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
320 | $scope.executingRpcRequest = subscription.executingRpcRequest; | 334 | $scope.executingRpcRequest = subscription.executingRpcRequest; |
321 | }, | 335 | }, |
322 | onRpcSuccess: function(subscription) { | 336 | onRpcSuccess: function(subscription) { |
323 | - $scope.executingRpcRequest = subscription.executingRpcRequest; | 337 | + $scope.executingRpcRequest = subscription.executingRpcRequest; |
324 | $scope.rpcErrorText = subscription.rpcErrorText; | 338 | $scope.rpcErrorText = subscription.rpcErrorText; |
325 | $scope.rpcRejection = subscription.rpcRejection; | 339 | $scope.rpcRejection = subscription.rpcRejection; |
326 | }, | 340 | }, |
@@ -436,7 +450,14 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -436,7 +450,14 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
436 | widgetContext.$container = $('#container', containerElement); | 450 | widgetContext.$container = $('#container', containerElement); |
437 | widgetContext.$containerParent = $(containerElement); | 451 | widgetContext.$containerParent = $(containerElement); |
438 | 452 | ||
439 | - $compile($element.contents())($scope); | 453 | + if (widgetSizeDetected) { |
454 | + widgetContext.$container.css('height', widgetContext.height + 'px'); | ||
455 | + widgetContext.$container.css('width', widgetContext.width + 'px'); | ||
456 | + } | ||
457 | + | ||
458 | + widgetContext.$scope = $scope.$new(); | ||
459 | + | ||
460 | + $compile($element.contents())(widgetContext.$scope); | ||
440 | 461 | ||
441 | addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef | 462 | addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef |
442 | } | 463 | } |
@@ -444,6 +465,9 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -444,6 +465,9 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
444 | function destroyWidgetElement() { | 465 | function destroyWidgetElement() { |
445 | removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef | 466 | removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef |
446 | $element.html(''); | 467 | $element.html(''); |
468 | + if (widgetContext.$scope) { | ||
469 | + widgetContext.$scope.$destroy(); | ||
470 | + } | ||
447 | widgetContext.$container = null; | 471 | widgetContext.$container = null; |
448 | widgetContext.$containerParent = null; | 472 | widgetContext.$containerParent = null; |
449 | } | 473 | } |
@@ -594,7 +618,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -594,7 +618,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
594 | 618 | ||
595 | function gridsterItemInitialized(item) { | 619 | function gridsterItemInitialized(item) { |
596 | if (item && item.gridster) { | 620 | if (item && item.gridster) { |
597 | - widgetContext.isMobile = item.gridster.isMobile; | ||
598 | gridsterItemInited = true; | 621 | gridsterItemInited = true; |
599 | onInit(); | 622 | onInit(); |
600 | // gridsterItemElement = $(item.$element); | 623 | // gridsterItemElement = $(item.$element); |
@@ -31,7 +31,7 @@ | @@ -31,7 +31,7 @@ | ||
31 | on-manage-dashboards="vm.openCustomerDashboards(event, vm.grid.detailsConfig.currentItem)" | 31 | on-manage-dashboards="vm.openCustomerDashboards(event, vm.grid.detailsConfig.currentItem)" |
32 | on-delete-customer="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-customer> | 32 | on-delete-customer="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-customer> |
33 | </md-tab> | 33 | </md-tab> |
34 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'attribute.attributes' | translate }}"> | 34 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}"> |
35 | <tb-attribute-table flex | 35 | <tb-attribute-table flex |
36 | entity-id="vm.grid.operatingItem().id.id" | 36 | entity-id="vm.grid.operatingItem().id.id" |
37 | entity-type="{{vm.types.entityType.customer}}" | 37 | entity-type="{{vm.types.entityType.customer}}" |
@@ -39,7 +39,7 @@ | @@ -39,7 +39,7 @@ | ||
39 | default-attribute-scope="{{vm.types.attributesScope.server.value}}"> | 39 | default-attribute-scope="{{vm.types.attributesScope.server.value}}"> |
40 | </tb-attribute-table> | 40 | </tb-attribute-table> |
41 | </md-tab> | 41 | </md-tab> |
42 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'attribute.latest-telemetry' | translate }}"> | 42 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.latest-telemetry' | translate }}"> |
43 | <tb-attribute-table flex | 43 | <tb-attribute-table flex |
44 | entity-id="vm.grid.operatingItem().id.id" | 44 | entity-id="vm.grid.operatingItem().id.id" |
45 | entity-type="{{vm.types.entityType.customer}}" | 45 | entity-type="{{vm.types.entityType.customer}}" |
@@ -48,19 +48,19 @@ | @@ -48,19 +48,19 @@ | ||
48 | disable-attribute-scope-selection="true"> | 48 | disable-attribute-scope-selection="true"> |
49 | </tb-attribute-table> | 49 | </tb-attribute-table> |
50 | </md-tab> | 50 | </md-tab> |
51 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'alarm.alarms' | translate }}"> | 51 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}"> |
52 | <tb-alarm-table flex entity-type="vm.types.entityType.customer" | 52 | <tb-alarm-table flex entity-type="vm.types.entityType.customer" |
53 | entity-id="vm.grid.operatingItem().id.id"> | 53 | entity-id="vm.grid.operatingItem().id.id"> |
54 | </tb-alarm-table> | 54 | </tb-alarm-table> |
55 | </md-tab> | 55 | </md-tab> |
56 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'customer.events' | translate }}"> | 56 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'customer.events' | translate }}"> |
57 | <tb-event-table flex entity-type="vm.types.entityType.customer" | 57 | <tb-event-table flex entity-type="vm.types.entityType.customer" |
58 | entity-id="vm.grid.operatingItem().id.id" | 58 | entity-id="vm.grid.operatingItem().id.id" |
59 | tenant-id="vm.grid.operatingItem().tenantId.id" | 59 | tenant-id="vm.grid.operatingItem().tenantId.id" |
60 | default-event-type="{{vm.types.eventType.error.value}}"> | 60 | default-event-type="{{vm.types.eventType.error.value}}"> |
61 | </tb-event-table> | 61 | </tb-event-table> |
62 | </md-tab> | 62 | </md-tab> |
63 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}"> | 63 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'relation.relations' | translate }}"> |
64 | <tb-relation-table flex | 64 | <tb-relation-table flex |
65 | entity-id="vm.grid.operatingItem().id.id" | 65 | entity-id="vm.grid.operatingItem().id.id" |
66 | entity-type="{{vm.types.entityType.customer}}"> | 66 | entity-type="{{vm.types.entityType.customer}}"> |
@@ -76,6 +76,10 @@ export default function AddWidgetController($scope, widgetService, entityService | @@ -76,6 +76,10 @@ export default function AddWidgetController($scope, widgetService, entityService | ||
76 | link = 'widgetsConfigRpc'; | 76 | link = 'widgetsConfigRpc'; |
77 | break; | 77 | break; |
78 | } | 78 | } |
79 | + case types.widgetType.alarm.value: { | ||
80 | + link = 'widgetsConfigAlarm'; | ||
81 | + break; | ||
82 | + } | ||
79 | case types.widgetType.static.value: { | 83 | case types.widgetType.static.value: { |
80 | link = 'widgetsConfigStatic'; | 84 | link = 'widgetsConfigStatic'; |
81 | break; | 85 | break; |
@@ -68,6 +68,7 @@ export default function DashboardSettingsController($scope, $mdDialog, statesCon | @@ -68,6 +68,7 @@ export default function DashboardSettingsController($scope, $mdDialog, statesCon | ||
68 | vm.gridSettings.color = vm.gridSettings.color || 'rgba(0,0,0,0.870588)'; | 68 | vm.gridSettings.color = vm.gridSettings.color || 'rgba(0,0,0,0.870588)'; |
69 | vm.gridSettings.columns = vm.gridSettings.columns || 24; | 69 | vm.gridSettings.columns = vm.gridSettings.columns || 24; |
70 | vm.gridSettings.margins = vm.gridSettings.margins || [10, 10]; | 70 | vm.gridSettings.margins = vm.gridSettings.margins || [10, 10]; |
71 | + vm.gridSettings.autoFillHeight = angular.isDefined(vm.gridSettings.autoFillHeight) ? vm.gridSettings.autoFillHeight : false; | ||
71 | vm.hMargin = vm.gridSettings.margins[0]; | 72 | vm.hMargin = vm.gridSettings.margins[0]; |
72 | vm.vMargin = vm.gridSettings.margins[1]; | 73 | vm.vMargin = vm.gridSettings.margins[1]; |
73 | vm.gridSettings.backgroundSizeMode = vm.gridSettings.backgroundSizeMode || '100%'; | 74 | vm.gridSettings.backgroundSizeMode = vm.gridSettings.backgroundSizeMode || '100%'; |
@@ -121,6 +121,9 @@ | @@ -121,6 +121,9 @@ | ||
121 | </div> | 121 | </div> |
122 | </md-input-container> | 122 | </md-input-container> |
123 | </div> | 123 | </div> |
124 | + <md-checkbox flex aria-label="{{ 'dashboard.autofill-height' | translate }}" | ||
125 | + ng-model="vm.gridSettings.autoFillHeight">{{ 'dashboard.autofill-height' | translate }} | ||
126 | + </md-checkbox> | ||
124 | <div flex | 127 | <div flex |
125 | ng-required="false" | 128 | ng-required="false" |
126 | md-color-picker | 129 | md-color-picker |
@@ -47,6 +47,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget | @@ -47,6 +47,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget | ||
47 | vm.latestWidgetTypes = []; | 47 | vm.latestWidgetTypes = []; |
48 | vm.timeseriesWidgetTypes = []; | 48 | vm.timeseriesWidgetTypes = []; |
49 | vm.rpcWidgetTypes = []; | 49 | vm.rpcWidgetTypes = []; |
50 | + vm.alarmWidgetTypes = []; | ||
50 | vm.staticWidgetTypes = []; | 51 | vm.staticWidgetTypes = []; |
51 | vm.widgetEditMode = $state.$current.data.widgetEditMode; | 52 | vm.widgetEditMode = $state.$current.data.widgetEditMode; |
52 | vm.iframeMode = $rootScope.iframeMode; | 53 | vm.iframeMode = $rootScope.iframeMode; |
@@ -263,6 +264,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget | @@ -263,6 +264,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget | ||
263 | vm.latestWidgetTypes = []; | 264 | vm.latestWidgetTypes = []; |
264 | vm.timeseriesWidgetTypes = []; | 265 | vm.timeseriesWidgetTypes = []; |
265 | vm.rpcWidgetTypes = []; | 266 | vm.rpcWidgetTypes = []; |
267 | + vm.alarmWidgetTypes = []; | ||
266 | vm.staticWidgetTypes = []; | 268 | vm.staticWidgetTypes = []; |
267 | if (vm.widgetsBundle) { | 269 | if (vm.widgetsBundle) { |
268 | var bundleAlias = vm.widgetsBundle.alias; | 270 | var bundleAlias = vm.widgetsBundle.alias; |
@@ -308,6 +310,8 @@ export default function DashboardController(types, utils, dashboardUtils, widget | @@ -308,6 +310,8 @@ export default function DashboardController(types, utils, dashboardUtils, widget | ||
308 | vm.latestWidgetTypes.push(widget); | 310 | vm.latestWidgetTypes.push(widget); |
309 | } else if (widgetTypeInfo.type === types.widgetType.rpc.value) { | 311 | } else if (widgetTypeInfo.type === types.widgetType.rpc.value) { |
310 | vm.rpcWidgetTypes.push(widget); | 312 | vm.rpcWidgetTypes.push(widget); |
313 | + } else if (widgetTypeInfo.type === types.widgetType.alarm.value) { | ||
314 | + vm.alarmWidgetTypes.push(widget); | ||
311 | } else if (widgetTypeInfo.type === types.widgetType.static.value) { | 315 | } else if (widgetTypeInfo.type === types.widgetType.static.value) { |
312 | vm.staticWidgetTypes.push(widget); | 316 | vm.staticWidgetTypes.push(widget); |
313 | } | 317 | } |
@@ -358,21 +362,6 @@ export default function DashboardController(types, utils, dashboardUtils, widget | @@ -358,21 +362,6 @@ export default function DashboardController(types, utils, dashboardUtils, widget | ||
358 | vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow; | 362 | vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow; |
359 | vm.dashboardCtx.aliasController = new AliasController($scope, $q, $filter, utils, | 363 | vm.dashboardCtx.aliasController = new AliasController($scope, $q, $filter, utils, |
360 | types, entityService, vm.dashboardCtx.stateController, vm.dashboardConfiguration.entityAliases); | 364 | types, entityService, vm.dashboardCtx.stateController, vm.dashboardConfiguration.entityAliases); |
361 | - | ||
362 | - /* entityService.processEntityAliases(vm.dashboard.configuration.entityAliases) | ||
363 | - .then( | ||
364 | - function(resolution) { | ||
365 | - if (resolution.error && !isTenantAdmin()) { | ||
366 | - vm.configurationError = true; | ||
367 | - showAliasesResolutionError(resolution.error); | ||
368 | - } else { | ||
369 | - vm.dashboardConfiguration = vm.dashboard.configuration; | ||
370 | - vm.dashboardCtx.dashboard = vm.dashboard; | ||
371 | - vm.dashboardCtx.aliasesInfo = resolution.aliasesInfo; | ||
372 | - vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow; | ||
373 | - } | ||
374 | - } | ||
375 | - );*/ | ||
376 | }, function fail() { | 365 | }, function fail() { |
377 | vm.configurationError = true; | 366 | vm.configurationError = true; |
378 | }); | 367 | }); |
@@ -744,6 +733,10 @@ export default function DashboardController(types, utils, dashboardUtils, widget | @@ -744,6 +733,10 @@ export default function DashboardController(types, utils, dashboardUtils, widget | ||
744 | link = 'widgetsConfigRpc'; | 733 | link = 'widgetsConfigRpc'; |
745 | break; | 734 | break; |
746 | } | 735 | } |
736 | + case types.widgetType.alarm.value: { | ||
737 | + link = 'widgetsConfigAlarm'; | ||
738 | + break; | ||
739 | + } | ||
747 | case types.widgetType.static.value: { | 740 | case types.widgetType.static.value: { |
748 | link = 'widgetsConfigStatic'; | 741 | link = 'widgetsConfigStatic'; |
749 | break; | 742 | break; |
@@ -851,6 +844,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget | @@ -851,6 +844,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget | ||
851 | vm.timeseriesWidgetTypes = []; | 844 | vm.timeseriesWidgetTypes = []; |
852 | vm.latestWidgetTypes = []; | 845 | vm.latestWidgetTypes = []; |
853 | vm.rpcWidgetTypes = []; | 846 | vm.rpcWidgetTypes = []; |
847 | + vm.alarmWidgetTypes = []; | ||
854 | vm.staticWidgetTypes = []; | 848 | vm.staticWidgetTypes = []; |
855 | } | 849 | } |
856 | 850 |
@@ -52,7 +52,7 @@ | @@ -52,7 +52,7 @@ | ||
52 | <tb-timewindow ng-show="vm.isEdit || vm.displayDashboardTimewindow()" | 52 | <tb-timewindow ng-show="vm.isEdit || vm.displayDashboardTimewindow()" |
53 | is-toolbar | 53 | is-toolbar |
54 | direction="left" | 54 | direction="left" |
55 | - tooltip-direction="bottom" aggregation | 55 | + tooltip-direction="bottom" aggregation="true" |
56 | ng-model="vm.dashboardCtx.dashboardTimewindow"> | 56 | ng-model="vm.dashboardCtx.dashboardTimewindow"> |
57 | </tb-timewindow> | 57 | </tb-timewindow> |
58 | <tb-aliases-entity-select ng-show="!vm.isEdit && vm.displayEntitiesSelect()" | 58 | <tb-aliases-entity-select ng-show="!vm.isEdit && vm.displayEntitiesSelect()" |
@@ -179,6 +179,7 @@ | @@ -179,6 +179,7 @@ | ||
179 | <tb-edit-widget | 179 | <tb-edit-widget |
180 | dashboard="vm.dashboard" | 180 | dashboard="vm.dashboard" |
181 | alias-controller="vm.dashboardCtx.aliasController" | 181 | alias-controller="vm.dashboardCtx.aliasController" |
182 | + widget-edit-mode="vm.widgetEditMode" | ||
182 | widget="vm.editingWidget" | 183 | widget="vm.editingWidget" |
183 | widget-layout="vm.editingWidgetLayout" | 184 | widget-layout="vm.editingWidgetLayout" |
184 | the-form="vm.widgetForm"> | 185 | the-form="vm.widgetForm"> |
@@ -205,7 +206,8 @@ | @@ -205,7 +206,8 @@ | ||
205 | </header-pane> | 206 | </header-pane> |
206 | <div ng-if="vm.isAddingWidget"> | 207 | <div ng-if="vm.isAddingWidget"> |
207 | <md-tabs ng-if="vm.timeseriesWidgetTypes.length > 0 || vm.latestWidgetTypes.length > 0 || | 208 | <md-tabs ng-if="vm.timeseriesWidgetTypes.length > 0 || vm.latestWidgetTypes.length > 0 || |
208 | - vm.rpcWidgetTypes.length > 0 || vm.staticWidgetTypes.length > 0" | 209 | + vm.rpcWidgetTypes.length > 0 || vm.alarmWidgetTypes.length > 0 || |
210 | + vm.staticWidgetTypes.length > 0" | ||
209 | flex | 211 | flex |
210 | class="tb-absolute-fill" md-border-bottom> | 212 | class="tb-absolute-fill" md-border-bottom> |
211 | <md-tab ng-if="vm.timeseriesWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.timeseries' | translate }}"> | 213 | <md-tab ng-if="vm.timeseriesWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.timeseries' | translate }}"> |
@@ -238,6 +240,16 @@ | @@ -238,6 +240,16 @@ | ||
238 | on-widget-clicked="vm.addWidgetFromType(event, widget)"> | 240 | on-widget-clicked="vm.addWidgetFromType(event, widget)"> |
239 | </tb-dashboard> | 241 | </tb-dashboard> |
240 | </md-tab> | 242 | </md-tab> |
243 | + <md-tab ng-if="vm.alarmWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.alarm' | translate }}"> | ||
244 | + <tb-dashboard | ||
245 | + widgets="vm.alarmWidgetTypes" | ||
246 | + is-edit="false" | ||
247 | + is-mobile="true" | ||
248 | + is-edit-action-enabled="false" | ||
249 | + is-remove-action-enabled="false" | ||
250 | + on-widget-clicked="vm.addWidgetFromType(event, widget)"> | ||
251 | + </tb-dashboard> | ||
252 | + </md-tab> | ||
241 | <md-tab ng-if="vm.staticWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.static' | translate }}"> | 253 | <md-tab ng-if="vm.staticWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.static' | translate }}"> |
242 | <tb-dashboard | 254 | <tb-dashboard |
243 | widgets="vm.staticWidgetTypes" | 255 | widgets="vm.staticWidgetTypes" |
@@ -250,7 +262,8 @@ | @@ -250,7 +262,8 @@ | ||
250 | </md-tab> | 262 | </md-tab> |
251 | </md-tabs> | 263 | </md-tabs> |
252 | <span translate ng-if="vm.timeseriesWidgetTypes.length === 0 && vm.latestWidgetTypes.length === 0 && | 264 | <span translate ng-if="vm.timeseriesWidgetTypes.length === 0 && vm.latestWidgetTypes.length === 0 && |
253 | - vm.rpcWidgetTypes.length === 0 && vm.staticWidgetTypes.length === 0 && vm.widgetsBundle" | 265 | + vm.rpcWidgetTypes.length === 0 && vm.alarmWidgetTypes.length === 0 && |
266 | + vm.staticWidgetTypes.length === 0 && vm.widgetsBundle" | ||
254 | layout-align="center center" | 267 | layout-align="center center" |
255 | style="text-transform: uppercase; display: flex;" | 268 | style="text-transform: uppercase; display: flex;" |
256 | class="md-headline tb-absolute-fill">widgets-bundle.empty</span> | 269 | class="md-headline tb-absolute-fill">widgets-bundle.empty</span> |
@@ -131,6 +131,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid | @@ -131,6 +131,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid | ||
131 | scope: { | 131 | scope: { |
132 | dashboard: '=', | 132 | dashboard: '=', |
133 | aliasController: '=', | 133 | aliasController: '=', |
134 | + widgetEditMode: '=', | ||
134 | widget: '=', | 135 | widget: '=', |
135 | widgetLayout: '=', | 136 | widgetLayout: '=', |
136 | theForm: '=' | 137 | theForm: '=' |
@@ -22,7 +22,7 @@ | @@ -22,7 +22,7 @@ | ||
22 | widget-settings-schema="settingsSchema" | 22 | widget-settings-schema="settingsSchema" |
23 | datakey-settings-schema="dataKeySettingsSchema" | 23 | datakey-settings-schema="dataKeySettingsSchema" |
24 | alias-controller="aliasController" | 24 | alias-controller="aliasController" |
25 | - functions-only="functionsOnly" | 25 | + functions-only="widgetEditMode" |
26 | fetch-entity-keys="fetchEntityKeys(entityAliasId, query, type)" | 26 | fetch-entity-keys="fetchEntityKeys(entityAliasId, query, type)" |
27 | on-create-entity-alias="createEntityAlias(event, alias, allowedEntityTypes)" | 27 | on-create-entity-alias="createEntityAlias(event, alias, allowedEntityTypes)" |
28 | the-form="theForm"></tb-widget-config> | 28 | the-form="theForm"></tb-widget-config> |
@@ -49,6 +49,7 @@ | @@ -49,6 +49,7 @@ | ||
49 | state-controller="vm.dashboardCtx.stateController" | 49 | state-controller="vm.dashboardCtx.stateController" |
50 | dashboard-timewindow="vm.dashboardCtx.dashboardTimewindow" | 50 | dashboard-timewindow="vm.dashboardCtx.dashboardTimewindow" |
51 | is-edit="vm.isEdit" | 51 | is-edit="vm.isEdit" |
52 | + autofill-height="vm.layoutCtx.gridSettings.autoFillHeight && !vm.isEdit" | ||
52 | is-mobile="vm.isMobile" | 53 | is-mobile="vm.isMobile" |
53 | is-mobile-disabled="vm.widgetEditMode" | 54 | is-mobile-disabled="vm.widgetEditMode" |
54 | is-edit-action-enabled="vm.isEdit" | 55 | is-edit-action-enabled="vm.isEdit" |
@@ -53,12 +53,6 @@ export default function DashboardStateDialogController($scope, $mdDialog, $filte | @@ -53,12 +53,6 @@ export default function DashboardStateDialogController($scope, $mdDialog, $filte | ||
53 | if (!vm.stateIdTouched && vm.isAdd) { | 53 | if (!vm.stateIdTouched && vm.isAdd) { |
54 | vm.state.id = vm.state.name.toLowerCase().replace(/\W/g,"_"); | 54 | vm.state.id = vm.state.name.toLowerCase().replace(/\W/g,"_"); |
55 | } | 55 | } |
56 | - var result = $filter('filter')(vm.allStates, {name: vm.state.name}, true); | ||
57 | - if (result && result.length && result[0].id !== vm.prevStateId) { | ||
58 | - $scope.theForm.name.$setValidity('stateExists', false); | ||
59 | - } else { | ||
60 | - $scope.theForm.name.$setValidity('stateExists', true); | ||
61 | - } | ||
62 | } | 56 | } |
63 | 57 | ||
64 | function checkStateId() { | 58 | function checkStateId() { |
@@ -37,18 +37,15 @@ | @@ -37,18 +37,15 @@ | ||
37 | <input name="name" required ng-model="vm.state.name"> | 37 | <input name="name" required ng-model="vm.state.name"> |
38 | <div ng-messages="theForm.name.$error"> | 38 | <div ng-messages="theForm.name.$error"> |
39 | <div ng-message="required" translate>dashboard.state-name-required</div> | 39 | <div ng-message="required" translate>dashboard.state-name-required</div> |
40 | - <div ng-message="stateExists" translate>dashboard.state-name-exists</div> | ||
41 | </div> | 40 | </div> |
42 | </md-input-container> | 41 | </md-input-container> |
43 | <md-input-container class="md-block"> | 42 | <md-input-container class="md-block"> |
44 | <label translate>dashboard.state-id</label> | 43 | <label translate>dashboard.state-id</label> |
45 | - <input name="stateId" ng-model="vm.state.id" | ||
46 | - ng-change="vm.stateIdTouched = true" | ||
47 | - ng-pattern="/^[a-zA-Z0-9_]*$/"> | 44 | + <input name="stateId" required ng-model="vm.state.id" |
45 | + ng-change="vm.stateIdTouched = true"> | ||
48 | <div ng-messages="theForm.stateId.$error"> | 46 | <div ng-messages="theForm.stateId.$error"> |
49 | <div ng-message="required" translate>dashboard.state-id-required</div> | 47 | <div ng-message="required" translate>dashboard.state-id-required</div> |
50 | <div ng-message="stateExists" translate>dashboard.state-id-exists</div> | 48 | <div ng-message="stateExists" translate>dashboard.state-id-exists</div> |
51 | - <div ng-message="pattern" translate>dashboard.invalid-state-id-format</div> | ||
52 | </div> | 49 | </div> |
53 | </md-input-container> | 50 | </md-input-container> |
54 | <md-checkbox flex aria-label="{{ 'dashboard.is-root-state' | translate }}" | 51 | <md-checkbox flex aria-label="{{ 'dashboard.is-root-state' | translate }}" |
@@ -97,10 +97,10 @@ export default function DefaultStateController($scope, $location, $state, $state | @@ -97,10 +97,10 @@ export default function DefaultStateController($scope, $location, $state, $state | ||
97 | 97 | ||
98 | function getStateName(id, state) { | 98 | function getStateName(id, state) { |
99 | var result = ''; | 99 | var result = ''; |
100 | - var translationId = types.translate.dashboardStatePrefix + id; | 100 | + var translationId = types.translate.customTranslationsPrefix + state.name; |
101 | var translation = $translate.instant(translationId); | 101 | var translation = $translate.instant(translationId); |
102 | if (translation != translationId) { | 102 | if (translation != translationId) { |
103 | - result = translation; | 103 | + result = translation + ''; |
104 | } else { | 104 | } else { |
105 | result = state.name; | 105 | result = state.name; |
106 | } | 106 | } |
@@ -17,7 +17,7 @@ | @@ -17,7 +17,7 @@ | ||
17 | import './entity-state-controller.scss'; | 17 | import './entity-state-controller.scss'; |
18 | 18 | ||
19 | /*@ngInject*/ | 19 | /*@ngInject*/ |
20 | -export default function EntityStateController($scope, $location, $state, $stateParams, $q, $translate, types, dashboardUtils, entityService) { | 20 | +export default function EntityStateController($scope, $location, $state, $stateParams, $q, $translate, utils, types, dashboardUtils, entityService) { |
21 | 21 | ||
22 | var vm = this; | 22 | var vm = this; |
23 | 23 | ||
@@ -106,18 +106,17 @@ export default function EntityStateController($scope, $location, $state, $stateP | @@ -106,18 +106,17 @@ export default function EntityStateController($scope, $location, $state, $stateP | ||
106 | function getStateName(index) { | 106 | function getStateName(index) { |
107 | var result = ''; | 107 | var result = ''; |
108 | if (vm.stateObject[index]) { | 108 | if (vm.stateObject[index]) { |
109 | + var stateName = vm.states[vm.stateObject[index].id].name; | ||
110 | + var translationId = types.translate.customTranslationsPrefix + stateName; | ||
111 | + var translation = $translate.instant(translationId); | ||
112 | + if (translation != translationId) { | ||
113 | + stateName = translation + ''; | ||
114 | + } | ||
109 | var params = vm.stateObject[index].params; | 115 | var params = vm.stateObject[index].params; |
110 | if (params && params.entityName) { | 116 | if (params && params.entityName) { |
111 | - result = params.entityName; | 117 | + result = utils.insertVariable(stateName, 'entityName', params.entityName); |
112 | } else { | 118 | } else { |
113 | - var id = vm.stateObject[index].id; | ||
114 | - var translationId = types.translate.dashboardStatePrefix + id; | ||
115 | - var translation = $translate.instant(translationId); | ||
116 | - if (translation != translationId) { | ||
117 | - result = translation; | ||
118 | - } else { | ||
119 | - result = vm.states[vm.stateObject[index].id].name; | ||
120 | - } | 119 | + result = stateName; |
121 | } | 120 | } |
122 | } | 121 | } |
123 | return result; | 122 | return result; |
@@ -243,11 +242,9 @@ export default function EntityStateController($scope, $location, $state, $stateP | @@ -243,11 +242,9 @@ export default function EntityStateController($scope, $location, $state, $stateP | ||
243 | } | 242 | } |
244 | 243 | ||
245 | function gotoState(stateId, update, openRightLayout) { | 244 | function gotoState(stateId, update, openRightLayout) { |
246 | - if (vm.dashboardCtrl.dashboardCtx.state != stateId) { | ||
247 | - vm.dashboardCtrl.openDashboardState(stateId, openRightLayout); | ||
248 | - if (update) { | ||
249 | - updateLocation(); | ||
250 | - } | 245 | + vm.dashboardCtrl.openDashboardState(stateId, openRightLayout); |
246 | + if (update) { | ||
247 | + updateLocation(); | ||
251 | } | 248 | } |
252 | } | 249 | } |
253 | 250 |
@@ -32,7 +32,7 @@ | @@ -32,7 +32,7 @@ | ||
32 | on-manage-credentials="vm.manageCredentials(event, vm.grid.detailsConfig.currentItem)" | 32 | on-manage-credentials="vm.manageCredentials(event, vm.grid.detailsConfig.currentItem)" |
33 | on-delete-device="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-device> | 33 | on-delete-device="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-device> |
34 | </md-tab> | 34 | </md-tab> |
35 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'attribute.attributes' | translate }}"> | 35 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}"> |
36 | <tb-attribute-table flex | 36 | <tb-attribute-table flex |
37 | entity-id="vm.grid.operatingItem().id.id" | 37 | entity-id="vm.grid.operatingItem().id.id" |
38 | entity-type="{{vm.types.entityType.device}}" | 38 | entity-type="{{vm.types.entityType.device}}" |
@@ -40,7 +40,7 @@ | @@ -40,7 +40,7 @@ | ||
40 | default-attribute-scope="{{vm.types.attributesScope.client.value}}"> | 40 | default-attribute-scope="{{vm.types.attributesScope.client.value}}"> |
41 | </tb-attribute-table> | 41 | </tb-attribute-table> |
42 | </md-tab> | 42 | </md-tab> |
43 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'attribute.latest-telemetry' | translate }}"> | 43 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.latest-telemetry' | translate }}"> |
44 | <tb-attribute-table flex | 44 | <tb-attribute-table flex |
45 | entity-id="vm.grid.operatingItem().id.id" | 45 | entity-id="vm.grid.operatingItem().id.id" |
46 | entity-type="{{vm.types.entityType.device}}" | 46 | entity-type="{{vm.types.entityType.device}}" |
@@ -49,19 +49,19 @@ | @@ -49,19 +49,19 @@ | ||
49 | disable-attribute-scope-selection="true"> | 49 | disable-attribute-scope-selection="true"> |
50 | </tb-attribute-table> | 50 | </tb-attribute-table> |
51 | </md-tab> | 51 | </md-tab> |
52 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'alarm.alarms' | translate }}"> | 52 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}"> |
53 | <tb-alarm-table flex entity-type="vm.types.entityType.device" | 53 | <tb-alarm-table flex entity-type="vm.types.entityType.device" |
54 | entity-id="vm.grid.operatingItem().id.id"> | 54 | entity-id="vm.grid.operatingItem().id.id"> |
55 | </tb-alarm-table> | 55 | </tb-alarm-table> |
56 | </md-tab> | 56 | </md-tab> |
57 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'device.events' | translate }}"> | 57 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'device.events' | translate }}"> |
58 | <tb-event-table flex entity-type="vm.types.entityType.device" | 58 | <tb-event-table flex entity-type="vm.types.entityType.device" |
59 | entity-id="vm.grid.operatingItem().id.id" | 59 | entity-id="vm.grid.operatingItem().id.id" |
60 | tenant-id="vm.grid.operatingItem().tenantId.id" | 60 | tenant-id="vm.grid.operatingItem().tenantId.id" |
61 | default-event-type="{{vm.types.eventType.error.value}}"> | 61 | default-event-type="{{vm.types.eventType.error.value}}"> |
62 | </tb-event-table> | 62 | </tb-event-table> |
63 | </md-tab> | 63 | </md-tab> |
64 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}"> | 64 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'relation.relations' | translate }}"> |
65 | <tb-relation-table flex | 65 | <tb-relation-table flex |
66 | entity-id="vm.grid.operatingItem().id.id" | 66 | entity-id="vm.grid.operatingItem().id.id" |
67 | entity-type="{{vm.types.entityType.device}}"> | 67 | entity-type="{{vm.types.entityType.device}}"> |
@@ -87,6 +87,7 @@ export default angular.module('thingsboard.help', []) | @@ -87,6 +87,7 @@ export default angular.module('thingsboard.help', []) | ||
87 | widgetsConfigTimeseries: helpBaseUrl + "/docs/user-guide/ui/dashboards#timeseries", | 87 | widgetsConfigTimeseries: helpBaseUrl + "/docs/user-guide/ui/dashboards#timeseries", |
88 | widgetsConfigLatest: helpBaseUrl + "/docs/user-guide/ui/dashboards#latest", | 88 | widgetsConfigLatest: helpBaseUrl + "/docs/user-guide/ui/dashboards#latest", |
89 | widgetsConfigRpc: helpBaseUrl + "/docs/user-guide/ui/dashboards#rpc", | 89 | widgetsConfigRpc: helpBaseUrl + "/docs/user-guide/ui/dashboards#rpc", |
90 | + widgetsConfigAlarm: helpBaseUrl + "/docs/user-guide/ui/dashboards#alarm", | ||
90 | widgetsConfigStatic: helpBaseUrl + "/docs/user-guide/ui/dashboards#static", | 91 | widgetsConfigStatic: helpBaseUrl + "/docs/user-guide/ui/dashboards#static", |
91 | }, | 92 | }, |
92 | getPluginLink: function(plugin) { | 93 | getPluginLink: function(plugin) { |
@@ -131,6 +131,7 @@ export default angular.module('thingsboard.locale', []) | @@ -131,6 +131,7 @@ export default angular.module('thingsboard.locale', []) | ||
131 | "type": "Type", | 131 | "type": "Type", |
132 | "severity": "Severity", | 132 | "severity": "Severity", |
133 | "originator": "Originator", | 133 | "originator": "Originator", |
134 | + "originator-type": "Originator type", | ||
134 | "details": "Details", | 135 | "details": "Details", |
135 | "status": "Status", | 136 | "status": "Status", |
136 | "alarm-details": "Alarm details", | 137 | "alarm-details": "Alarm details", |
@@ -144,7 +145,17 @@ export default angular.module('thingsboard.locale', []) | @@ -144,7 +145,17 @@ export default angular.module('thingsboard.locale', []) | ||
144 | "severity-warning": "Warning", | 145 | "severity-warning": "Warning", |
145 | "severity-indeterminate": "Indeterminate", | 146 | "severity-indeterminate": "Indeterminate", |
146 | "acknowledge": "Acknowledge", | 147 | "acknowledge": "Acknowledge", |
147 | - "clear": "Clear" | 148 | + "clear": "Clear", |
149 | + "search": "Search alarms", | ||
150 | + "selected-alarms": "{ count, select, 1 {1 alarm} other {# alarms} } selected", | ||
151 | + "no-data": "No data to display", | ||
152 | + "polling-interval": "Alarms polling interval (sec)", | ||
153 | + "polling-interval-required": "Alarms polling interval is required.", | ||
154 | + "min-polling-interval-message": "At least 1 sec polling interval is allowed.", | ||
155 | + "aknowledge-alarms-title": "Acknowledge { count, select, 1 {1 alarm} other {# alarms} }", | ||
156 | + "aknowledge-alarms-text": "Are you sure you want to acknowledge { count, select, 1 {1 alarm} other {# alarms} }?", | ||
157 | + "clear-alarms-title": "Clear { count, select, 1 {1 alarm} other {# alarms} }", | ||
158 | + "clear-alarms-text": "Are you sure you want to clear { count, select, 1 {1 alarm} other {# alarms} }?" | ||
148 | }, | 159 | }, |
149 | "alias": { | 160 | "alias": { |
150 | "add": "Add alias", | 161 | "add": "Add alias", |
@@ -420,6 +431,7 @@ export default angular.module('thingsboard.locale', []) | @@ -420,6 +431,7 @@ export default angular.module('thingsboard.locale', []) | ||
420 | "vertical-margin-required": "Vertical margin value is required.", | 431 | "vertical-margin-required": "Vertical margin value is required.", |
421 | "min-vertical-margin-message": "Only 0 is allowed as minimum vertical margin value.", | 432 | "min-vertical-margin-message": "Only 0 is allowed as minimum vertical margin value.", |
422 | "max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value.", | 433 | "max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value.", |
434 | + "autofill-height": "Auto fill layout height", | ||
423 | "display-title": "Display dashboard title", | 435 | "display-title": "Display dashboard title", |
424 | "toolbar-always-open": "Keep toolbar opened", | 436 | "toolbar-always-open": "Keep toolbar opened", |
425 | "title-color": "Title color", | 437 | "title-color": "Title color", |
@@ -461,11 +473,9 @@ export default angular.module('thingsboard.locale', []) | @@ -461,11 +473,9 @@ export default angular.module('thingsboard.locale', []) | ||
461 | "state": "Dashboard state", | 473 | "state": "Dashboard state", |
462 | "state-name": "Name", | 474 | "state-name": "Name", |
463 | "state-name-required": "Dashboard state name is required.", | 475 | "state-name-required": "Dashboard state name is required.", |
464 | - "state-name-exists": "Dashboard state with the same name is already exists.", | ||
465 | "state-id": "State Id", | 476 | "state-id": "State Id", |
466 | "state-id-required": "Dashboard state id is required.", | 477 | "state-id-required": "Dashboard state id is required.", |
467 | "state-id-exists": "Dashboard state with the same id is already exists.", | 478 | "state-id-exists": "Dashboard state with the same id is already exists.", |
468 | - "invalid-state-id-format": "Only alphanumeric characters and underscore are allowed.", | ||
469 | "is-root-state": "Root state", | 479 | "is-root-state": "Root state", |
470 | "delete-state-title": "Delete dashboard state", | 480 | "delete-state-title": "Delete dashboard state", |
471 | "delete-state-text": "Are you sure you want delete dashboard state with name '{{stateName}}'?", | 481 | "delete-state-text": "Are you sure you want delete dashboard state with name '{{stateName}}'?", |
@@ -486,8 +496,10 @@ export default angular.module('thingsboard.locale', []) | @@ -486,8 +496,10 @@ export default angular.module('thingsboard.locale', []) | ||
486 | "configuration": "Data key configuration", | 496 | "configuration": "Data key configuration", |
487 | "timeseries": "Timeseries", | 497 | "timeseries": "Timeseries", |
488 | "attributes": "Attributes", | 498 | "attributes": "Attributes", |
499 | + "alarm": "Alarm fields", | ||
489 | "timeseries-required": "Entity timeseries are required.", | 500 | "timeseries-required": "Entity timeseries are required.", |
490 | "timeseries-or-attributes-required": "Entity timeseries/attributes are required.", | 501 | "timeseries-or-attributes-required": "Entity timeseries/attributes are required.", |
502 | + "alarm-fields-required": "Alarm fields are required.", | ||
491 | "function-types": "Function types", | 503 | "function-types": "Function types", |
492 | "function-types-required": "Function types are required." | 504 | "function-types-required": "Function types are required." |
493 | }, | 505 | }, |
@@ -637,6 +649,8 @@ export default angular.module('thingsboard.locale', []) | @@ -637,6 +649,8 @@ export default angular.module('thingsboard.locale', []) | ||
637 | "no-aliases-found": "No aliases found.", | 649 | "no-aliases-found": "No aliases found.", |
638 | "no-alias-matching": "'{{alias}}' not found.", | 650 | "no-alias-matching": "'{{alias}}' not found.", |
639 | "create-new-alias": "Create a new one!", | 651 | "create-new-alias": "Create a new one!", |
652 | + "key": "Key", | ||
653 | + "key-name": "Key name", | ||
640 | "no-keys-found": "No keys found.", | 654 | "no-keys-found": "No keys found.", |
641 | "no-key-matching": "'{{key}}' not found.", | 655 | "no-key-matching": "'{{key}}' not found.", |
642 | "create-new-key": "Create a new one!", | 656 | "create-new-key": "Create a new one!", |
@@ -1046,6 +1060,7 @@ export default angular.module('thingsboard.locale', []) | @@ -1046,6 +1060,7 @@ export default angular.module('thingsboard.locale', []) | ||
1046 | "timeseries": "Time series", | 1060 | "timeseries": "Time series", |
1047 | "latest-values": "Latest values", | 1061 | "latest-values": "Latest values", |
1048 | "rpc": "Control widget", | 1062 | "rpc": "Control widget", |
1063 | + "alarm": "Alarm widget", | ||
1049 | "static": "Static widget", | 1064 | "static": "Static widget", |
1050 | "select-widget-type": "Select widget type", | 1065 | "select-widget-type": "Select widget type", |
1051 | "missing-widget-title-error": "Widget title must be specified!", | 1066 | "missing-widget-title-error": "Widget title must be specified!", |
@@ -1133,7 +1148,8 @@ export default angular.module('thingsboard.locale', []) | @@ -1133,7 +1148,8 @@ export default angular.module('thingsboard.locale', []) | ||
1133 | "datasource-parameters": "Parameters", | 1148 | "datasource-parameters": "Parameters", |
1134 | "remove-datasource": "Remove datasource", | 1149 | "remove-datasource": "Remove datasource", |
1135 | "add-datasource": "Add datasource", | 1150 | "add-datasource": "Add datasource", |
1136 | - "target-device": "Target device" | 1151 | + "target-device": "Target device", |
1152 | + "alarm-source": "Alarm source" | ||
1137 | }, | 1153 | }, |
1138 | "widget-type": { | 1154 | "widget-type": { |
1139 | "import": "Import widget type", | 1155 | "import": "Import widget type", |
@@ -1150,6 +1166,8 @@ export default angular.module('thingsboard.locale', []) | @@ -1150,6 +1166,8 @@ export default angular.module('thingsboard.locale', []) | ||
1150 | "zh_CN": "Chinese", | 1166 | "zh_CN": "Chinese", |
1151 | "ru_RU": "Russian", | 1167 | "ru_RU": "Russian", |
1152 | "es_ES": "Spanish" | 1168 | "es_ES": "Spanish" |
1169 | + }, | ||
1170 | + "custom": { | ||
1153 | } | 1171 | } |
1154 | } | 1172 | } |
1155 | } | 1173 | } |
@@ -18,7 +18,7 @@ | @@ -18,7 +18,7 @@ | ||
18 | export default function ThingsboardMissingTranslateHandler($log, types) { | 18 | export default function ThingsboardMissingTranslateHandler($log, types) { |
19 | 19 | ||
20 | return function (translationId) { | 20 | return function (translationId) { |
21 | - if (translationId && !translationId.startsWith(types.translate.dashboardStatePrefix)) { | 21 | + if (translationId && !translationId.startsWith(types.translate.customTranslationsPrefix)) { |
22 | $log.warn('Translation for ' + translationId + ' doesn\'t exist'); | 22 | $log.warn('Translation for ' + translationId + ' doesn\'t exist'); |
23 | } | 23 | } |
24 | }; | 24 | }; |
@@ -31,7 +31,7 @@ | @@ -31,7 +31,7 @@ | ||
31 | on-export-plugin="vm.exportPlugin(event, vm.grid.detailsConfig.currentItem)" | 31 | on-export-plugin="vm.exportPlugin(event, vm.grid.detailsConfig.currentItem)" |
32 | on-delete-plugin="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-plugin> | 32 | on-delete-plugin="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-plugin> |
33 | </md-tab> | 33 | </md-tab> |
34 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" label="{{ 'attribute.attributes' | translate }}"> | 34 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}"> |
35 | <tb-attribute-table flex | 35 | <tb-attribute-table flex |
36 | entity-id="vm.grid.operatingItem().id.id" | 36 | entity-id="vm.grid.operatingItem().id.id" |
37 | entity-type="{{vm.types.entityType.plugin}}" | 37 | entity-type="{{vm.types.entityType.plugin}}" |
@@ -39,7 +39,7 @@ | @@ -39,7 +39,7 @@ | ||
39 | default-attribute-scope="{{vm.types.attributesScope.server.value}}"> | 39 | default-attribute-scope="{{vm.types.attributesScope.server.value}}"> |
40 | </tb-attribute-table> | 40 | </tb-attribute-table> |
41 | </md-tab> | 41 | </md-tab> |
42 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" label="{{ 'attribute.latest-telemetry' | translate }}"> | 42 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.latest-telemetry' | translate }}"> |
43 | <tb-attribute-table flex | 43 | <tb-attribute-table flex |
44 | entity-id="vm.grid.operatingItem().id.id" | 44 | entity-id="vm.grid.operatingItem().id.id" |
45 | entity-type="{{vm.types.entityType.plugin}}" | 45 | entity-type="{{vm.types.entityType.plugin}}" |
@@ -48,19 +48,19 @@ | @@ -48,19 +48,19 @@ | ||
48 | disable-attribute-scope-selection="true"> | 48 | disable-attribute-scope-selection="true"> |
49 | </tb-attribute-table> | 49 | </tb-attribute-table> |
50 | </md-tab> | 50 | </md-tab> |
51 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" label="{{ 'alarm.alarms' | translate }}"> | 51 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}"> |
52 | <tb-alarm-table flex entity-type="vm.types.entityType.plugin" | 52 | <tb-alarm-table flex entity-type="vm.types.entityType.plugin" |
53 | entity-id="vm.grid.operatingItem().id.id"> | 53 | entity-id="vm.grid.operatingItem().id.id"> |
54 | </tb-alarm-table> | 54 | </tb-alarm-table> |
55 | </md-tab> | 55 | </md-tab> |
56 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" label="{{ 'plugin.events' | translate }}"> | 56 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'plugin.events' | translate }}"> |
57 | <tb-event-table flex entity-type="vm.types.entityType.plugin" | 57 | <tb-event-table flex entity-type="vm.types.entityType.plugin" |
58 | entity-id="vm.grid.operatingItem().id.id" | 58 | entity-id="vm.grid.operatingItem().id.id" |
59 | tenant-id="vm.grid.operatingItem().tenantId.id" | 59 | tenant-id="vm.grid.operatingItem().tenantId.id" |
60 | default-event-type="{{vm.types.eventType.lcEvent.value}}"> | 60 | default-event-type="{{vm.types.eventType.lcEvent.value}}"> |
61 | </tb-event-table> | 61 | </tb-event-table> |
62 | </md-tab> | 62 | </md-tab> |
63 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" label="{{ 'relation.relations' | translate }}"> | 63 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'relation.relations' | translate }}"> |
64 | <tb-relation-table flex | 64 | <tb-relation-table flex |
65 | entity-id="vm.grid.operatingItem().id.id" | 65 | entity-id="vm.grid.operatingItem().id.id" |
66 | entity-type="{{vm.types.entityType.plugin}}"> | 66 | entity-type="{{vm.types.entityType.plugin}}"> |
@@ -31,7 +31,7 @@ | @@ -31,7 +31,7 @@ | ||
31 | on-export-rule="vm.exportRule(event, vm.grid.detailsConfig.currentItem)" | 31 | on-export-rule="vm.exportRule(event, vm.grid.detailsConfig.currentItem)" |
32 | on-delete-rule="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-rule> | 32 | on-delete-rule="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-rule> |
33 | </md-tab> | 33 | </md-tab> |
34 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" label="{{ 'attribute.attributes' | translate }}"> | 34 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}"> |
35 | <tb-attribute-table flex | 35 | <tb-attribute-table flex |
36 | entity-id="vm.grid.operatingItem().id.id" | 36 | entity-id="vm.grid.operatingItem().id.id" |
37 | entity-type="{{vm.types.entityType.rule}}" | 37 | entity-type="{{vm.types.entityType.rule}}" |
@@ -39,7 +39,7 @@ | @@ -39,7 +39,7 @@ | ||
39 | default-attribute-scope="{{vm.types.attributesScope.server.value}}"> | 39 | default-attribute-scope="{{vm.types.attributesScope.server.value}}"> |
40 | </tb-attribute-table> | 40 | </tb-attribute-table> |
41 | </md-tab> | 41 | </md-tab> |
42 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" label="{{ 'attribute.latest-telemetry' | translate }}"> | 42 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.latest-telemetry' | translate }}"> |
43 | <tb-attribute-table flex | 43 | <tb-attribute-table flex |
44 | entity-id="vm.grid.operatingItem().id.id" | 44 | entity-id="vm.grid.operatingItem().id.id" |
45 | entity-type="{{vm.types.entityType.rule}}" | 45 | entity-type="{{vm.types.entityType.rule}}" |
@@ -48,19 +48,19 @@ | @@ -48,19 +48,19 @@ | ||
48 | disable-attribute-scope-selection="true"> | 48 | disable-attribute-scope-selection="true"> |
49 | </tb-attribute-table> | 49 | </tb-attribute-table> |
50 | </md-tab> | 50 | </md-tab> |
51 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" label="{{ 'alarm.alarms' | translate }}"> | 51 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}"> |
52 | <tb-alarm-table flex entity-type="vm.types.entityType.rule" | 52 | <tb-alarm-table flex entity-type="vm.types.entityType.rule" |
53 | entity-id="vm.grid.operatingItem().id.id"> | 53 | entity-id="vm.grid.operatingItem().id.id"> |
54 | </tb-alarm-table> | 54 | </tb-alarm-table> |
55 | </md-tab> | 55 | </md-tab> |
56 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" label="{{ 'rule.events' | translate }}"> | 56 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'rule.events' | translate }}"> |
57 | <tb-event-table flex entity-type="vm.types.entityType.rule" | 57 | <tb-event-table flex entity-type="vm.types.entityType.rule" |
58 | entity-id="vm.grid.operatingItem().id.id" | 58 | entity-id="vm.grid.operatingItem().id.id" |
59 | tenant-id="vm.grid.operatingItem().tenantId.id" | 59 | tenant-id="vm.grid.operatingItem().tenantId.id" |
60 | default-event-type="{{vm.types.eventType.lcEvent.value}}"> | 60 | default-event-type="{{vm.types.eventType.lcEvent.value}}"> |
61 | </tb-event-table> | 61 | </tb-event-table> |
62 | </md-tab> | 62 | </md-tab> |
63 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" label="{{ 'relation.relations' | translate }}"> | 63 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'relation.relations' | translate }}"> |
64 | <tb-relation-table flex | 64 | <tb-relation-table flex |
65 | entity-id="vm.grid.operatingItem().id.id" | 65 | entity-id="vm.grid.operatingItem().id.id" |
66 | entity-type="{{vm.types.entityType.rule}}"> | 66 | entity-type="{{vm.types.entityType.rule}}"> |
@@ -29,7 +29,7 @@ | @@ -29,7 +29,7 @@ | ||
29 | on-manage-users="vm.openTenantUsers(event, vm.grid.detailsConfig.currentItem)" | 29 | on-manage-users="vm.openTenantUsers(event, vm.grid.detailsConfig.currentItem)" |
30 | on-delete-tenant="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-tenant> | 30 | on-delete-tenant="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-tenant> |
31 | </md-tab> | 31 | </md-tab> |
32 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'attribute.attributes' | translate }}"> | 32 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}"> |
33 | <tb-attribute-table flex | 33 | <tb-attribute-table flex |
34 | entity-id="vm.grid.operatingItem().id.id" | 34 | entity-id="vm.grid.operatingItem().id.id" |
35 | entity-type="{{vm.types.entityType.tenant}}" | 35 | entity-type="{{vm.types.entityType.tenant}}" |
@@ -37,7 +37,7 @@ | @@ -37,7 +37,7 @@ | ||
37 | default-attribute-scope="{{vm.types.attributesScope.server.value}}"> | 37 | default-attribute-scope="{{vm.types.attributesScope.server.value}}"> |
38 | </tb-attribute-table> | 38 | </tb-attribute-table> |
39 | </md-tab> | 39 | </md-tab> |
40 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'attribute.latest-telemetry' | translate }}"> | 40 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.latest-telemetry' | translate }}"> |
41 | <tb-attribute-table flex | 41 | <tb-attribute-table flex |
42 | entity-id="vm.grid.operatingItem().id.id" | 42 | entity-id="vm.grid.operatingItem().id.id" |
43 | entity-type="{{vm.types.entityType.tenant}}" | 43 | entity-type="{{vm.types.entityType.tenant}}" |
@@ -46,19 +46,19 @@ | @@ -46,19 +46,19 @@ | ||
46 | disable-attribute-scope-selection="true"> | 46 | disable-attribute-scope-selection="true"> |
47 | </tb-attribute-table> | 47 | </tb-attribute-table> |
48 | </md-tab> | 48 | </md-tab> |
49 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'alarm.alarms' | translate }}"> | 49 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}"> |
50 | <tb-alarm-table flex entity-type="vm.types.entityType.tenant" | 50 | <tb-alarm-table flex entity-type="vm.types.entityType.tenant" |
51 | entity-id="vm.grid.operatingItem().id.id"> | 51 | entity-id="vm.grid.operatingItem().id.id"> |
52 | </tb-alarm-table> | 52 | </tb-alarm-table> |
53 | </md-tab> | 53 | </md-tab> |
54 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'tenant.events' | translate }}"> | 54 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'tenant.events' | translate }}"> |
55 | <tb-event-table flex entity-type="vm.types.entityType.tenant" | 55 | <tb-event-table flex entity-type="vm.types.entityType.tenant" |
56 | entity-id="vm.grid.operatingItem().id.id" | 56 | entity-id="vm.grid.operatingItem().id.id" |
57 | tenant-id="vm.types.id.nullUid" | 57 | tenant-id="vm.types.id.nullUid" |
58 | default-event-type="{{vm.types.eventType.error.value}}"> | 58 | default-event-type="{{vm.types.eventType.error.value}}"> |
59 | </tb-event-table> | 59 | </tb-event-table> |
60 | </md-tab> | 60 | </md-tab> |
61 | - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}"> | 61 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'relation.relations' | translate }}"> |
62 | <tb-relation-table flex | 62 | <tb-relation-table flex |
63 | entity-id="vm.grid.operatingItem().id.id" | 63 | entity-id="vm.grid.operatingItem().id.id" |
64 | entity-type="{{vm.types.entityType.tenant}}"> | 64 | entity-type="{{vm.types.entityType.tenant}}"> |
ui/src/app/widget/lib/alarms-table-widget.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 | +import './alarms-table-widget.scss'; | ||
18 | + | ||
19 | +/* eslint-disable import/no-unresolved, import/default */ | ||
20 | + | ||
21 | +import alarmsTableWidgetTemplate from './alarms-table-widget.tpl.html'; | ||
22 | +import alarmDetailsDialogTemplate from '../../alarm/alarm-details-dialog.tpl.html'; | ||
23 | + | ||
24 | +/* eslint-enable import/no-unresolved, import/default */ | ||
25 | + | ||
26 | +import tinycolor from 'tinycolor2'; | ||
27 | +import cssjs from '../../../vendor/css.js/css'; | ||
28 | + | ||
29 | +export default angular.module('thingsboard.widgets.alarmsTableWidget', []) | ||
30 | + .directive('tbAlarmsTableWidget', AlarmsTableWidget) | ||
31 | + .name; | ||
32 | + | ||
33 | +/*@ngInject*/ | ||
34 | +function AlarmsTableWidget() { | ||
35 | + return { | ||
36 | + restrict: "E", | ||
37 | + scope: true, | ||
38 | + bindToController: { | ||
39 | + tableId: '=', | ||
40 | + ctx: '=' | ||
41 | + }, | ||
42 | + controller: AlarmsTableWidgetController, | ||
43 | + controllerAs: 'vm', | ||
44 | + templateUrl: alarmsTableWidgetTemplate | ||
45 | + }; | ||
46 | +} | ||
47 | + | ||
48 | +/*@ngInject*/ | ||
49 | +function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDialog, $document, $translate, $q, alarmService, utils, types) { | ||
50 | + var vm = this; | ||
51 | + | ||
52 | + vm.stylesInfo = {}; | ||
53 | + vm.contentsInfo = {}; | ||
54 | + vm.columnWidth = {}; | ||
55 | + | ||
56 | + vm.showData = true; | ||
57 | + vm.hasData = false; | ||
58 | + | ||
59 | + vm.alarms = []; | ||
60 | + vm.alarmsCount = 0; | ||
61 | + vm.selectedAlarms = [] | ||
62 | + | ||
63 | + vm.alarmSource = null; | ||
64 | + vm.allAlarms = null; | ||
65 | + | ||
66 | + vm.currentAlarm = null; | ||
67 | + | ||
68 | + vm.enableSelection = true; | ||
69 | + vm.displayDetails = true; | ||
70 | + vm.allowAcknowledgment = true; | ||
71 | + vm.allowClear = true; | ||
72 | + vm.displayPagination = true; | ||
73 | + vm.defaultPageSize = 10; | ||
74 | + vm.defaultSortOrder = '-'+types.alarmFields.createdTime.value; | ||
75 | + | ||
76 | + vm.query = { | ||
77 | + order: vm.defaultSortOrder, | ||
78 | + limit: vm.defaultPageSize, | ||
79 | + page: 1, | ||
80 | + search: null | ||
81 | + }; | ||
82 | + | ||
83 | + vm.searchAction = { | ||
84 | + name: 'action.search', | ||
85 | + show: true, | ||
86 | + onAction: function() { | ||
87 | + vm.enterFilterMode(); | ||
88 | + }, | ||
89 | + icon: 'search' | ||
90 | + }; | ||
91 | + | ||
92 | + vm.enterFilterMode = enterFilterMode; | ||
93 | + vm.exitFilterMode = exitFilterMode; | ||
94 | + vm.onReorder = onReorder; | ||
95 | + vm.onPaginate = onPaginate; | ||
96 | + vm.onRowClick = onRowClick; | ||
97 | + vm.isCurrent = isCurrent; | ||
98 | + vm.openAlarmDetails = openAlarmDetails; | ||
99 | + vm.ackAlarms = ackAlarms; | ||
100 | + vm.clearAlarms = clearAlarms; | ||
101 | + | ||
102 | + vm.cellStyle = cellStyle; | ||
103 | + vm.cellContent = cellContent; | ||
104 | + | ||
105 | + $scope.$watch('vm.ctx', function() { | ||
106 | + if (vm.ctx) { | ||
107 | + vm.settings = vm.ctx.settings; | ||
108 | + vm.widgetConfig = vm.ctx.widgetConfig; | ||
109 | + vm.subscription = vm.ctx.defaultSubscription; | ||
110 | + vm.alarmSource = vm.subscription.alarmSource; | ||
111 | + initializeConfig(); | ||
112 | + updateAlarmSource(); | ||
113 | + } | ||
114 | + }); | ||
115 | + | ||
116 | + $scope.$watch("vm.query.search", function(newVal, prevVal) { | ||
117 | + if (!angular.equals(newVal, prevVal) && vm.query.search != null) { | ||
118 | + updateAlarms(); | ||
119 | + } | ||
120 | + }); | ||
121 | + | ||
122 | + $scope.$on('alarms-table-data-updated', function(event, tableId) { | ||
123 | + if (vm.tableId == tableId) { | ||
124 | + if (vm.subscription) { | ||
125 | + vm.allAlarms = vm.subscription.alarms; | ||
126 | + updateAlarms(true); | ||
127 | + $scope.$digest(); | ||
128 | + } | ||
129 | + } | ||
130 | + }); | ||
131 | + | ||
132 | + $scope.$watch(function() { return $mdMedia('gt-xs'); }, function(isGtXs) { | ||
133 | + vm.isGtXs = isGtXs; | ||
134 | + }); | ||
135 | + | ||
136 | + $scope.$watch(function() { return $mdMedia('gt-md'); }, function(isGtMd) { | ||
137 | + vm.isGtMd = isGtMd; | ||
138 | + if (vm.isGtMd) { | ||
139 | + vm.limitOptions = [vm.defaultPageSize, vm.defaultPageSize*2, vm.defaultPageSize*3]; | ||
140 | + } else { | ||
141 | + vm.limitOptions = null; | ||
142 | + } | ||
143 | + }); | ||
144 | + | ||
145 | + $scope.$watch('vm.selectedAlarms.length', function (newLength) { | ||
146 | + var selectionMode = newLength ? true : false; | ||
147 | + if (vm.ctx) { | ||
148 | + if (selectionMode) { | ||
149 | + vm.ctx.hideTitlePanel = true; | ||
150 | + } else if (vm.query.search == null) { | ||
151 | + vm.ctx.hideTitlePanel = false; | ||
152 | + } | ||
153 | + } | ||
154 | + }); | ||
155 | + | ||
156 | + function initializeConfig() { | ||
157 | + | ||
158 | + vm.ctx.widgetActions = [ vm.searchAction ]; | ||
159 | + | ||
160 | + if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) { | ||
161 | + var translationId = types.translate.customTranslationsPrefix + vm.settings.alarmsTitle; | ||
162 | + var translation = $translate.instant(translationId); | ||
163 | + if (translation != translationId) { | ||
164 | + vm.alarmsTitle = translation + ''; | ||
165 | + } else { | ||
166 | + vm.alarmsTitle = vm.settings.alarmsTitle; | ||
167 | + } | ||
168 | + } else { | ||
169 | + vm.alarmsTitle = $translate.instant('alarm.alarms'); | ||
170 | + } | ||
171 | + | ||
172 | + vm.ctx.widgetTitle = vm.alarmsTitle; | ||
173 | + | ||
174 | + vm.enableSelection = angular.isDefined(vm.settings.enableSelection) ? vm.settings.enableSelection : true; | ||
175 | + vm.searchAction.show = angular.isDefined(vm.settings.enableSearch) ? vm.settings.enableSearch : true; | ||
176 | + vm.displayDetails = angular.isDefined(vm.settings.displayDetails) ? vm.settings.displayDetails : true; | ||
177 | + vm.allowAcknowledgment = angular.isDefined(vm.settings.allowAcknowledgment) ? vm.settings.allowAcknowledgment : true; | ||
178 | + vm.allowClear = angular.isDefined(vm.settings.allowClear) ? vm.settings.allowClear : true; | ||
179 | + if (!vm.allowAcknowledgment && !vm.allowClear) { | ||
180 | + vm.enableSelection = false; | ||
181 | + } | ||
182 | + | ||
183 | + vm.displayPagination = angular.isDefined(vm.settings.displayPagination) ? vm.settings.displayPagination : true; | ||
184 | + | ||
185 | + var pageSize = vm.settings.defaultPageSize; | ||
186 | + if (angular.isDefined(pageSize) && Number.isInteger(pageSize) && pageSize > 0) { | ||
187 | + vm.defaultPageSize = pageSize; | ||
188 | + } | ||
189 | + | ||
190 | + if (vm.settings.defaultSortOrder && vm.settings.defaultSortOrder.length) { | ||
191 | + vm.defaultSortOrder = vm.settings.defaultSortOrder; | ||
192 | + } | ||
193 | + | ||
194 | + vm.query.order = vm.defaultSortOrder; | ||
195 | + vm.query.limit = vm.defaultPageSize; | ||
196 | + if (vm.isGtMd) { | ||
197 | + vm.limitOptions = [vm.defaultPageSize, vm.defaultPageSize*2, vm.defaultPageSize*3]; | ||
198 | + } else { | ||
199 | + vm.limitOptions = null; | ||
200 | + } | ||
201 | + | ||
202 | + var origColor = vm.widgetConfig.color || 'rgba(0, 0, 0, 0.87)'; | ||
203 | + var defaultColor = tinycolor(origColor); | ||
204 | + var mdDark = defaultColor.setAlpha(0.87).toRgbString(); | ||
205 | + var mdDarkSecondary = defaultColor.setAlpha(0.54).toRgbString(); | ||
206 | + var mdDarkDisabled = defaultColor.setAlpha(0.26).toRgbString(); | ||
207 | + //var mdDarkIcon = mdDarkSecondary; | ||
208 | + var mdDarkDivider = defaultColor.setAlpha(0.12).toRgbString(); | ||
209 | + | ||
210 | + var cssString = 'table.md-table th.md-column {\n'+ | ||
211 | + 'color: ' + mdDarkSecondary + ';\n'+ | ||
212 | + '}\n'+ | ||
213 | + 'table.md-table th.md-column.md-checkbox-column md-checkbox:not(.md-checked) .md-icon {\n'+ | ||
214 | + 'border-color: ' + mdDarkSecondary + ';\n'+ | ||
215 | + '}\n'+ | ||
216 | + 'table.md-table th.md-column md-icon.md-sort-icon {\n'+ | ||
217 | + 'color: ' + mdDarkDisabled + ';\n'+ | ||
218 | + '}\n'+ | ||
219 | + 'table.md-table th.md-column.md-active, table.md-table th.md-column.md-active md-icon {\n'+ | ||
220 | + 'color: ' + mdDark + ';\n'+ | ||
221 | + '}\n'+ | ||
222 | + 'table.md-table td.md-cell {\n'+ | ||
223 | + 'color: ' + mdDark + ';\n'+ | ||
224 | + 'border-top: 1px '+mdDarkDivider+' solid;\n'+ | ||
225 | + '}\n'+ | ||
226 | + 'table.md-table td.md-cell.md-checkbox-cell md-checkbox:not(.md-checked) .md-icon {\n'+ | ||
227 | + 'border-color: ' + mdDarkSecondary + ';\n'+ | ||
228 | + '}\n'+ | ||
229 | + 'table.md-table td.md-cell.md-placeholder {\n'+ | ||
230 | + 'color: ' + mdDarkDisabled + ';\n'+ | ||
231 | + '}\n'+ | ||
232 | + 'table.md-table td.md-cell md-select > .md-select-value > span.md-select-icon {\n'+ | ||
233 | + 'color: ' + mdDarkSecondary + ';\n'+ | ||
234 | + '}\n'+ | ||
235 | + '.md-table-pagination {\n'+ | ||
236 | + 'color: ' + mdDarkSecondary + ';\n'+ | ||
237 | + 'border-top: 1px '+mdDarkDivider+' solid;\n'+ | ||
238 | + '}\n'+ | ||
239 | + '.md-table-pagination .buttons md-icon {\n'+ | ||
240 | + 'color: ' + mdDarkSecondary + ';\n'+ | ||
241 | + '}\n'+ | ||
242 | + '.md-table-pagination md-select:not([disabled]):focus .md-select-value {\n'+ | ||
243 | + 'color: ' + mdDarkSecondary + ';\n'+ | ||
244 | + '}'; | ||
245 | + | ||
246 | + var cssParser = new cssjs(); | ||
247 | + cssParser.testMode = false; | ||
248 | + var namespace = 'ts-table-' + hashCode(cssString); | ||
249 | + cssParser.cssPreviewNamespace = namespace; | ||
250 | + cssParser.createStyleElement(namespace, cssString); | ||
251 | + $element.addClass(namespace); | ||
252 | + | ||
253 | + function hashCode(str) { | ||
254 | + var hash = 0; | ||
255 | + var i, char; | ||
256 | + if (str.length === 0) return hash; | ||
257 | + for (i = 0; i < str.length; i++) { | ||
258 | + char = str.charCodeAt(i); | ||
259 | + hash = ((hash << 5) - hash) + char; | ||
260 | + hash = hash & hash; | ||
261 | + } | ||
262 | + return hash; | ||
263 | + } | ||
264 | + } | ||
265 | + | ||
266 | + function enterFilterMode () { | ||
267 | + vm.query.search = ''; | ||
268 | + vm.ctx.hideTitlePanel = true; | ||
269 | + } | ||
270 | + | ||
271 | + function exitFilterMode () { | ||
272 | + vm.query.search = null; | ||
273 | + updateAlarms(); | ||
274 | + vm.ctx.hideTitlePanel = false; | ||
275 | + } | ||
276 | + | ||
277 | + function onReorder () { | ||
278 | + updateAlarms(); | ||
279 | + } | ||
280 | + | ||
281 | + function onPaginate () { | ||
282 | + updateAlarms(); | ||
283 | + } | ||
284 | + | ||
285 | + function onRowClick($event, alarm) { | ||
286 | + if (vm.currentAlarm != alarm) { | ||
287 | + vm.currentAlarm = alarm; | ||
288 | + } | ||
289 | + } | ||
290 | + | ||
291 | + function isCurrent(alarm) { | ||
292 | + return (vm.currentAlarm && alarm && vm.currentAlarm.id && alarm.id) && | ||
293 | + (vm.currentAlarm.id.id === alarm.id.id); | ||
294 | + } | ||
295 | + | ||
296 | + function openAlarmDetails($event, alarm) { | ||
297 | + if (alarm && alarm.id) { | ||
298 | + var onShowingCallback = { | ||
299 | + onShowing: function(){} | ||
300 | + } | ||
301 | + $mdDialog.show({ | ||
302 | + controller: 'AlarmDetailsDialogController', | ||
303 | + controllerAs: 'vm', | ||
304 | + templateUrl: alarmDetailsDialogTemplate, | ||
305 | + locals: { | ||
306 | + alarmId: alarm.id.id, | ||
307 | + allowAcknowledgment: vm.allowAcknowledgment, | ||
308 | + allowClear: vm.allowClear, | ||
309 | + showingCallback: onShowingCallback | ||
310 | + }, | ||
311 | + parent: angular.element($document[0].body), | ||
312 | + targetEvent: $event, | ||
313 | + fullscreen: true, | ||
314 | + skipHide: true, | ||
315 | + onShowing: function(scope, element) { | ||
316 | + onShowingCallback.onShowing(scope, element); | ||
317 | + } | ||
318 | + }).then(function (alarm) { | ||
319 | + if (alarm) { | ||
320 | + vm.subscription.update(); | ||
321 | + } | ||
322 | + }); | ||
323 | + | ||
324 | + } | ||
325 | + } | ||
326 | + | ||
327 | + function ackAlarms($event) { | ||
328 | + if ($event) { | ||
329 | + $event.stopPropagation(); | ||
330 | + } | ||
331 | + if (vm.selectedAlarms && vm.selectedAlarms.length > 0) { | ||
332 | + var title = $translate.instant('alarm.aknowledge-alarms-title', {count: vm.selectedAlarms.length}, 'messageformat'); | ||
333 | + var content = $translate.instant('alarm.aknowledge-alarms-text', {count: vm.selectedAlarms.length}, 'messageformat'); | ||
334 | + var confirm = $mdDialog.confirm() | ||
335 | + .targetEvent($event) | ||
336 | + .title(title) | ||
337 | + .htmlContent(content) | ||
338 | + .ariaLabel(title) | ||
339 | + .cancel($translate.instant('action.no')) | ||
340 | + .ok($translate.instant('action.yes')); | ||
341 | + $mdDialog.show(confirm).then(function () { | ||
342 | + var tasks = []; | ||
343 | + for (var i=0;i<vm.selectedAlarms.length;i++) { | ||
344 | + var alarm = vm.selectedAlarms[i]; | ||
345 | + if (alarm.id) { | ||
346 | + tasks.push(alarmService.ackAlarm(alarm.id.id)); | ||
347 | + } | ||
348 | + } | ||
349 | + if (tasks.length) { | ||
350 | + $q.all(tasks).then(function () { | ||
351 | + vm.selectedAlarms = []; | ||
352 | + vm.subscription.update(); | ||
353 | + }); | ||
354 | + } | ||
355 | + | ||
356 | + }); | ||
357 | + } | ||
358 | + } | ||
359 | + | ||
360 | + function clearAlarms($event) { | ||
361 | + if ($event) { | ||
362 | + $event.stopPropagation(); | ||
363 | + } | ||
364 | + if (vm.selectedAlarms && vm.selectedAlarms.length > 0) { | ||
365 | + var title = $translate.instant('alarm.clear-alarms-title', {count: vm.selectedAlarms.length}, 'messageformat'); | ||
366 | + var content = $translate.instant('alarm.clear-alarms-text', {count: vm.selectedAlarms.length}, 'messageformat'); | ||
367 | + var confirm = $mdDialog.confirm() | ||
368 | + .targetEvent($event) | ||
369 | + .title(title) | ||
370 | + .htmlContent(content) | ||
371 | + .ariaLabel(title) | ||
372 | + .cancel($translate.instant('action.no')) | ||
373 | + .ok($translate.instant('action.yes')); | ||
374 | + $mdDialog.show(confirm).then(function () { | ||
375 | + var tasks = []; | ||
376 | + for (var i=0;i<vm.selectedAlarms.length;i++) { | ||
377 | + var alarm = vm.selectedAlarms[i]; | ||
378 | + if (alarm.id) { | ||
379 | + tasks.push(alarmService.clearAlarm(alarm.id.id)); | ||
380 | + } | ||
381 | + } | ||
382 | + if (tasks.length) { | ||
383 | + $q.all(tasks).then(function () { | ||
384 | + vm.selectedAlarms = []; | ||
385 | + vm.subscription.update(); | ||
386 | + }); | ||
387 | + } | ||
388 | + | ||
389 | + }); | ||
390 | + } | ||
391 | + } | ||
392 | + | ||
393 | + | ||
394 | + function updateAlarms(preserveSelections) { | ||
395 | + if (!preserveSelections) { | ||
396 | + vm.selectedAlarms = []; | ||
397 | + } | ||
398 | + var result = $filter('orderBy')(vm.allAlarms, vm.query.order); | ||
399 | + if (vm.query.search != null) { | ||
400 | + result = $filter('filter')(result, {$: vm.query.search}); | ||
401 | + } | ||
402 | + vm.alarmsCount = result.length; | ||
403 | + | ||
404 | + if (vm.displayPagination) { | ||
405 | + var startIndex = vm.query.limit * (vm.query.page - 1); | ||
406 | + vm.alarms = result.slice(startIndex, startIndex + vm.query.limit); | ||
407 | + } else { | ||
408 | + vm.alarms = result; | ||
409 | + } | ||
410 | + | ||
411 | + if (preserveSelections) { | ||
412 | + var newSelectedAlarms = []; | ||
413 | + if (vm.selectedAlarms && vm.selectedAlarms.length) { | ||
414 | + var i = vm.selectedAlarms.length; | ||
415 | + while (i--) { | ||
416 | + var selectedAlarm = vm.selectedAlarms[i]; | ||
417 | + if (selectedAlarm.id) { | ||
418 | + result = $filter('filter')(vm.alarms, {id: {id: selectedAlarm.id.id} }); | ||
419 | + if (result && result.length) { | ||
420 | + newSelectedAlarms.push(result[0]); | ||
421 | + } | ||
422 | + } | ||
423 | + } | ||
424 | + } | ||
425 | + vm.selectedAlarms = newSelectedAlarms; | ||
426 | + } | ||
427 | + } | ||
428 | + | ||
429 | + function cellStyle(alarm, key) { | ||
430 | + var style = {}; | ||
431 | + if (alarm && key) { | ||
432 | + var styleInfo = vm.stylesInfo[key.label]; | ||
433 | + var value = getAlarmValue(alarm, key); | ||
434 | + if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { | ||
435 | + try { | ||
436 | + style = styleInfo.cellStyleFunction(value); | ||
437 | + } catch (e) { | ||
438 | + style = {}; | ||
439 | + } | ||
440 | + } else { | ||
441 | + style = defaultStyle(key, value); | ||
442 | + } | ||
443 | + } | ||
444 | + if (!style.width) { | ||
445 | + var columnWidth = vm.columnWidth[key.label]; | ||
446 | + style.width = columnWidth; | ||
447 | + } | ||
448 | + return style; | ||
449 | + } | ||
450 | + | ||
451 | + function cellContent(alarm, key) { | ||
452 | + var strContent = ''; | ||
453 | + if (alarm && key) { | ||
454 | + var contentInfo = vm.contentsInfo[key.label]; | ||
455 | + var value = getAlarmValue(alarm, key); | ||
456 | + if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) { | ||
457 | + if (angular.isDefined(value)) { | ||
458 | + strContent = '' + value; | ||
459 | + } | ||
460 | + var content = strContent; | ||
461 | + try { | ||
462 | + content = contentInfo.cellContentFunction(value, alarm, $filter); | ||
463 | + } catch (e) { | ||
464 | + content = strContent; | ||
465 | + } | ||
466 | + } else { | ||
467 | + content = defaultContent(key, value); | ||
468 | + } | ||
469 | + return content; | ||
470 | + } else { | ||
471 | + return strContent; | ||
472 | + } | ||
473 | + } | ||
474 | + | ||
475 | + function defaultContent(key, value) { | ||
476 | + if (angular.isDefined(value)) { | ||
477 | + var alarmField = types.alarmFields[key.name]; | ||
478 | + if (alarmField) { | ||
479 | + if (alarmField.time) { | ||
480 | + return $filter('date')(value, 'yyyy-MM-dd HH:mm:ss'); | ||
481 | + } else if (alarmField.value == types.alarmFields.severity.value) { | ||
482 | + return $translate.instant(types.alarmSeverity[value].name); | ||
483 | + } else if (alarmField.value == types.alarmFields.status.value) { | ||
484 | + return $translate.instant('alarm.display-status.'+value); | ||
485 | + } else if (alarmField.value == types.alarmFields.originatorType.value) { | ||
486 | + return $translate.instant(types.entityTypeTranslations[value].type); | ||
487 | + } | ||
488 | + else { | ||
489 | + return value; | ||
490 | + } | ||
491 | + } else { | ||
492 | + return value; | ||
493 | + } | ||
494 | + } else { | ||
495 | + return ''; | ||
496 | + } | ||
497 | + } | ||
498 | + function defaultStyle(key, value) { | ||
499 | + if (angular.isDefined(value)) { | ||
500 | + var alarmField = types.alarmFields[key.name]; | ||
501 | + if (alarmField) { | ||
502 | + if (alarmField.value == types.alarmFields.severity.value) { | ||
503 | + return { | ||
504 | + fontWeight: 'bold', | ||
505 | + color: types.alarmSeverity[value].color | ||
506 | + }; | ||
507 | + } else { | ||
508 | + return {}; | ||
509 | + } | ||
510 | + } else { | ||
511 | + return {}; | ||
512 | + } | ||
513 | + } else { | ||
514 | + return {}; | ||
515 | + } | ||
516 | + } | ||
517 | + | ||
518 | + const getDescendantProp = (obj, path) => ( | ||
519 | + path.split('.').reduce((acc, part) => acc && acc[part], obj) | ||
520 | + ); | ||
521 | + | ||
522 | + function getAlarmValue(alarm, key) { | ||
523 | + var alarmField = types.alarmFields[key.name]; | ||
524 | + if (alarmField) { | ||
525 | + return getDescendantProp(alarm, alarmField.value); | ||
526 | + } else { | ||
527 | + return getDescendantProp(alarm, key.name); | ||
528 | + } | ||
529 | + } | ||
530 | + | ||
531 | + function updateAlarmSource() { | ||
532 | + | ||
533 | + vm.ctx.widgetTitle = utils.createLabelFromDatasource(vm.alarmSource, vm.alarmsTitle); | ||
534 | + | ||
535 | + vm.stylesInfo = {}; | ||
536 | + vm.contentsInfo = {}; | ||
537 | + vm.columnWidth = {}; | ||
538 | + | ||
539 | + for (var d = 0; d < vm.alarmSource.dataKeys.length; d++ ) { | ||
540 | + var dataKey = vm.alarmSource.dataKeys[d]; | ||
541 | + | ||
542 | + var translationId = types.translate.customTranslationsPrefix + dataKey.label; | ||
543 | + var translation = $translate.instant(translationId); | ||
544 | + if (translation != translationId) { | ||
545 | + dataKey.title = translation + ''; | ||
546 | + } else { | ||
547 | + dataKey.title = dataKey.label; | ||
548 | + } | ||
549 | + | ||
550 | + var keySettings = dataKey.settings; | ||
551 | + | ||
552 | + var cellStyleFunction = null; | ||
553 | + var useCellStyleFunction = false; | ||
554 | + | ||
555 | + if (keySettings.useCellStyleFunction === true) { | ||
556 | + if (angular.isDefined(keySettings.cellStyleFunction) && keySettings.cellStyleFunction.length > 0) { | ||
557 | + try { | ||
558 | + cellStyleFunction = new Function('value', keySettings.cellStyleFunction); | ||
559 | + useCellStyleFunction = true; | ||
560 | + } catch (e) { | ||
561 | + cellStyleFunction = null; | ||
562 | + useCellStyleFunction = false; | ||
563 | + } | ||
564 | + } | ||
565 | + } | ||
566 | + | ||
567 | + vm.stylesInfo[dataKey.label] = { | ||
568 | + useCellStyleFunction: useCellStyleFunction, | ||
569 | + cellStyleFunction: cellStyleFunction | ||
570 | + }; | ||
571 | + | ||
572 | + var cellContentFunction = null; | ||
573 | + var useCellContentFunction = false; | ||
574 | + | ||
575 | + if (keySettings.useCellContentFunction === true) { | ||
576 | + if (angular.isDefined(keySettings.cellContentFunction) && keySettings.cellContentFunction.length > 0) { | ||
577 | + try { | ||
578 | + cellContentFunction = new Function('value, alarm, filter', keySettings.cellContentFunction); | ||
579 | + useCellContentFunction = true; | ||
580 | + } catch (e) { | ||
581 | + cellContentFunction = null; | ||
582 | + useCellContentFunction = false; | ||
583 | + } | ||
584 | + } | ||
585 | + } | ||
586 | + | ||
587 | + vm.contentsInfo[dataKey.label] = { | ||
588 | + useCellContentFunction: useCellContentFunction, | ||
589 | + cellContentFunction: cellContentFunction | ||
590 | + }; | ||
591 | + | ||
592 | + var columnWidth = angular.isDefined(keySettings.columnWidth) ? keySettings.columnWidth : '0px'; | ||
593 | + vm.columnWidth[dataKey.label] = columnWidth; | ||
594 | + } | ||
595 | + } | ||
596 | + | ||
597 | +} |
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-has-timewindow { | ||
18 | + .tb-alarms-table { | ||
19 | + md-toolbar { | ||
20 | + min-height: 60px; | ||
21 | + max-height: 60px; | ||
22 | + &.md-table-toolbar { | ||
23 | + .md-toolbar-tools { | ||
24 | + max-height: 60px; | ||
25 | + } | ||
26 | + } | ||
27 | + } | ||
28 | + } | ||
29 | +} | ||
30 | + | ||
31 | +.tb-alarms-table { | ||
32 | + | ||
33 | + md-toolbar { | ||
34 | + min-height: 39px; | ||
35 | + max-height: 39px; | ||
36 | + &.md-table-toolbar { | ||
37 | + .md-toolbar-tools { | ||
38 | + max-height: 39px; | ||
39 | + } | ||
40 | + } | ||
41 | + } | ||
42 | + | ||
43 | + &.tb-data-table { | ||
44 | + table.md-table, table.md-table.md-row-select { | ||
45 | + tbody { | ||
46 | + tr { | ||
47 | + td { | ||
48 | + &.tb-action-cell { | ||
49 | + min-width: 36px; | ||
50 | + max-width: 36px; | ||
51 | + width: 36px; | ||
52 | + } | ||
53 | + } | ||
54 | + } | ||
55 | + } | ||
56 | + } | ||
57 | + } | ||
58 | +} |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2017 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<div class="tb-absolute-fill tb-alarms-table tb-data-table" layout="column"> | ||
19 | + <div ng-show="vm.showData" flex class="tb-absolute-fill" layout="column"> | ||
20 | + <md-toolbar class="md-table-toolbar md-default" ng-show="!vm.selectedAlarms.length && | ||
21 | + vm.query.search != null"> | ||
22 | + <div class="md-toolbar-tools"> | ||
23 | + <md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}"> | ||
24 | + <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon> | ||
25 | + <md-tooltip md-direction="top"> | ||
26 | + {{'alarm.search' | translate}} | ||
27 | + </md-tooltip> | ||
28 | + </md-button> | ||
29 | + <md-input-container flex> | ||
30 | + <label> </label> | ||
31 | + <input ng-model="vm.query.search" placeholder="{{'alarm.search' | translate}}"/> | ||
32 | + </md-input-container> | ||
33 | + <md-button class="md-icon-button" aria-label="Close" ng-click="vm.exitFilterMode()"> | ||
34 | + <md-icon aria-label="Close" class="material-icons">close</md-icon> | ||
35 | + <md-tooltip md-direction="top"> | ||
36 | + {{ 'action.close' | translate }} | ||
37 | + </md-tooltip> | ||
38 | + </md-button> | ||
39 | + </div> | ||
40 | + </md-toolbar> | ||
41 | + <md-toolbar class="md-table-toolbar alternate" ng-show="vm.selectedAlarms.length"> | ||
42 | + <div class="md-toolbar-tools"> | ||
43 | + <span translate="alarm.selected-alarms" | ||
44 | + translate-values="{count: vm.selectedAlarms.length}" | ||
45 | + translate-interpolation="messageformat"></span> | ||
46 | + <span flex></span> | ||
47 | + <md-button ng-if="vm.allowAcknowledgment" class="md-icon-button" ng-click="vm.ackAlarms($event)"> | ||
48 | + <md-icon>done</md-icon> | ||
49 | + <md-tooltip md-direction="top"> | ||
50 | + {{ 'alarm.acknowledge' | translate }} | ||
51 | + </md-tooltip> | ||
52 | + </md-button> | ||
53 | + <md-button ng-if="vm.allowClear" class="md-icon-button" ng-click="vm.clearAlarms($event)"> | ||
54 | + <md-icon>clear</md-icon> | ||
55 | + <md-tooltip md-direction="top"> | ||
56 | + {{ 'alarm.clear' | translate }} | ||
57 | + </md-tooltip> | ||
58 | + </md-button> | ||
59 | + </div> | ||
60 | + </md-toolbar> | ||
61 | + <md-table-container flex> | ||
62 | + <table md-table md-row-select="vm.enableSelection" multiple="" ng-model="vm.selectedAlarms"> | ||
63 | + <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder"> | ||
64 | + <tr md-row> | ||
65 | + <th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.alarmSource.dataKeys"><span>{{ key.title }}</span></th> | ||
66 | + <th md-column ng-if="vm.displayDetails"><span> </span></th> | ||
67 | + </tr> | ||
68 | + </thead> | ||
69 | + <tbody md-body> | ||
70 | + <tr ng-show="vm.alarms.length" md-row md-select="alarm" | ||
71 | + md-select-id="id.id" md-auto-select="false" ng-repeat="alarm in vm.alarms" | ||
72 | + ng-click="vm.onRowClick($event, alarm)" ng-class="{'tb-current': vm.isCurrent(alarm)}"> | ||
73 | + <td md-cell flex ng-repeat="key in vm.alarmSource.dataKeys" | ||
74 | + ng-style="vm.cellStyle(alarm, key)" | ||
75 | + ng-bind-html="vm.cellContent(alarm, key)"> | ||
76 | + </td> | ||
77 | + <td md-cell ng-if="vm.displayDetails" class="tb-action-cell"> | ||
78 | + <md-button class="md-icon-button" aria-label="{{ 'alarm.details' | translate }}" | ||
79 | + ng-click="vm.openAlarmDetails($event, alarm)"> | ||
80 | + <md-icon aria-label="{{ 'alarm.details' | translate }}" class="material-icons">more_horiz</md-icon> | ||
81 | + <md-tooltip md-direction="top"> | ||
82 | + {{ 'alarm.details' | translate }} | ||
83 | + </md-tooltip> | ||
84 | + </md-button> | ||
85 | + </td> | ||
86 | + </tr> | ||
87 | + </tbody> | ||
88 | + </table> | ||
89 | + <md-divider></md-divider> | ||
90 | + <span ng-show="!vm.alarms.length" | ||
91 | + layout-align="center center" | ||
92 | + class="no-data-found" translate>alarm.no-alarms-prompt</span> | ||
93 | + </md-table-container> | ||
94 | + <md-table-pagination ng-if="vm.displayPagination" md-boundary-links md-limit="vm.query.limit" md-limit-options="vm.limitOptions" | ||
95 | + md-page="vm.query.page" md-total="{{vm.alarmsCount}}" | ||
96 | + md-on-paginate="vm.onPaginate" md-page-select="vm.isGtMd"> | ||
97 | + </md-table-pagination> | ||
98 | + </div> | ||
99 | + <span ng-show="!vm.showData" | ||
100 | + layout-align="center center" | ||
101 | + style="text-transform: uppercase; display: flex;" | ||
102 | + class="tb-absolute-fill" translate>alarm.no-data</span> | ||
103 | +</div> |
@@ -54,6 +54,13 @@ | @@ -54,6 +54,13 @@ | ||
54 | <span translate>{{vm.types.widgetType.rpc.name}}</span> | 54 | <span translate>{{vm.types.widgetType.rpc.name}}</span> |
55 | </md-button> | 55 | </md-button> |
56 | <md-button class="tb-card-button md-raised md-primary" layout="column" | 56 | <md-button class="tb-card-button md-raised md-primary" layout="column" |
57 | + ng-click="vm.typeSelected(vm.types.widgetType.alarm.value)"> | ||
58 | + <md-icon class="material-icons tb-md-96" | ||
59 | + aria-label="{{ vm.types.widgetType.alarm.name | translate }}">error | ||
60 | + </md-icon> | ||
61 | + <span translate>{{vm.types.widgetType.alarm.name}}</span> | ||
62 | + </md-button> | ||
63 | + <md-button class="tb-card-button md-raised md-primary" layout="column" | ||
57 | ng-click="vm.typeSelected(vm.types.widgetType.static.value)"> | 64 | ng-click="vm.typeSelected(vm.types.widgetType.static.value)"> |
58 | <md-icon class="material-icons tb-md-96" | 65 | <md-icon class="material-icons tb-md-96" |
59 | aria-label="{{ vm.types.widgetType.static.name | translate }}">font_download | 66 | aria-label="{{ vm.types.widgetType.static.name | translate }}">font_download |
@@ -329,10 +329,14 @@ export default function WidgetEditorController(widgetService, userService, types | @@ -329,10 +329,14 @@ export default function WidgetEditorController(widgetService, userService, types | ||
329 | $scope.$watch('vm.widget.type', function (newVal, oldVal) { | 329 | $scope.$watch('vm.widget.type', function (newVal, oldVal) { |
330 | if (!angular.equals(newVal, oldVal)) { | 330 | if (!angular.equals(newVal, oldVal)) { |
331 | var config = angular.fromJson(vm.widget.defaultConfig); | 331 | var config = angular.fromJson(vm.widget.defaultConfig); |
332 | - if (vm.widget.type !== types.widgetType.rpc.value) { | 332 | + if (vm.widget.type !== types.widgetType.rpc.value |
333 | + && vm.widget.type !== types.widgetType.alarm.value) { | ||
333 | if (config.targetDeviceAliases) { | 334 | if (config.targetDeviceAliases) { |
334 | delete config.targetDeviceAliases; | 335 | delete config.targetDeviceAliases; |
335 | } | 336 | } |
337 | + if (config.alarmSource) { | ||
338 | + delete config.alarmSource; | ||
339 | + } | ||
336 | if (!config.datasources) { | 340 | if (!config.datasources) { |
337 | config.datasources = []; | 341 | config.datasources = []; |
338 | } | 342 | } |
@@ -346,22 +350,38 @@ export default function WidgetEditorController(widgetService, userService, types | @@ -346,22 +350,38 @@ export default function WidgetEditorController(widgetService, userService, types | ||
346 | for (var i = 0; i < config.datasources.length; i++) { | 350 | for (var i = 0; i < config.datasources.length; i++) { |
347 | var datasource = config.datasources[i]; | 351 | var datasource = config.datasources[i]; |
348 | datasource.type = vm.widget.type; | 352 | datasource.type = vm.widget.type; |
349 | - if (vm.widget.type !== types.widgetType.timeseries.value && datasource.intervalSec) { | ||
350 | - delete datasource.intervalSec; | ||
351 | - } else if (vm.widget.type === types.widgetType.timeseries.value && !datasource.intervalSec) { | ||
352 | - datasource.intervalSec = 60; | ||
353 | - } | ||
354 | } | 353 | } |
355 | - } else { | 354 | + } else if (vm.widget.type == types.widgetType.rpc.value) { |
356 | if (config.datasources) { | 355 | if (config.datasources) { |
357 | delete config.datasources; | 356 | delete config.datasources; |
358 | } | 357 | } |
358 | + if (config.alarmSource) { | ||
359 | + delete config.alarmSource; | ||
360 | + } | ||
359 | if (config.timewindow) { | 361 | if (config.timewindow) { |
360 | delete config.timewindow; | 362 | delete config.timewindow; |
361 | } | 363 | } |
362 | if (!config.targetDeviceAliases) { | 364 | if (!config.targetDeviceAliases) { |
363 | config.targetDeviceAliases = []; | 365 | config.targetDeviceAliases = []; |
364 | } | 366 | } |
367 | + } else { // alarm | ||
368 | + if (config.datasources) { | ||
369 | + delete config.datasources; | ||
370 | + } | ||
371 | + if (config.targetDeviceAliases) { | ||
372 | + delete config.targetDeviceAliases; | ||
373 | + } | ||
374 | + if (!config.alarmSource) { | ||
375 | + config.alarmSource = {}; | ||
376 | + config.alarmSource.type = vm.widget.type | ||
377 | + } | ||
378 | + if (!config.timewindow) { | ||
379 | + config.timewindow = { | ||
380 | + realtime: { | ||
381 | + timewindowMs: 24 * 60 * 60 * 1000 | ||
382 | + } | ||
383 | + }; | ||
384 | + } | ||
365 | } | 385 | } |
366 | vm.widget.defaultConfig = angular.toJson(config); | 386 | vm.widget.defaultConfig = angular.toJson(config); |
367 | } | 387 | } |
@@ -281,7 +281,61 @@ pre.tb-highlight { | @@ -281,7 +281,61 @@ pre.tb-highlight { | ||
281 | display: flex; | 281 | display: flex; |
282 | } | 282 | } |
283 | table.md-table { | 283 | table.md-table { |
284 | + &.md-row-select td.md-cell, | ||
285 | + &.md-row-select th.md-column { | ||
286 | + &:first-child { | ||
287 | + width: 20px; | ||
288 | + padding: 0 0 0 12px; | ||
289 | + } | ||
290 | + | ||
291 | + &:nth-child(2) { | ||
292 | + padding: 0 12px; | ||
293 | + } | ||
294 | + | ||
295 | + &:nth-child(n+3):nth-last-child(n+2) { | ||
296 | + padding: 0 28px 0 0; | ||
297 | + } | ||
298 | + } | ||
299 | + | ||
300 | + &:not(.md-row-select) td.md-cell, | ||
301 | + &:not(.md-row-select) th.md-column { | ||
302 | + &:first-child { | ||
303 | + padding: 0 12px; | ||
304 | + } | ||
305 | + | ||
306 | + &:nth-child(n+2):nth-last-child(n+2) { | ||
307 | + padding: 0 28px 0 0; | ||
308 | + } | ||
309 | + } | ||
310 | + | ||
311 | + td.md-cell, | ||
312 | + th.md-column { | ||
313 | + | ||
314 | + &:last-child { | ||
315 | + padding: 0 12px 0 0; | ||
316 | + } | ||
317 | + | ||
318 | + } | ||
319 | + } | ||
320 | + | ||
321 | + table.md-table, table.md-table.md-row-select { | ||
284 | tbody { | 322 | tbody { |
323 | + &.md-body { | ||
324 | + tr { | ||
325 | + &.md-row:not([disabled]) { | ||
326 | + outline: none; | ||
327 | + &:hover { | ||
328 | + background-color: rgba(221, 221, 221, 0.3) !important; | ||
329 | + } | ||
330 | + &.md-selected { | ||
331 | + background-color: rgba(221, 221, 221, 0.5) !important; | ||
332 | + } | ||
333 | + &.tb-current, &.tb-current:hover{ | ||
334 | + background-color: rgba(221, 221, 221, 0.65) !important; | ||
335 | + } | ||
336 | + } | ||
337 | + } | ||
338 | + } | ||
285 | tr { | 339 | tr { |
286 | td { | 340 | td { |
287 | &.tb-action-cell { | 341 | &.tb-action-cell { |