thingsboard-memoizeit

Details

diff --git a/ui/src/app/alarm/alarm-details-dialog.controller.js b/ui/src/app/alarm/alarm-details-dialog.controller.js
index 0cc05ef..6c82e20 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 c958201..48f991c 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 @@
         </div>
     </md-dialog-content>
     <md-dialog-actions layout="row">
-        <md-button ng-if="vm.alarm.status==vm.types.alarmStatus.activeUnack ||
-                          vm.alarm.status==vm.types.alarmStatus.clearedUnack"
+        <md-button ng-if="vm.allowAcknowledgment && (vm.alarm.status==vm.types.alarmStatus.activeUnack ||
+                          vm.alarm.status==vm.types.alarmStatus.clearedUnack)"
                    class="md-raised md-primary"
                    ng-disabled="loading"
                    ng-click="vm.acknowledge()"
                    style="margin-right:20px;">{{ 'alarm.acknowledge' |
             translate }}
         </md-button>
-        <md-button ng-if="vm.alarm.status==vm.types.alarmStatus.activeAck ||
-                          vm.alarm.status==vm.types.alarmStatus.activeUnack"
+        <md-button ng-if="vm.allowClear && (vm.alarm.status==vm.types.alarmStatus.activeAck ||
+                          vm.alarm.status==vm.types.alarmStatus.activeUnack)"
                    class="md-raised md-primary"
                    ng-disabled="loading"
                    ng-click="vm.clear()">{{ 'alarm.clear' |
diff --git a/ui/src/app/alarm/alarm-row.directive.js b/ui/src/app/alarm/alarm-row.directive.js
index 9cb9bed..09c04f9 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 c2ee07a..db4cead 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 16e4cc3..030b852 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 9610360..6a4cbcc 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 90810af..d19617f 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 755c4f4..bb7a4b9 100644
--- a/ui/src/app/components/datakey-config.tpl.html
+++ b/ui/src/app/components/datakey-config.tpl.html
@@ -16,7 +16,9 @@
 
 -->
 <md-content class="md-padding" layout="column">
-    <md-autocomplete ng-if="model.type === types.dataKeyType.timeseries || model.type === types.dataKeyType.attribute"
+    <md-autocomplete ng-if="model.type === types.dataKeyType.timeseries ||
+    						model.type === types.dataKeyType.attribute ||
+    						model.type === types.dataKeyType.alarm"
     	   style="padding-bottom: 8px;"
            ng-required="true"
    		   md-no-cache="true"
@@ -27,8 +29,8 @@
 		   md-items="item in keysSearch(keySearchText)"
 	 	   md-item-text="item"
 		   md-min-length="0"
-           placeholder="Key name"
-           md-floating-label="Key">
+           placeholder="{{ 'entity.key-name' | translate }}"
+           md-floating-label="{{ 'entity.key' | translate }}">
            <span md-highlight-text="keySearchText" md-highlight-flags="^i">{{item}}</span>
     </md-autocomplete>
 	<div layout="row" layout-align="start center">     
@@ -48,7 +50,7 @@
 		    md-color-history="false">
 		</div>    
 	</div>
-	<div layout="row" layout-align="start center">
+	<div layout="row" layout-align="start center" ng-if="model.type !== types.dataKeyType.alarm">
 		<md-input-container flex>
 			<label translate>datakey.units</label>
 			<input name="units" ng-model="model.units">
diff --git a/ui/src/app/components/datasource-entity.scss b/ui/src/app/components/datasource-entity.scss
index 7a87fc7..b9e892b 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 d5cd782..9876491 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">
 				   <md-autocomplete
 						   md-no-cache="true"
 						   id="alarm_datakey"
@@ -152,6 +152,9 @@
 							   </div>
 							   <div ng-if="textIsNotEmpty(alarmDataKeySearchText)">
 								   <span translate translate-values='{ key: "{{alarmDataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>entity.no-key-matching</span>
+								   <span>
+										<a translate ng-click="createKey($event, '#alarm_datakey_chips')">entity.create-new-key</a>
+								   </span>
 							   </div>
 						   </div>
 					   </md-not-found>
diff --git a/ui/src/app/components/datasource-func.scss b/ui/src/app/components/datasource-func.scss
index 8739ed8..fdda0e4 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 1ce1108..7fa4aac 100644
--- a/ui/src/app/components/datasource-func.tpl.html
+++ b/ui/src/app/components/datasource-func.tpl.html
@@ -48,9 +48,9 @@
 									<span translate>device.no-keys-found</span>
 								</div>
 								<div ng-if="textIsNotEmpty(dataKeySearchText)">
-									<span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>device.no-key-matching</span>
+									<span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>entity.no-key-matching</span>
 									<span>
-										<a translate ng-click="createKey($event, '#function_datakey_chips')">device.create-new-key</a>
+										<a translate ng-click="createKey($event, '#function_datakey_chips')">entity.create-new-key</a>
 									</span>
 								</div>
 							</div>
@@ -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">
 			<md-autocomplete
 					md-no-cache="true"
 					id="alarm_datakey"
@@ -100,6 +100,9 @@
 						</div>
 						<div ng-if="textIsNotEmpty(alarmDataKeySearchText)">
 							<span translate translate-values='{ key: "{{alarmDataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>entity.no-key-matching</span>
+							<span>
+								<a translate ng-click="createKey($event, '#alarm_datakey_chips')">entity.create-new-key</a>
+							</span>
 						</div>
 					</div>
 				</md-not-found>
diff --git a/ui/src/app/components/widget.controller.js b/ui/src/app/components/widget.controller.js
index 2f76396..85f8e78 100644
--- a/ui/src/app/components/widget.controller.js
+++ b/ui/src/app/components/widget.controller.js
@@ -289,6 +289,10 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
             }
             if (widget.type == types.widgetType.alarm.value) {
                 options.alarmSource = angular.copy(widget.config.alarmSource);
+                options.alarmSearchStatus = angular.isDefined(widget.config.alarmSearchStatus) ?
+                    widget.config.alarmSearchStatus : types.alarmSearchStatus.any;
+                options.alarmsPollingInterval = angular.isDefined(widget.config.alarmsPollingInterval) ?
+                    widget.config.alarmsPollingInterval * 1000 : 5000;
             } else {
                 options.datasources = angular.copy(widget.config.datasources)
             }
diff --git a/ui/src/app/components/widget-config.directive.js b/ui/src/app/components/widget-config.directive.js
index e0b4800..3849cd3 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 386f33a..4bfe55c 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"></tb-timewindow>
                 </section>
             </div>
+            <div ng-show="widgetType === types.widgetType.alarm.value" layout='column' layout-align="center"
+                 layout-gt-sm='row' layout-align-gt-sm="start center">
+                <md-input-container class="md-block" flex>
+                    <label translate>alarm.alarm-status</label>
+                    <md-select ng-model="alarmSearchStatus" style="padding-bottom: 24px;">
+                        <md-option ng-repeat="searchStatus in types.alarmSearchStatus" ng-value="searchStatus">
+                            {{ ('alarm.search-status.' + searchStatus) | translate }}
+                        </md-option>
+                    </md-select>
+                </md-input-container>
+                <md-input-container flex class="md-block">
+                    <label translate>alarm.polling-interval</label>
+                    <input ng-required="widgetType === types.widgetType.alarm.value"
+                           type="number"
+                           step="1"
+                           min="1"
+                           name="alarmsPollingInterval"
+                           ng-model="alarmsPollingInterval"/>
+                    <div ng-messages="theForm.alarmsPollingInterval.$error" multiple md-auto-hide="false">
+                        <div ng-message="required" translate>alarm.polling-interval-required</div>
+                        <div ng-message="min" translate>alarm.min-polling-interval-message</div>
+                    </div>
+                </md-input-container>
+            </div>
             <v-accordion id="datasources-accordion" control="datasourcesAccordion" class="vAccordion--default"
                          ng-show="widgetType !== types.widgetType.rpc.value
                          && widgetType !== types.widgetType.alarm.value
diff --git a/ui/src/app/dashboard/states/entity-state-controller.js b/ui/src/app/dashboard/states/entity-state-controller.js
index 51cf67d..6cb8592 100644
--- a/ui/src/app/dashboard/states/entity-state-controller.js
+++ b/ui/src/app/dashboard/states/entity-state-controller.js
@@ -243,11 +243,9 @@ export default function EntityStateController($scope, $location, $state, $stateP
     }
 
     function gotoState(stateId, update, openRightLayout) {
-        if (vm.dashboardCtrl.dashboardCtx.state != stateId) {
-            vm.dashboardCtrl.openDashboardState(stateId, openRightLayout);
-            if (update) {
-                updateLocation();
-            }
+        vm.dashboardCtrl.openDashboardState(stateId, openRightLayout);
+        if (update) {
+            updateLocation();
         }
     }
 
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 85fe2b0..ee0f0c2 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -148,7 +148,14 @@ export default angular.module('thingsboard.locale', [])
                     "clear": "Clear",
                     "search": "Search alarms",
                     "selected-alarms": "{ count, select, 1 {1 alarm} other {# alarms} } selected",
-                    "no-data": "No data to display"
+                    "no-data": "No data to display",
+                    "polling-interval": "Alarms polling interval (sec)",
+                    "polling-interval-required": "Alarms polling interval is required.",
+                    "min-polling-interval-message": "At least 1 sec polling interval is allowed.",
+                    "aknowledge-alarms-title": "Acknowledge { count, select, 1 {1 alarm} other {# alarms} }",
+                    "aknowledge-alarms-text": "Are you sure you want to acknowledge { count, select, 1 {1 alarm} other {# alarms} }?",
+                    "clear-alarms-title": "Clear { count, select, 1 {1 alarm} other {# alarms} }",
+                    "clear-alarms-text": "Are you sure you want to clear { count, select, 1 {1 alarm} other {# alarms} }?"
                 },
                 "alias": {
                     "add": "Add alias",
@@ -643,6 +650,8 @@ export default angular.module('thingsboard.locale', [])
                     "no-aliases-found": "No aliases found.",
                     "no-alias-matching": "'{{alias}}' not found.",
                     "create-new-alias": "Create a new one!",
+                    "key": "Key",
+                    "key-name": "Key name",
                     "no-keys-found": "No keys found.",
                     "no-key-matching": "'{{key}}' not found.",
                     "create-new-key": "Create a new one!",
diff --git a/ui/src/app/locale/translate-handler.js b/ui/src/app/locale/translate-handler.js
index d227041..a0723cf 100644
--- a/ui/src/app/locale/translate-handler.js
+++ b/ui/src/app/locale/translate-handler.js
@@ -18,7 +18,8 @@
 export default function ThingsboardMissingTranslateHandler($log, types) {
 
     return function (translationId) {
-        if (translationId && !translationId.startsWith(types.translate.dashboardStatePrefix)) {
+        if (translationId && !translationId.startsWith(types.translate.dashboardStatePrefix) &&
+                             !translationId.startsWith(types.translate.keyLabelPrefix)) {
             $log.warn('Translation for ' + translationId + ' doesn\'t exist');
         }
     };
diff --git a/ui/src/app/widget/lib/alarms-table-widget.js b/ui/src/app/widget/lib/alarms-table-widget.js
index 9a3edcf..9a06100 100644
--- a/ui/src/app/widget/lib/alarms-table-widget.js
+++ b/ui/src/app/widget/lib/alarms-table-widget.js
@@ -19,6 +19,7 @@ import './alarms-table-widget.scss';
 /* eslint-disable import/no-unresolved, import/default */
 
 import alarmsTableWidgetTemplate from './alarms-table-widget.tpl.html';
+import alarmDetailsDialogTemplate from '../../alarm/alarm-details-dialog.tpl.html';
 
 /* eslint-enable import/no-unresolved, import/default */
 
@@ -46,11 +47,12 @@ function AlarmsTableWidget() {
 }
 
 /*@ngInject*/
-function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUtil, $translate, utils, types) {
+function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDialog, $document, $translate, $q, alarmService, utils, types) {
     var vm = this;
 
     vm.stylesInfo = {};
     vm.contentsInfo = {};
+    vm.columnWidth = {};
 
     vm.showData = true;
     vm.hasData = false;
@@ -64,19 +66,32 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti
 
     vm.currentAlarm = null;
 
+    vm.alarmsTitle = $translate.instant('alarm.alarms');
+    vm.enableSelection = true;
+    vm.enableSearch = true;
+    vm.displayDetails = true;
+    vm.allowAcknowledgment = true;
+    vm.allowClear = true;
+    vm.displayPagination = true;
+    vm.defaultPageSize = 10;
+    vm.defaultSortOrder = '-'+types.alarmFields.createdTime.value;
+
     vm.query = {
-        order: '-'+types.alarmFields.createdTime.value,
-        limit: 10,
+        order: vm.defaultSortOrder,
+        limit: vm.defaultPageSize,
         page: 1,
         search: null
     };
 
-    vm.alarmsTitle = $translate.instant('alarm.alarms');
-
     vm.enterFilterMode = enterFilterMode;
     vm.exitFilterMode = exitFilterMode;
     vm.onReorder = onReorder;
     vm.onPaginate = onPaginate;
+    vm.onRowClick = onRowClick;
+    vm.isCurrent = isCurrent;
+    vm.openAlarmDetails = openAlarmDetails;
+    vm.ackAlarms = ackAlarms;
+    vm.clearAlarms = clearAlarms;
 
     vm.cellStyle = cellStyle;
     vm.cellContent = cellContent;
@@ -119,7 +134,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti
     $scope.$watch(function() { return $mdMedia('gt-md'); }, function(isGtMd) {
         vm.isGtMd = isGtMd;
         if (vm.isGtMd) {
-            vm.limitOptions = [5, 10, 15];
+            vm.limitOptions = [vm.defaultPageSize, vm.defaultPageSize*2, vm.defaultPageSize*3];
         } else {
             vm.limitOptions = null;
         }
@@ -130,7 +145,33 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti
         if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) {
             vm.alarmsTitle = vm.settings.alarmsTitle;
         }
-        //TODO:
+        vm.enableSelection = angular.isDefined(vm.settings.enableSelection) ? vm.settings.enableSelection : true;
+        vm.enableSearch = angular.isDefined(vm.settings.enableSearch) ? vm.settings.enableSearch : true;
+        vm.displayDetails = angular.isDefined(vm.settings.displayDetails) ? vm.settings.displayDetails : true;
+        vm.allowAcknowledgment = angular.isDefined(vm.settings.allowAcknowledgment) ? vm.settings.allowAcknowledgment : true;
+        vm.allowClear = angular.isDefined(vm.settings.allowClear) ? vm.settings.allowClear : true;
+        if (!vm.allowAcknowledgment && !vm.allowClear) {
+            vm.enableSelection = false;
+        }
+
+        vm.displayPagination = angular.isDefined(vm.settings.displayPagination) ? vm.settings.displayPagination : true;
+
+        var pageSize = vm.settings.defaultPageSize;
+        if (angular.isDefined(pageSize) && Number.isInteger(pageSize) && pageSize > 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<vm.selectedAlarms.length;i++) {
+                    var alarm = vm.selectedAlarms[i];
+                    if (alarm.id) {
+                        tasks.push(alarmService.ackAlarm(alarm.id.id));
+                    }
+                }
+                if (tasks.length) {
+                    $q.all(tasks).then(function () {
+                        vm.selectedAlarms = [];
+                        vm.subscription.update();
+                    });
+                }
+
+            });
+        }
+    }
+
+    function clearAlarms($event) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        if (vm.selectedAlarms && vm.selectedAlarms.length > 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.selectedAlarms.length;i++) {
+                    var alarm = vm.selectedAlarms[i];
+                    if (alarm.id) {
+                        tasks.push(alarmService.clearAlarm(alarm.id.id));
+                    }
+                }
+                if (tasks.length) {
+                    $q.all(tasks).then(function () {
+                        vm.selectedAlarms = [];
+                        vm.subscription.update();
+                    });
+                }
+
+            });
+        }
+    }
+
+
     function updateAlarms(preserveSelections) {
         if (!preserveSelections) {
             vm.selectedAlarms = [];
@@ -216,8 +366,14 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti
             result = $filter('filter')(result, {$: vm.query.search});
         }
         vm.alarmsCount = result.length;
-        var startIndex = vm.query.limit * (vm.query.page - 1);
-        vm.alarms = result.slice(startIndex, startIndex + vm.query.limit);
+
+        if (vm.displayPagination) {
+            var startIndex = vm.query.limit * (vm.query.page - 1);
+            vm.alarms = result.slice(startIndex, startIndex + vm.query.limit);
+        } else {
+            vm.alarms = result;
+        }
+
         if (preserveSelections) {
             var newSelectedAlarms = [];
             if (vm.selectedAlarms && vm.selectedAlarms.length) {
@@ -251,6 +407,10 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti
                 style = defaultStyle(key, value);
             }
         }
+        if (!style.width) {
+            var columnWidth = vm.columnWidth[key.label];
+            style.width = columnWidth;
+        }
         return style;
     }
 
