thingsboard-aplcache

Details

diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java
index fe35ef6..260669f 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java
@@ -26,10 +26,8 @@ import org.thingsboard.rule.engine.api.*;
 import org.thingsboard.rule.engine.api.util.DonAsynchron;
 import org.thingsboard.rule.engine.api.util.TbNodeUtils;
 import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
-import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
 import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
 import org.thingsboard.server.common.data.kv.TsKvEntry;
-import org.thingsboard.server.common.data.kv.TsKvQuery;
 import org.thingsboard.server.common.data.plugin.ComponentType;
 import org.thingsboard.server.common.msg.TbMsg;
 
@@ -39,8 +37,7 @@ import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
-import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.FETCH_MODE_ALL;
-import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.MAX_FETCH_SIZE;
+import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.*;
 import static org.thingsboard.server.common.data.kv.Aggregation.NONE;
 
 /**
@@ -64,6 +61,7 @@ public class TbGetTelemetryNode implements TbNode {
     private long endTsOffset;
     private int limit;
     private ObjectMapper mapper;
+    private String fetchMode;
 
     @Override
     public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
@@ -72,6 +70,7 @@ public class TbGetTelemetryNode implements TbNode {
         startTsOffset = TimeUnit.valueOf(config.getStartIntervalTimeUnit()).toMillis(config.getStartInterval());
         endTsOffset = TimeUnit.valueOf(config.getEndIntervalTimeUnit()).toMillis(config.getEndInterval());
         limit = config.getFetchMode().equals(FETCH_MODE_ALL) ? MAX_FETCH_SIZE : 1;
+        fetchMode = config.getFetchMode();
         mapper = new ObjectMapper();
         mapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, false);
         mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
@@ -96,14 +95,18 @@ public class TbGetTelemetryNode implements TbNode {
         }
     }
 
-    //TODO: handle direction;
     private List<ReadTsKvQuery> buildQueries() {
         long ts = System.currentTimeMillis();
         long startTs = ts - startTsOffset;
         long endTs = ts - endTsOffset;
-
+        String orderBy;
+        if (fetchMode.equals(FETCH_MODE_FIRST) || fetchMode.equals(FETCH_MODE_ALL)) {
+            orderBy = "ASC";
+        } else {
+            orderBy = "DESC";
+        }
         return tsKeyNames.stream()
-                .map(key -> new BaseReadTsKvQuery(key, startTs, endTs, 1, limit, NONE))
+                .map(key -> new BaseReadTsKvQuery(key, startTs, endTs, 1, limit, NONE, orderBy))
                 .collect(Collectors.toList());
     }
 
@@ -116,7 +119,7 @@ public class TbGetTelemetryNode implements TbNode {
         }
 
         for (String key : tsKeyNames) {
-            if(resultNode.has(key)){
+            if (resultNode.has(key)) {
                 msg.getMetaData().putValue(key, resultNode.get(key).toString());
             }
         }
@@ -127,11 +130,11 @@ public class TbGetTelemetryNode implements TbNode {
     }
 
     private void processArray(ObjectNode node, TsKvEntry entry) {
-        if(node.has(entry.getKey())){
+        if (node.has(entry.getKey())) {
             ArrayNode arrayNode = (ArrayNode) node.get(entry.getKey());
             ObjectNode obj = buildNode(entry);
             arrayNode.add(obj);
-        }else {
+        } else {
             ArrayNode arrayNode = mapper.createArrayNode();
             ObjectNode obj = buildNode(entry);
             arrayNode.add(obj);
diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js
index 350b1ef..a66c4b1 100644
--- a/ui/src/app/api/subscription.js
+++ b/ui/src/app/api/subscription.js
@@ -267,6 +267,14 @@ export default class Subscription {
         } else {
             this.startWatchingTimewindow();
         }
+        registration = this.ctx.$scope.$watch(function () {
+            return subscription.alarmSearchStatus;
+        }, function (newAlarmSearchStatus, prevAlarmSearchStatus) {
+            if (!angular.equals(newAlarmSearchStatus, prevAlarmSearchStatus)) {
+                subscription.update();
+            }
+        }, true);
+        this.registrations.push(registration);
     }
 
     initDataSubscription() {
diff --git a/ui/src/app/components/datetime-period.tpl.html b/ui/src/app/components/datetime-period.tpl.html
index 605c3a8..7479576 100644
--- a/ui/src/app/components/datetime-period.tpl.html
+++ b/ui/src/app/components/datetime-period.tpl.html
@@ -18,14 +18,14 @@
 <section layout="column" layout-align="start start">
 	<section layout="row" layout-align="start start">
 	    <mdp-date-picker ng-model="startDate" mdp-placeholder="{{ 'datetime.date-from' | translate }}"
-	       	mdp-max-date="maxStartDate"></mdp-date-picker>
+	       	></mdp-date-picker>
 	    <mdp-time-picker ng-model="startDate" mdp-placeholder="{{ 'datetime.time-from' | translate }}"
-	    	mdp-max-date="maxStartDate" mdp-auto-switch="true"></mdp-time-picker>   	
+	    	mdp-auto-switch="true"></mdp-time-picker>
     </section>
 	<section layout="row" layout-align="start start">
 	    <mdp-date-picker ng-model="endDate" mdp-placeholder="{{ 'datetime.date-to' | translate }}"
-	       	mdp-min-date="minEndDate" mdp-max-date="maxEndDate"></mdp-date-picker>
+	       	></mdp-date-picker>
 	    <mdp-time-picker ng-model="endDate" mdp-placeholder="{{ 'datetime.time-to' | translate }}"
-	    	mdp-min-date="minEndDate" mdp-max-date="maxEndDate" mdp-auto-switch="true"></mdp-time-picker>   	
+	    	mdp-auto-switch="true"></mdp-time-picker>
     </section>
 </section>
\ No newline at end of file
diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json
index 6321d63..2caf013 100644
--- a/ui/src/app/locale/locale.constant-en_US.json
+++ b/ui/src/app/locale/locale.constant-en_US.json
@@ -133,8 +133,13 @@
         "min-polling-interval-message": "At least 1 sec polling interval is allowed.",
         "aknowledge-alarms-title": "Acknowledge { count, plural, 1 {1 alarm} other {# alarms} }",
         "aknowledge-alarms-text": "Are you sure you want to acknowledge { count, plural, 1 {1 alarm} other {# alarms} }?",
+        "aknowledge-alarm-title": "Acknowledge Alarm",
+        "aknowledge-alarm-text": "Are you sure you want to acknowledge Alarm?",
         "clear-alarms-title": "Clear { count, plural, 1 {1 alarm} other {# alarms} }",
-        "clear-alarms-text": "Are you sure you want to clear { count, plural, 1 {1 alarm} other {# alarms} }?"
+        "clear-alarms-text": "Are you sure you want to clear { count, plural, 1 {1 alarm} other {# alarms} }?",
+        "clear-alarm-title": "Clear Alarm",
+        "clear-alarm-text": "Are you sure you want to clear Alarm?",
+        "alarm-status-filter": "Alarm Status Filter"
     },
     "alias": {
         "add": "Add alias",
@@ -748,7 +753,8 @@
         "entity-name": "Entity name",
         "details": "Entity details",
         "no-entities-prompt": "No entities found",
-        "no-data": "No data to display"
+        "no-data": "No data to display",
+        "columns-to-display": "Columns to Display"
     },
     "event": {
         "event-type": "Event type",
diff --git a/ui/src/app/widget/lib/alarms-table-widget.js b/ui/src/app/widget/lib/alarms-table-widget.js
index 0696a7b..6ae17e0 100644
--- a/ui/src/app/widget/lib/alarms-table-widget.js
+++ b/ui/src/app/widget/lib/alarms-table-widget.js
@@ -14,11 +14,15 @@
  * limitations under the License.
  */
 import './alarms-table-widget.scss';
