diff --git a/ui/src/app/alarm/alarm-details-dialog.controller.js b/ui/src/app/alarm/alarm-details-dialog.controller.js index 0cc05ef295..6c82e2086c 100644 --- a/ui/src/app/alarm/alarm-details-dialog.controller.js +++ b/ui/src/app/alarm/alarm-details-dialog.controller.js @@ -23,11 +23,14 @@ import './alarm-details-dialog.scss'; const js_beautify = beautify.js; /*@ngInject*/ -export default function AlarmDetailsDialogController($mdDialog, $filter, $translate, types, alarmService, alarmId, showingCallback) { +export default function AlarmDetailsDialogController($mdDialog, $filter, $translate, types, + alarmService, alarmId, allowAcknowledgment, allowClear, showingCallback) { var vm = this; vm.alarmId = alarmId; + vm.allowAcknowledgment = allowAcknowledgment; + vm.allowClear = allowClear; vm.types = types; vm.alarm = null; diff --git a/ui/src/app/alarm/alarm-details-dialog.tpl.html b/ui/src/app/alarm/alarm-details-dialog.tpl.html index c958201ea9..48f991c52a 100644 --- a/ui/src/app/alarm/alarm-details-dialog.tpl.html +++ b/ui/src/app/alarm/alarm-details-dialog.tpl.html @@ -84,16 +84,16 @@ - {{ 'alarm.acknowledge' | translate }} - {{ 'alarm.clear' | diff --git a/ui/src/app/alarm/alarm-row.directive.js b/ui/src/app/alarm/alarm-row.directive.js index 9cb9bedd68..09c04f9594 100644 --- a/ui/src/app/alarm/alarm-row.directive.js +++ b/ui/src/app/alarm/alarm-row.directive.js @@ -40,7 +40,12 @@ export default function AlarmRowDirective($compile, $templateCache, types, $mdDi controller: 'AlarmDetailsDialogController', controllerAs: 'vm', templateUrl: alarmDetailsDialogTemplate, - locals: {alarmId: scope.alarm.id.id, showingCallback: onShowingCallback}, + locals: { + alarmId: scope.alarm.id.id, + allowAcknowledgment: true, + allowClear: true, + showingCallback: onShowingCallback + }, parent: angular.element($document[0].body), targetEvent: $event, fullscreen: true, diff --git a/ui/src/app/api/alarm.service.js b/ui/src/app/api/alarm.service.js index c2ee07a8ef..db4ceadde1 100644 --- a/ui/src/app/api/alarm.service.js +++ b/ui/src/app/api/alarm.service.js @@ -252,12 +252,12 @@ function AlarmService($http, $q, $interval, $filter, $timeout, utils, types) { $timeout(function() { alarmSourceListener.alarmsUpdated([simulatedAlarm], false); }); - } else { - var pollingInterval = 5000; //TODO: + } else if (alarmSource.entityType && alarmSource.entityId) { + var pollingInterval = alarmSourceListener.alarmsPollingInterval; alarmSourceListener.alarmsQuery = { entityType: alarmSource.entityType, entityId: alarmSource.entityId, - alarmSearchStatus: null, //TODO: + alarmSearchStatus: alarmSourceListener.alarmSearchStatus, alarmStatus: null } var originatorKeys = $filter('filter')(alarmSource.dataKeys, {name: 'originator'}); diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js index 16e4cc34ac..030b8521ee 100644 --- a/ui/src/app/api/subscription.js +++ b/ui/src/app/api/subscription.js @@ -70,6 +70,12 @@ export default class Subscription { this.callbacks.dataLoading = this.callbacks.dataLoading || function(){}; this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || function(){}; this.alarmSource = options.alarmSource; + + this.alarmSearchStatus = angular.isDefined(options.alarmSearchStatus) ? + options.alarmSearchStatus : this.ctx.types.alarmSearchStatus.any; + this.alarmsPollingInterval = angular.isDefined(options.alarmsPollingInterval) ? + options.alarmsPollingInterval : 5000; + this.alarmSourceListener = null; this.alarms = []; @@ -193,8 +199,7 @@ export default class Subscription { registration = this.ctx.$scope.$on('dashboardTimewindowChanged', function (event, newDashboardTimewindow) { if (!angular.equals(subscription.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) { subscription.timeWindowConfig = angular.copy(newDashboardTimewindow); - subscription.unsubscribe(); - subscription.subscribe(); + subscription.update(); } }); this.registrations.push(registration); @@ -281,8 +286,7 @@ export default class Subscription { registration = this.ctx.$scope.$on('dashboardTimewindowChanged', function (event, newDashboardTimewindow) { if (!angular.equals(subscription.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) { subscription.timeWindowConfig = angular.copy(newDashboardTimewindow); - subscription.unsubscribe(); - subscription.subscribe(); + subscription.update(); } }); this.registrations.push(registration); @@ -298,8 +302,7 @@ export default class Subscription { return subscription.timeWindowConfig; }, function (newTimewindow, prevTimewindow) { if (!angular.equals(newTimewindow, prevTimewindow)) { - subscription.unsubscribe(); - subscription.subscribe(); + subscription.update(); } }, true); this.registrations.push(this.timeWindowWatchRegistration); @@ -502,8 +505,7 @@ export default class Subscription { this.timeWindowConfig = angular.copy(this.originalTimewindow); this.originalTimewindow = null; this.callbacks.timeWindowUpdated(this, this.timeWindowConfig); - this.unsubscribe(); - this.subscribe(); + this.update(); this.startWatchingTimewindow(); } } @@ -519,8 +521,7 @@ export default class Subscription { } this.timeWindowConfig = this.ctx.timeService.toHistoryTimewindow(this.timeWindowConfig, startTimeMs, endTimeMs); this.callbacks.timeWindowUpdated(this, this.timeWindowConfig); - this.unsubscribe(); - this.subscribe(); + this.update(); this.startWatchingTimewindow(); } } @@ -618,6 +619,11 @@ export default class Subscription { this.callbacks.legendDataUpdated(this, apply !== false); } + update() { + this.unsubscribe(); + this.subscribe(); + } + subscribe() { if (this.type === this.ctx.types.widgetType.rpc.value) { return; @@ -688,6 +694,8 @@ export default class Subscription { this.alarmSourceListener = { subscriptionTimewindow: this.subscriptionTimewindow, alarmSource: this.alarmSource, + alarmSearchStatus: this.alarmSearchStatus, + alarmsPollingInterval: this.alarmsPollingInterval, alarmsUpdated: function(alarms, apply) { subscription.alarmsUpdated(alarms, apply); } diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 9610360642..6a4cbccc2f 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -394,7 +394,8 @@ export default angular.module('thingsboard.types', []) cards: "cards" }, translate: { - dashboardStatePrefix: "dashboardState.state." + dashboardStatePrefix: "dashboardState.state.", + keyLabelPrefix: "key.label." } } ).name; diff --git a/ui/src/app/components/datakey-config.directive.js b/ui/src/app/components/datakey-config.directive.js index 90810afeb2..d19617f440 100644 --- a/ui/src/app/components/datakey-config.directive.js +++ b/ui/src/app/components/datakey-config.directive.js @@ -63,6 +63,12 @@ function DatakeyConfig($compile, $templateCache, $q, types) { element.html(template); scope.types = types; + + scope.alarmFields = []; + for (var alarmField in types.alarmFields) { + scope.alarmFields.push(alarmField); + } + scope.selectedKey = null; scope.keySearchText = null; scope.usePostProcessing = false; @@ -112,21 +118,39 @@ function DatakeyConfig($compile, $templateCache, $q, types) { }, true); scope.keysSearch = function (searchText) { - if (scope.entityAlias) { - var deferred = $q.defer(); - scope.fetchEntityKeys({entityAliasId: scope.entityAlias.id, query: searchText, type: scope.model.type}) - .then(function (keys) { - keys.push(searchText); - deferred.resolve(keys); - }, function (e) { - deferred.reject(e); - }); - return deferred.promise; + if (scope.model.type === types.dataKeyType.alarm) { + var dataKeys = searchText ? scope.alarmFields.filter( + scope.createFilterForDataKey(searchText)) : scope.alarmFields; + dataKeys.push(searchText); + return dataKeys; } else { - return $q.when([]); + if (scope.entityAlias) { + var deferred = $q.defer(); + scope.fetchEntityKeys({ + entityAliasId: scope.entityAlias.id, + query: searchText, + type: scope.model.type + }) + .then(function (keys) { + keys.push(searchText); + deferred.resolve(keys); + }, function (e) { + deferred.reject(e); + }); + return deferred.promise; + } else { + return $q.when([]); + } } }; + scope.createFilterForDataKey = function (query) { + var lowercaseQuery = angular.lowercase(query); + return function filterFn(dataKey) { + return (angular.lowercase(dataKey).indexOf(lowercaseQuery) === 0); + }; + }; + $compile(element.contents())(scope); } diff --git a/ui/src/app/components/datakey-config.tpl.html b/ui/src/app/components/datakey-config.tpl.html index 755c4f49a8..bb7a4b9e9b 100644 --- a/ui/src/app/components/datakey-config.tpl.html +++ b/ui/src/app/components/datakey-config.tpl.html @@ -16,7 +16,9 @@ --> - + placeholder="{{ 'entity.key-name' | translate }}" + md-floating-label="{{ 'entity.key' | translate }}"> {{item}}
@@ -48,7 +50,7 @@ md-color-history="false">
-
+
diff --git a/ui/src/app/components/datasource-entity.scss b/ui/src/app/components/datasource-entity.scss index 7a87fc700c..b9e892bf14 100644 --- a/ui/src/app/components/datasource-entity.scss +++ b/ui/src/app/components/datasource-entity.scss @@ -15,7 +15,7 @@ */ @import '../../scss/constants'; -.tb-entity-alias-autocomplete, .tb-timeseries-datakey-autocomplete, .tb-attribute-datakey-autocomplete { +.tb-entity-alias-autocomplete, .tb-timeseries-datakey-autocomplete, .tb-attribute-datakey-autocomplete, .tb-alarm-datakey-autocomplete { .tb-not-found { display: block; line-height: 1.5; diff --git a/ui/src/app/components/datasource-entity.tpl.html b/ui/src/app/components/datasource-entity.tpl.html index d5cd782290..9876491086 100644 --- a/ui/src/app/components/datasource-entity.tpl.html +++ b/ui/src/app/components/datasource-entity.tpl.html @@ -133,7 +133,7 @@ ng-required="true" ng-model="alarmDataKeys" md-autocomplete-snap md-transform-chip="transformAlarmDataKeyChip($chip)" - md-require-match="true"> + md-require-match="false">
entity.no-key-matching + + entity.create-new-key +
diff --git a/ui/src/app/components/datasource-func.scss b/ui/src/app/components/datasource-func.scss index 8739ed8fee..fdda0e47da 100644 --- a/ui/src/app/components/datasource-func.scss +++ b/ui/src/app/components/datasource-func.scss @@ -26,7 +26,7 @@ } } - .tb-func-datakey-autocomplete { + .tb-func-datakey-autocomplete, .tb-alarm-datakey-autocomplete { .tb-not-found { display: block; line-height: 1.5; diff --git a/ui/src/app/components/datasource-func.tpl.html b/ui/src/app/components/datasource-func.tpl.html index 1ce1108aa5..7fa4aac008 100644 --- a/ui/src/app/components/datasource-func.tpl.html +++ b/ui/src/app/components/datasource-func.tpl.html @@ -48,9 +48,9 @@ device.no-keys-found
- device.no-key-matching + entity.no-key-matching - device.create-new-key + entity.create-new-key
@@ -81,7 +81,7 @@ ng-required="true" ng-model="alarmDataKeys" md-autocomplete-snap md-transform-chip="transformAlarmDataKeyChip($chip)" - md-require-match="true"> + md-require-match="false">
entity.no-key-matching + + entity.create-new-key +
diff --git a/ui/src/app/components/widget-config.directive.js b/ui/src/app/components/widget-config.directive.js index e0b4800582..3849cd3c11 100644 --- a/ui/src/app/components/widget-config.directive.js +++ b/ui/src/app/components/widget-config.directive.js @@ -144,6 +144,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout scope.targetDeviceAlias.value = null; } } else if (scope.widgetType === types.widgetType.alarm.value && scope.isDataEnabled) { + scope.alarmSearchStatus = angular.isDefined(config.alarmSearchStatus) ? + config.alarmSearchStatus : types.alarmSearchStatus.any; + scope.alarmsPollingInterval = angular.isDefined(config.alarmsPollingInterval) ? + config.alarmsPollingInterval : 5; if (config.alarmSource) { scope.alarmSource.value = config.alarmSource; } else { @@ -205,7 +209,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout }; scope.$watch('title + showTitle + dropShadow + enableFullscreen + backgroundColor + color + ' + - 'padding + titleStyle + mobileOrder + mobileHeight + units + decimals + useDashboardTimewindow + showLegend', function () { + 'padding + titleStyle + mobileOrder + mobileHeight + units + decimals + useDashboardTimewindow + ' + + 'alarmSearchStatus + alarmsPollingInterval + showLegend', function () { if (ngModelCtrl.$viewValue) { var value = ngModelCtrl.$viewValue; if (value.config) { @@ -225,6 +230,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout config.units = scope.units; config.decimals = scope.decimals; config.useDashboardTimewindow = scope.useDashboardTimewindow; + config.alarmSearchStatus = scope.alarmSearchStatus; + config.alarmsPollingInterval = scope.alarmsPollingInterval; config.showLegend = scope.showLegend; } if (value.layout) { diff --git a/ui/src/app/components/widget-config.tpl.html b/ui/src/app/components/widget-config.tpl.html index 386f33a62f..4bfe55c0ef 100644 --- a/ui/src/app/components/widget-config.tpl.html +++ b/ui/src/app/components/widget-config.tpl.html @@ -31,6 +31,30 @@ flex ng-model="timewindow"> +
+ + + + + {{ ('alarm.search-status.' + searchStatus) | translate }} + + + + + + +
+
alarm.polling-interval-required
+
alarm.min-polling-interval-message
+
+
+
0) { + vm.defaultPageSize = pageSize; + } + + if (vm.settings.defaultSortOrder && vm.settings.defaultSortOrder.length) { + vm.defaultSortOrder = vm.settings.defaultSortOrder; + } + + vm.query.order = vm.defaultSortOrder; + vm.query.limit = vm.defaultPageSize; + if (vm.isGtMd) { + vm.limitOptions = [vm.defaultPageSize, vm.defaultPageSize*2, vm.defaultPageSize*3]; + } else { + vm.limitOptions = null; + } var origColor = vm.widgetConfig.color || 'rgba(0, 0, 0, 0.87)'; var defaultColor = tinycolor(origColor); @@ -207,6 +248,115 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti updateAlarms(); } + function onRowClick($event, alarm) { + if (vm.currentAlarm != alarm) { + vm.currentAlarm = alarm; + } + } + + function isCurrent(alarm) { + return (vm.currentAlarm && alarm && vm.currentAlarm.id && alarm.id) && + (vm.currentAlarm.id.id === alarm.id.id); + } + + function openAlarmDetails($event, alarm) { + if (alarm && alarm.id) { + var onShowingCallback = { + onShowing: function(){} + } + $mdDialog.show({ + controller: 'AlarmDetailsDialogController', + controllerAs: 'vm', + templateUrl: alarmDetailsDialogTemplate, + locals: { + alarmId: alarm.id.id, + allowAcknowledgment: vm.allowAcknowledgment, + allowClear: vm.allowClear, + showingCallback: onShowingCallback + }, + parent: angular.element($document[0].body), + targetEvent: $event, + fullscreen: true, + skipHide: true, + onShowing: function(scope, element) { + onShowingCallback.onShowing(scope, element); + } + }).then(function (alarm) { + if (alarm) { + vm.subscription.update(); + } + }); + + } + } + + function ackAlarms($event) { + if ($event) { + $event.stopPropagation(); + } + if (vm.selectedAlarms && vm.selectedAlarms.length > 0) { + var title = $translate.instant('alarm.aknowledge-alarms-title', {count: vm.selectedAlarms.length}, 'messageformat'); + var content = $translate.instant('alarm.aknowledge-alarms-text', {count: vm.selectedAlarms.length}, 'messageformat'); + var confirm = $mdDialog.confirm() + .targetEvent($event) + .title(title) + .htmlContent(content) + .ariaLabel(title) + .cancel($translate.instant('action.no')) + .ok($translate.instant('action.yes')); + $mdDialog.show(confirm).then(function () { + var tasks = []; + for (var i=0;i 0) { + var title = $translate.instant('alarm.clear-alarms-title', {count: vm.selectedAlarms.length}, 'messageformat'); + var content = $translate.instant('alarm.clear-alarms-text', {count: vm.selectedAlarms.length}, 'messageformat'); + var confirm = $mdDialog.confirm() + .targetEvent($event) + .title(title) + .htmlContent(content) + .ariaLabel(title) + .cancel($translate.instant('action.no')) + .ok($translate.instant('action.yes')); + $mdDialog.show(confirm).then(function () { + var tasks = []; + for (var i=0;i {{ vm.alarmsTitle }} - + search {{ 'action.search' | translate }} @@ -57,13 +57,13 @@ translate-values="{count: vm.selectedAlarms.length}" translate-interpolation="messageformat"> - + done {{ 'alarm.acknowledge' | translate }} - + clear {{ 'alarm.clear' | translate }} @@ -72,22 +72,22 @@ - +
- - + + - -
{{ key.label }} {{ key.title }} 
+ more_horiz @@ -104,7 +104,7 @@ layout-align="center center" class="no-data-found" translate>alarm.no-alarms-prompt - diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss index ab720c5e5a..5b5f92947f 100644 --- a/ui/src/scss/main.scss +++ b/ui/src/scss/main.scss @@ -281,7 +281,55 @@ pre.tb-highlight { display: flex; } table.md-table { + &.md-row-select td.md-cell, + &.md-row-select th.md-column { + &:first-child { + width: 20px; + padding: 0 0 0 12px; + } + + &:nth-child(2) { + padding: 0 12px; + } + + &:nth-child(n+3):nth-last-child(n+2) { + padding: 0 28px 0 0; + } + } + + &:not(.md-row-select) td.md-cell, + &:not(.md-row-select) th.md-column { + &:first-child { + padding: 0 12px; + } + + &:nth-child(n+2):nth-last-child(n+2) { + padding: 0 28px 0 0; + } + } + + td.md-cell, + th.md-column { + + &:last-child { + padding: 0 12px 0 0; + } + + } + } + + table.md-table, table.md-table.md-row-select { tbody { + &.md-body { + tr { + &.md-row:not([disabled]) { + outline: none; + &.tb-current, &.tb-current:hover{ + background-color: #dddddd !important; + } + } + } + } tr { td { &.tb-action-cell {