@@ -295,7 +455,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti
                     return value;
                 }
             } else {
-                return '';
+                return value;
             }
         } else {
             return '';
@@ -313,6 +473,8 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti
                 } else {
                     return {};
                 }
+            } else {
+                return {};
             }
         } else {
             return {};
@@ -340,9 +502,19 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti
 
         vm.stylesInfo = {};
         vm.contentsInfo = {};
+        vm.columnWidth = {};
 
         for (var d = 0; d < vm.alarmSource.dataKeys.length; d++ ) {
             var dataKey = vm.alarmSource.dataKeys[d];
+
+            var translationId = types.translate.keyLabelPrefix + dataKey.label;
+            var translation = $translate.instant(translationId);
+            if (translation != translationId) {
+                dataKey.title = translation;
+            } else {
+                dataKey.title = dataKey.label;
+            }
+
             var keySettings = dataKey.settings;
 
             var cellStyleFunction = null;
@@ -384,6 +556,9 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdUti
                 useCellContentFunction: useCellContentFunction,
                 cellContentFunction: cellContentFunction
             };
+
+            var columnWidth = angular.isDefined(keySettings.columnWidth) ? keySettings.columnWidth : '0px';
+            vm.columnWidth[dataKey.label] = columnWidth;
         }
     }
 