+import './display-columns-panel.scss';
+import './alarm-status-filter-panel.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';
+import displayColumnsPanelTemplate from './display-columns-panel.tpl.html';
+import alarmStatusFilterPanelTemplate from './alarm-status-filter-panel.tpl.html';
 
 /* eslint-enable import/no-unresolved, import/default */
 
@@ -45,7 +49,7 @@ function AlarmsTableWidget() {
 }
 
 /*@ngInject*/
-function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDialog, $document, $translate, $q, $timeout, alarmService, utils, types) {
+function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDialog, $mdPanel, $document, $translate, $q, $timeout, alarmService, utils, types) {
     var vm = this;
 
     vm.stylesInfo = {};
@@ -60,6 +64,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
     vm.selectedAlarms = []
 
     vm.alarmSource = null;
+    vm.alarmSearchStatus = null;
     vm.allAlarms = [];
 
     vm.currentAlarm = null;
@@ -95,14 +100,20 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
     vm.onPaginate = onPaginate;
     vm.onRowClick = onRowClick;
     vm.onActionButtonClick = onActionButtonClick;
+    vm.actionEnabled = actionEnabled;
     vm.isCurrent = isCurrent;
     vm.openAlarmDetails = openAlarmDetails;
     vm.ackAlarms = ackAlarms;
+    vm.ackAlarm = ackAlarm;
     vm.clearAlarms = clearAlarms;
+    vm.clearAlarm = clearAlarm;
 
     vm.cellStyle = cellStyle;
     vm.cellContent = cellContent;
 
+    vm.editAlarmStatusFilter = editAlarmStatusFilter;
+    vm.editColumnsToDisplay = editColumnsToDisplay;
+
     $scope.$watch('vm.ctx', function() {
         if (vm.ctx) {
             vm.settings = vm.ctx.settings;
@@ -158,7 +169,41 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
 
         vm.ctx.widgetActions = [ vm.searchAction ];
 
-        vm.actionCellDescriptors = vm.ctx.actionsApi.getActionDescriptors('actionCellButton');
+        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.displayDetails) {
+            vm.actionCellDescriptors.push(
+                {
+                    displayName: $translate.instant('alarm.details'),
+                    icon: 'more_horiz',
+                    details: true
+                }
+            );
+        }
+
+        if (vm.allowAcknowledgment) {
+            vm.actionCellDescriptors.push(
+                {
+                    displayName: $translate.instant('alarm.acknowledge'),
+                    icon: 'done',
+                    acknowledge: true
+                }
+            );
+        }
+
+        if (vm.allowClear) {
+            vm.actionCellDescriptors.push(
+                {
+                    displayName: $translate.instant('alarm.clear'),
+                    icon: 'clear',
+                    clear: true
+                }
+            );
+        }
+
+        vm.actionCellDescriptors = vm.actionCellDescriptors.concat(vm.ctx.actionsApi.getActionDescriptors('actionCellButton'));
 
         if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) {
             vm.alarmsTitle = utils.customTranslation(vm.settings.alarmsTitle, vm.settings.alarmsTitle);
@@ -170,9 +215,6 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
 
         vm.enableSelection = angular.isDefined(vm.settings.enableSelection) ? vm.settings.enableSelection : true;
         vm.searchAction.show = 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;
         }
@@ -305,16 +347,35 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
     }
 
     function onActionButtonClick($event, alarm, actionDescriptor) {
-        if ($event) {
-            $event.stopPropagation();
+        if (actionDescriptor.details) {
+            vm.openAlarmDetails($event, alarm);
+        } else if (actionDescriptor.acknowledge) {
+            vm.ackAlarm($event, alarm);
+        } else if (actionDescriptor.clear) {
+            vm.clearAlarm($event, alarm);
+        } else {
+            if ($event) {
+                $event.stopPropagation();
+            }
+            var entityId;
+            var entityName;
+            if (alarm && alarm.originator) {
+                entityId = alarm.originator;
+                entityName = alarm.originatorName;
+            }
+            vm.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, {alarm: alarm});
         }
-        var entityId;
-        var entityName;
-        if (alarm && alarm.originator) {
-            entityId = alarm.originator;
-            entityName = alarm.originatorName;
+    }
+
+    function actionEnabled(alarm, actionDescriptor) {
+        if (actionDescriptor.acknowledge) {
+            return (alarm.status == types.alarmStatus.activeUnack ||
+                    alarm.status == types.alarmStatus.clearedUnack);
+        } else if (actionDescriptor.clear) {
+            return (alarm.status == types.alarmStatus.activeAck ||
+                    alarm.status == types.alarmStatus.activeUnack);
         }
-        vm.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, { alarm: alarm });
+        return true;
     }
 
     function isCurrent(alarm) {
@@ -387,6 +448,25 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
         }
     }
 
+    function ackAlarm($event, alarm) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        var confirm = $mdDialog.confirm()
+            .targetEvent($event)
+            .title($translate.instant('alarm.aknowledge-alarm-title'))
+            .htmlContent($translate.instant('alarm.aknowledge-alarm-text'))
+            .ariaLabel($translate.instant('alarm.acknowledge'))
+            .cancel($translate.instant('action.no'))
+            .ok($translate.instant('action.yes'));
+        $mdDialog.show(confirm).then(function () {
+            alarmService.ackAlarm(alarm.id.id).then(function () {
+                vm.selectedAlarms = [];
+                vm.subscription.update();
+            });
+        });
+    }
+
     function clearAlarms($event) {
         if ($event) {
             $event.stopPropagation();
@@ -420,6 +500,24 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
         }
     }
 
+    function clearAlarm($event, alarm) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        var confirm = $mdDialog.confirm()
+            .targetEvent($event)
+            .title($translate.instant('alarm.clear-alarm-title'))
+            .htmlContent($translate.instant('alarm.clear-alarm-text'))
+            .ariaLabel($translate.instant('alarm.clear'))
+            .cancel($translate.instant('action.no'))
+            .ok($translate.instant('action.yes'));
+        $mdDialog.show(confirm).then(function () {
+            alarmService.clearAlarm(alarm.id.id).then(function () {
+                vm.selectedAlarms = [];
+                vm.subscription.update();
+            });
+        });
+    }
 
     function updateAlarms(preserveSelections) {
         if (!preserveSelections) {
@@ -558,6 +656,54 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
         }
     }
 