diff --git a/ui/src/app/widget/lib/alarms-table-widget.scss b/ui/src/app/widget/lib/alarms-table-widget.scss
index afe50f0..ac5addd 100644
--- a/ui/src/app/widget/lib/alarms-table-widget.scss
+++ b/ui/src/app/widget/lib/alarms-table-widget.scss
@@ -17,14 +17,14 @@
 .tb-alarms-table {
   margin-top: 15px;
   &.tb-data-table {
-    table.md-table {
+    table.md-table, table.md-table.md-row-select {
       tbody {
         tr {
           td {
-            &.ag-action-cell {
-              min-width: 40px;
-              max-width: 40px;
-              width: 40px;
+            &.tb-action-cell {
+              min-width: 36px;
+              max-width: 36px;
+              width: 36px;
             }
           }
         }
diff --git a/ui/src/app/widget/lib/alarms-table-widget.tpl.html b/ui/src/app/widget/lib/alarms-table-widget.tpl.html
index f1732b0..cfb92ca 100644
--- a/ui/src/app/widget/lib/alarms-table-widget.tpl.html
+++ b/ui/src/app/widget/lib/alarms-table-widget.tpl.html
@@ -22,7 +22,7 @@
             <div class="md-toolbar-tools">
                 <span>{{ vm.alarmsTitle }}</span>
                 <span flex></span>
-                <md-button class="md-icon-button" ng-click="vm.enterFilterMode()">
+                <md-button ng-if="vm.enableSearch" class="md-icon-button" ng-click="vm.enterFilterMode()">
                     <md-icon>search</md-icon>
                     <md-tooltip md-direction="top">
                         {{ 'action.search' | translate }}
@@ -57,13 +57,13 @@
                       translate-values="{count: vm.selectedAlarms.length}"
                       translate-interpolation="messageformat"></span>
                 <span flex></span>
-                <md-button class="md-icon-button" ng-click="vm.ackAlarms($event)">
+                <md-button ng-if="vm.allowAcknowledgment" class="md-icon-button" ng-click="vm.ackAlarms($event)">
                     <md-icon>done</md-icon>
                     <md-tooltip md-direction="top">
                         {{ 'alarm.acknowledge' | translate }}
                     </md-tooltip>
                 </md-button>
-                <md-button class="md-icon-button" ng-click="vm.clearAlarms($event)">
+                <md-button ng-if="vm.allowClear" class="md-icon-button" ng-click="vm.clearAlarms($event)">
                     <md-icon>clear</md-icon>
                     <md-tooltip md-direction="top">
                         {{ 'alarm.clear' | translate }}
@@ -72,22 +72,22 @@
             </div>
         </md-toolbar>
         <md-table-container flex>
-            <table md-table md-row-select multiple="" ng-model="vm.selectedAlarms">
+            <table md-table md-row-select="vm.enableSelection" multiple="" ng-model="vm.selectedAlarms">
                 <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
                 <tr md-row>
-                    <th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.alarmSource.dataKeys"><span>{{ key.label }}</span></th>
-                    <th md-column><span>&nbsp</span></th>
+                    <th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.alarmSource.dataKeys"><span>{{ key.title }}</span></th>
+                    <th md-column ng-if="vm.displayDetails"><span>&nbsp</span></th>
                 </tr>
                 </thead>
                 <tbody md-body>
                 <tr ng-show="vm.alarms.length" md-row md-select="alarm"
                     md-select-id="id.id" md-auto-select="false" ng-repeat="alarm in vm.alarms"
                     ng-click="vm.onRowClick($event, alarm)" ng-class="{'tb-current': vm.isCurrent(alarm)}">
-                    <td md-cell ng-repeat="key in vm.alarmSource.dataKeys"
+                    <td md-cell flex ng-repeat="key in vm.alarmSource.dataKeys"
                         ng-style="vm.cellStyle(alarm, key)"
                         ng-bind-html="vm.cellContent(alarm, key)">
                     </td>
-                    <td md-cell class="tb-action-cell">
+                    <td md-cell ng-if="vm.displayDetails" class="tb-action-cell">
                         <md-button class="md-icon-button" aria-label="{{ 'alarm.details' | translate }}"
                                    ng-click="vm.openAlarmDetails($event, alarm)">
                             <md-icon aria-label="{{ 'alarm.details' | translate }}" class="material-icons">more_horiz</md-icon>
@@ -104,7 +104,7 @@
                   layout-align="center center"
                   class="no-data-found" translate>alarm.no-alarms-prompt</span>
         </md-table-container>
-        <md-table-pagination md-boundary-links md-limit="vm.query.limit" md-limit-options="vm.limitOptions"
+        <md-table-pagination ng-if="vm.displayPagination" md-boundary-links md-limit="vm.query.limit" md-limit-options="vm.limitOptions"
                              md-page="vm.query.page" md-total="{{vm.alarmsCount}}"
                              md-on-paginate="vm.onPaginate" md-page-select="vm.isGtMd">
         </md-table-pagination>
diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss
index ab720c5..5b5f929 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 {