+    function editAlarmStatusFilter($event) {
+        var element = angular.element($event.target);
+        var position = $mdPanel.newPanelPosition()
+            .relativeTo(element)
+            .addPanelPosition($mdPanel.xPosition.ALIGN_END, $mdPanel.yPosition.BELOW);
+        var config = {
+            attachTo: angular.element($document[0].body),
+            controller: AlarmStatusFilterPanelController,
+            controllerAs: 'vm',
+            templateUrl: alarmStatusFilterPanelTemplate,
+            panelClass: 'tb-alarm-status-filter-panel',
+            position: position,
+            fullscreen: false,
+            locals: {
+                'subscription': vm.subscription
+            },
+            openFrom: $event,
+            clickOutsideToClose: true,
+            escapeToClose: true,
+            focusOnOpen: false
+        };
+        $mdPanel.open(config);
+    }
+
+    function editColumnsToDisplay($event) {
+        var element = angular.element($event.target);
+        var position = $mdPanel.newPanelPosition()
+            .relativeTo(element)
+            .addPanelPosition($mdPanel.xPosition.ALIGN_END, $mdPanel.yPosition.BELOW);
+        var config = {
+            attachTo: angular.element($document[0].body),
+            controller: DisplayColumnsPanelController,
+            controllerAs: 'vm',
+            templateUrl: displayColumnsPanelTemplate,
+            panelClass: 'tb-display-columns-panel',
+            position: position,
+            fullscreen: false,
+            locals: {
+                'columns': vm.alarmSource.dataKeys
+            },
+            openFrom: $event,
+            clickOutsideToClose: true,
+            escapeToClose: true,
+            focusOnOpen: false
+        };
+        $mdPanel.open(config);
+    }
+
     function updateAlarmSource() {
 
         vm.ctx.widgetTitle = utils.createLabelFromDatasource(vm.alarmSource, vm.alarmsTitle);
@@ -570,6 +716,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
             var dataKey = vm.alarmSource.dataKeys[d];
 
             dataKey.title = utils.customTranslation(dataKey.label, dataKey.label);
+            dataKey.display = true;
 
             var keySettings = dataKey.settings;
 
@@ -618,4 +765,19 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
         }
     }
 
-}
\ No newline at end of file
+}
+
+/*@ngInject*/
+function DisplayColumnsPanelController(columns) {  //eslint-disable-line
+
+    var vm = this;
+    vm.columns = columns;
+}
+
+/*@ngInject*/
+function AlarmStatusFilterPanelController(subscription, types) {  //eslint-disable-line
+
+    var vm = this;
+    vm.types = types;
+    vm.subscription = subscription;
+}
diff --git a/ui/src/app/widget/lib/alarms-table-widget.scss b/ui/src/app/widget/lib/alarms-table-widget.scss
index 2922996..a031523 100644
--- a/ui/src/app/widget/lib/alarms-table-widget.scss
+++ b/ui/src/app/widget/lib/alarms-table-widget.scss
@@ -44,6 +44,27 @@
   &.tb-data-table {
     table.md-table,
     table.md-table.md-row-select {
+      th.md-column {
+        &.tb-action-cell {
+          .md-button {
+            /* stylelint-disable-next-line selector-max-class */
+            &.md-icon-button {
+              width: 36px;
+              height: 36px;
+              padding: 6px;
+              margin: 0;
+              /* stylelint-disable-next-line selector-max-class */
+              md-icon {
+                width: 24px;
+                height: 24px;
+                font-size: 24px !important;
+                line-height: 24px !important;
+              }
+            }
+          }
+        }
+      }
+
       tbody {
         tr {
           td {
@@ -51,6 +72,15 @@
               width: 36px;
               min-width: 36px;
               max-width: 36px;
+
+              .md-button[disabled] {
+                &.md-icon-button {
+                  /* stylelint-disable-next-line selector-max-class */
+                  md-icon {
+                    color: rgba(0, 0, 0, .38);
+                  }
+                }
+              }
             }
           }
         }
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 8480058..39843e1 100644
--- a/ui/src/app/widget/lib/alarms-table-widget.tpl.html
+++ b/ui/src/app/widget/lib/alarms-table-widget.tpl.html
@@ -62,33 +62,45 @@
             <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.title }}</span></th>
-                    <th md-column ng-if="vm.displayDetails"><span>&nbsp</span></th>
-                    <th md-column ng-if="vm.actionCellDescriptors.length"><span>&nbsp</span></th>
+                    <th ng-if="key.display" md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.alarmSource.dataKeys"><span>{{ key.title }}</span></th>
+                    <th md-column class="tb-action-cell" layout="row" layout-align="end center">
+                        <md-button class="md-icon-button"
+                                   aria-label="{{'alarm.alarm-status-filter' | translate}}"
+                                   ng-click="vm.editAlarmStatusFilter($event)">
+                            <md-icon aria-label="{{'alarm.alarm-status-filter' | translate}}"
+                                     class="material-icons">filter_list
+                            </md-icon>
+                            <md-tooltip md-direction="top">
+                                {{'alarm.alarm-status-filter' | translate}}
+                            </md-tooltip>
+                        </md-button>
+                        <md-button class="md-icon-button"
+                                   aria-label="{{'entity.columns-to-display' | translate}}"
+                                   ng-click="vm.editColumnsToDisplay($event)">
+                            <md-icon aria-label="{{'entity.columns-to-display' | translate}}"
+                                     class="material-icons">view_column
+                            </md-icon>
+                            <md-tooltip md-direction="top">
+                                {{'entity.columns-to-display' | translate}}
+                            </md-tooltip>
+                        </md-button>
+                    </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 flex ng-repeat="key in vm.alarmSource.dataKeys"
+                    <td ng-if="key.display" 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 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)" ng-disabled="$root.loading">
-                            <md-icon aria-label="{{ 'alarm.details' | translate }}" class="material-icons">more_horiz</md-icon>
-                            <md-tooltip md-direction="top">
-                                {{ 'alarm.details' | translate }}
-                            </md-tooltip>
-                        </md-button>
-                    </td>
-                    <td md-cell ng-if="vm.actionCellDescriptors.length" class="tb-action-cell"
+                    <td md-cell class="tb-action-cell"
                         ng-style="{minWidth: vm.actionCellDescriptors.length*36+'px',
                                    maxWidth: vm.actionCellDescriptors.length*36+'px',
                                    width: vm.actionCellDescriptors.length*36+'px'}">
                         <md-button class="md-icon-button" ng-repeat="actionDescriptor in vm.actionCellDescriptors"
+                                   ng-disabled="!vm.actionEnabled(alarm, actionDescriptor)"
                                    aria-label="{{ actionDescriptor.displayName }}"
                                    ng-click="vm.onActionButtonClick($event, alarm, actionDescriptor)" ng-disabled="$root.loading">
                             <md-icon aria-label="{{ actionDescriptor.displayName }}" class="material-icons">{{actionDescriptor.icon}}</md-icon>
diff --git a/ui/src/app/widget/lib/alarm-status-filter-panel.scss b/ui/src/app/widget/lib/alarm-status-filter-panel.scss
new file mode 100644
index 0000000..4bf6eee
--- /dev/null
+++ b/ui/src/app/widget/lib/alarm-status-filter-panel.scss
@@ -0,0 +1,31 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.tb-alarm-status-filter-panel {
+  min-width: 300px;
+  overflow: hidden;
+  background: #fff;
+  border-radius: 4px;
+  box-shadow:
+    0 7px 8px -4px rgba(0, 0, 0, .2),
+    0 13px 19px 2px rgba(0, 0, 0, .14),
+    0 5px 24px 4px rgba(0, 0, 0, .12);
+
+  md-content {
+    overflow: hidden;
+    background-color: #fff;
+  }
+}
diff --git a/ui/src/app/widget/lib/alarm-status-filter-panel.tpl.html b/ui/src/app/widget/lib/alarm-status-filter-panel.tpl.html
new file mode 100644
index 0000000..45e8414
--- /dev/null
+++ b/ui/src/app/widget/lib/alarm-status-filter-panel.tpl.html
@@ -0,0 +1,28 @@
+<!--
+
+    Copyright © 2016-2018 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+
+<md-content style="height: 100%" flex layout="column" class="md-padding">
+    <label class="tb-title" translate>alarm.alarm-status-filter</label>
+    <md-radio-group ng-model="vm.subscription.alarmSearchStatus" class="md-primary">
+        <md-radio-button ng-value="searchStatus"
+                         aria-label="{{ ('alarm.search-status.' + searchStatus) | translate }}"
+                         class="md-primary md-align-top-left md-radio-interactive" ng-repeat="searchStatus in vm.types.alarmSearchStatus">
+            {{ ('alarm.search-status.' + searchStatus) | translate }}
+        </md-radio-button>
+    </md-radio-group>
+</md-content>
diff --git a/ui/src/app/widget/lib/display-columns-panel.scss b/ui/src/app/widget/lib/display-columns-panel.scss
new file mode 100644
index 0000000..2e518cb
--- /dev/null
+++ b/ui/src/app/widget/lib/display-columns-panel.scss
@@ -0,0 +1,31 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.tb-display-columns-panel {
+  min-width: 300px;
+  overflow: hidden;
+  background: #fff;
+  border-radius: 4px;
+  box-shadow:
+    0 7px 8px -4px rgba(0, 0, 0, .2),
+    0 13px 19px 2px rgba(0, 0, 0, .14),
+    0 5px 24px 4px rgba(0, 0, 0, .12);
+
+  md-content {
+    overflow: hidden;
+    background-color: #fff;
+  }
+}
diff --git a/ui/src/app/widget/lib/display-columns-panel.tpl.html b/ui/src/app/widget/lib/display-columns-panel.tpl.html
new file mode 100644
index 0000000..42e2471
--- /dev/null
+++ b/ui/src/app/widget/lib/display-columns-panel.tpl.html
@@ -0,0 +1,24 @@
+<!--
+
+    Copyright © 2016-2018 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+
+<md-content style="height: 100%" flex layout="column" class="md-padding">
+    <label class="tb-title" translate>entity.columns-to-display</label>
+    <md-checkbox aria-label="{{ 'entity.columns-to-display' | translate }}" ng-repeat="column in vm.columns"
+                 ng-model="column.display">{{ column.title }}
+    </md-checkbox>
+</md-content>
diff --git a/ui/src/app/widget/lib/entities-table-widget.js b/ui/src/app/widget/lib/entities-table-widget.js
index d0b629d..620f313 100644
--- a/ui/src/app/widget/lib/entities-table-widget.js
+++ b/ui/src/app/widget/lib/entities-table-widget.js
@@ -14,11 +14,13 @@
  * limitations under the License.
  */
 import './entities-table-widget.scss';
+import './display-columns-panel.scss';
 
 /* eslint-disable import/no-unresolved, import/default */
 
 import entitiesTableWidgetTemplate from './entities-table-widget.tpl.html';
 //import entityDetailsDialogTemplate from './entitiy-details-dialog.tpl.html';
+import displayColumnsPanelTemplate from './display-columns-panel.tpl.html';
 
 /* eslint-enable import/no-unresolved, import/default */
 
@@ -45,7 +47,7 @@ function EntitiesTableWidget() {
 }
 
 /*@ngInject*/
-function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $translate, $timeout, utils, types) {
+function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $mdPanel, $document, $translate, $timeout, utils, types) {
     var vm = this;
 
     vm.stylesInfo = {};
@@ -98,6 +100,8 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
     vm.cellStyle = cellStyle;
     vm.cellContent = cellContent;
 
+    vm.editColumnsToDisplay = editColumnsToDisplay;
+
     $scope.$watch('vm.ctx', function() {
         if (vm.ctx && vm.ctx.defaultSubscription) {
             vm.settings = vm.ctx.settings;
@@ -414,12 +418,37 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
         }
     }
 
+    function editColumnsToDisplay($event) {
+        var element = angular.element($event.target);
+        var position = $mdPanel.newPanelPosition()
+            .relativeTo(element)
+            .addPanelPosition($mdPanel.xPosition.ALIGN_END, $mdPanel.yPosition.BELOW);
+        var config = {
+            attachTo: angular.element($document[0].body),
+            controller: DisplayColumnsPanelController,
+            controllerAs: 'vm',
+            templateUrl: displayColumnsPanelTemplate,
+            panelClass: 'tb-display-columns-panel',
+            position: position,
+            fullscreen: false,
+            locals: {
+                'columns': vm.columns
+            },
+            openFrom: $event,
+            clickOutsideToClose: true,
+            escapeToClose: true,
+            focusOnOpen: false
+        };
+        $mdPanel.open(config);
+    }
+
     function updateDatasources() {
 
         vm.stylesInfo = {};
         vm.contentsInfo = {};
         vm.columnWidth = {};
         vm.dataKeys = [];
+        vm.columns = [];
         vm.allEntities = [];
 
         var datasource;
@@ -429,6 +458,42 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
 
         vm.ctx.widgetTitle = utils.createLabelFromDatasource(datasource, vm.entitiesTitle);
 
+        if (vm.displayEntityName) {
+            vm.columns.push(
+                {
+                    name: 'entityName',
+                    label: 'entityName',
+                    title: vm.entityNameColumnTitle,
+                    display: true
+                }
+            );
+            vm.contentsInfo['entityName'] = {
+                useCellContentFunction: false
+            };
+            vm.stylesInfo['entityName'] = {
+                useCellStyleFunction: false
+            };
+            vm.columnWidth['entityName'] = '0px';
+        }
+
+        if (vm.displayEntityType) {
+            vm.columns.push(
+                {
+                    name: 'entityType',
+                    label: 'entityType',
+                    title: $translate.instant('entity.entity-type'),
+                    display: true
+                }
+            );
+            vm.contentsInfo['entityType'] = {
+                useCellContentFunction: false
+            };
+            vm.stylesInfo['entityType'] = {
+                useCellStyleFunction: false
+            };
+            vm.columnWidth['entityType'] = '0px';
+        }
+
         for (var d = 0; d < datasource.dataKeys.length; d++ ) {
             dataKey = angular.copy(datasource.dataKeys[d]);
             if (dataKey.type == types.dataKeyType.function) {
@@ -482,6 +547,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
 
             var columnWidth = angular.isDefined(keySettings.columnWidth) ? keySettings.columnWidth : '0px';
             vm.columnWidth[dataKey.label] = columnWidth;
+
+            dataKey.display = true;
+            vm.columns.push(dataKey);
         }
 
         for (var i=0;i<vm.datasources.length;i++) {
@@ -511,4 +579,11 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
 
     }
 
-}
\ No newline at end of file
+}
+
+/*@ngInject*/
+function DisplayColumnsPanelController(columns) {  //eslint-disable-line
+
+    var vm = this;
+    vm.columns = columns;
+}
diff --git a/ui/src/app/widget/lib/entities-table-widget.scss b/ui/src/app/widget/lib/entities-table-widget.scss
index 85648d6..d745529 100644
--- a/ui/src/app/widget/lib/entities-table-widget.scss
+++ b/ui/src/app/widget/lib/entities-table-widget.scss
@@ -44,6 +44,27 @@
   &.tb-data-table {
     table.md-table,
     table.md-table.md-row-select {
+      th.md-column {
+        &.tb-action-cell {
+          .md-button {
+            /* stylelint-disable-next-line selector-max-class */
+            &.md-icon-button {
+              width: 36px;
+              height: 36px;
+              padding: 6px;
+              margin: 0;
+              /* stylelint-disable-next-line selector-max-class */
+              md-icon {
+                width: 24px;
+                height: 24px;
+                font-size: 24px !important;
+                line-height: 24px !important;
+              }
+            }
+          }
+        }
+      }
+
       tbody {
         tr {
           td {
@@ -51,6 +72,15 @@
               width: 36px;
               min-width: 36px;
               max-width: 36px;
+
+              .md-button[disabled] {
+                &.md-icon-button {
+                  /* stylelint-disable-next-line selector-max-class */
+                  md-icon {
+                    color: rgba(0, 0, 0, .38);
+                  }
+                }
+              }
             }
           }
         }
diff --git a/ui/src/app/widget/lib/entities-table-widget.tpl.html b/ui/src/app/widget/lib/entities-table-widget.tpl.html
index 474d536..66932a0 100644
--- a/ui/src/app/widget/lib/entities-table-widget.tpl.html
+++ b/ui/src/app/widget/lib/entities-table-widget.tpl.html
@@ -41,23 +41,30 @@
             <table md-table>
                 <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
                 <tr md-row>
-                    <th md-column ng-if="vm.displayEntityName" md-order-by="entityName"><span>{{vm.entityNameColumnTitle}}</span></th>
-                    <th md-column ng-if="vm.displayEntityType" md-order-by="entityType"><span translate>entity.entity-type</span></th>
-                    <th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.dataKeys"><span>{{ key.title }}</span></th>
-                    <th md-column ng-if="vm.actionCellDescriptors.length"><span>&nbsp</span></th>
+                    <th ng-if="column.display" md-column md-order-by="{{ column.name }}" ng-repeat="column in vm.columns"><span>{{ column.title }}</span></th>
+                    <th md-column class="tb-action-cell" layout="row" layout-align="end center">
+                        <md-button class="md-icon-button"
+                                   aria-label="{{'entity.columns-to-display' | translate}}"
+                                   ng-click="vm.editColumnsToDisplay($event)">
+                            <md-icon aria-label="{{'entity.columns-to-display' | translate}}"
+                                     class="material-icons">view_column
+                            </md-icon>
+                            <md-tooltip md-direction="top">
+                                {{'entity.columns-to-display' | translate}}
+                            </md-tooltip>
+                        </md-button>
+                    </th>
                 </tr>
                 </thead>
                 <tbody md-body>
                 <tr ng-show="vm.entities.length" md-row md-select="entity"
                     md-select-id="id.id" md-auto-select="false" ng-repeat="entity in vm.entities"
                     ng-click="vm.onRowClick($event, entity)" ng-class="{'tb-current': vm.isCurrent(entity)}">
-                    <td md-cell flex ng-if="vm.displayEntityName">{{entity.entityName}}</td>
-                    <td md-cell flex ng-if="vm.displayEntityType">{{entity.entityType}}</td>
-                    <td md-cell flex ng-repeat="key in vm.dataKeys"
-                        ng-style="vm.cellStyle(entity, key)"
-                        ng-bind-html="vm.cellContent(entity, key)">
+                    <td ng-if="column.display" md-cell flex ng-repeat="column in vm.columns"
+                        ng-style="vm.cellStyle(entity, column)"
+                        ng-bind-html="vm.cellContent(entity, column)">
                     </td>
-                    <td md-cell ng-if="vm.actionCellDescriptors.length" class="tb-action-cell"
+                    <td md-cell class="tb-action-cell"
                         ng-style="{minWidth: vm.actionCellDescriptors.length*36+'px',
                                    maxWidth: vm.actionCellDescriptors.length*36+'px',
                                    width: vm.actionCellDescriptors.length*36+'px'}">