You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by to...@apache.org on 2018/11/13 15:26:07 UTC

[ambari-logsearch] branch ui-branch-fix updated: [AMBARI-24551] [Log Search UI] get rid of redundant requests after undoing or redoing several history steps (#22)

This is an automated email from the ASF dual-hosted git repository.

tobiasistvan pushed a commit to branch ui-branch-fix
in repository https://gitbox.apache.org/repos/asf/ambari-logsearch.git


The following commit(s) were added to refs/heads/ui-branch-fix by this push:
     new 5670040  [AMBARI-24551] [Log Search UI] get rid of redundant requests after undoing or redoing several history steps (#22)
5670040 is described below

commit 567004042bc974f6c13b45ec596d4558c80d026f
Author: Istvan Tobias <to...@gmail.com>
AuthorDate: Tue Nov 13 16:26:01 2018 +0100

    [AMBARI-24551] [Log Search UI] get rid of redundant requests after undoing or redoing several history steps (#22)
    
    * [AMBARI-24551] [Log Search UI] get rid of redundant requests after undoing or redoing several history steps
    
    * [AMBARI-24551] [Log Search UI] get rid of redundant requests after undoing or redoing several history steps - PR fixes
    
    * In progress
    
    * [AMBARI-24551] [Log Search UI] get rid of redundant requests after undoing or redoing several history steps - fixing form control implementations
    
    * [AMBARI-24551] [Log Search UI] get rid of redundant requests after undoing or redoing several history steps - change history manager with actions/reducers/effects focused solution.
    
    * [AMBARI-24551] [Log Search UI] get rid of redundant requests after undoing or redoing several history steps - working history manager
    
    * [AMBARI-24551] [Log Search UI] get rid of redundant requests after undoing or redoing several history steps - request in progress indicators
    
    * [AMBARI-24551] [Log Search UI] get rid of redundant requests after undoing or redoing several history steps - fixing dropdown with icons, optimize code readibility, changing labels
    
    * [AMBARI-24551] [Log Search UI] get rid of redundant requests after undoing or redoing several history steps - cleaning up the branch, writing tests fixing issues revealed by unit tests.
    
    * [AMBARI-24551] [Log Search UI] get rid of redundant requests after undoing or redoing several history steps - PR change requests
---
 ambari-logsearch-web/src/app/app-routing.module.ts |  19 +-
 ambari-logsearch-web/src/app/app.module.ts         |   8 +-
 .../models/filter-url-param-change.interface.ts}   |  18 +-
 .../src/app/classes/models/store.ts                |   2 +
 .../action-menu/action-menu.component.html         |  14 +-
 .../action-menu/action-menu.component.less         |  72 +---
 .../action-menu/action-menu.component.ts           |  46 +--
 .../src/app/components/app.component.html          |   4 +
 .../src/app/components/app.component.less          |  25 ++
 .../src/app/components/app.component.ts            |   7 +-
 .../cluster-filter/cluster-filter.component.ts     |   4 +-
 .../filter-button/filter-button.component.ts       |  59 +---
 .../filter-history-manager.component.html          |  29 ++
 .../filter-history-manager.component.less          | 114 ++++++
 .../filter-history-manager.component.spec.ts       | 296 ++++++++++++++++
 .../filter-history-manager.component.ts            | 381 +++++++++++++++++++++
 .../filters-panel/filters-panel.component.html     |   2 +-
 .../filters-panel/filters-panel.component.ts       |  20 +-
 .../log-index-filter/log-index-filter.component.ts |   2 +-
 .../components/login-form/login-form.component.ts  |   4 +-
 .../logs-container/logs-container.component.html   |  14 +-
 .../logs-container/logs-container.component.less   |  27 ++
 .../logs-container/logs-container.component.ts     | 129 ++++---
 .../menu-button/menu-button.component.spec.ts      |   4 +-
 .../menu-button/menu-button.component.ts           |  58 +++-
 .../pagination-controls.component.ts               |  24 +-
 .../components/search-box/search-box.component.ts  |  29 +-
 .../service-logs-table.component.ts                |   6 +
 .../time-range-picker.component.ts                 |  11 +-
 .../src/app/modules/app-load/app-load.module.ts    |   4 +-
 .../models/data-availability-state.model.ts        |   1 +
 .../src/app/modules/shared/animations.less         |  31 ++
 .../dropdown-button/dropdown-button.component.ts   |  24 +-
 .../dropdown-list/dropdown-list.component.html     |   2 +-
 .../dropdown-list/dropdown-list.component.less     |   5 +-
 .../dropdown-list/dropdown-list.component.ts       |  12 +-
 .../filter-dropdown/filter-dropdown.component.ts   |  15 +-
 .../modal-dialog/modal-dialog.component.spec.ts    |   2 +
 .../src/app/modules/shared/shared.module.ts        |  34 +-
 .../src/app/modules/shared/variables.less          |   1 +
 .../src/app/services/filter-history.guard.ts       | 128 +++++++
 .../src/app/services/history-manager.service.ts    | 114 ++----
 .../src/app/services/http-client.service.ts        |  10 +-
 .../app/services/log-index-filter.service.spec.ts  |   4 +-
 .../src/app/services/logs-container.service.ts     | 159 +++++----
 .../app/services/logs-filtering-utils.service.ts   | 158 ++++++---
 .../src/app/services/storage/reducers.service.ts   |   4 +-
 .../app/store/actions/filter-history.actions.ts    |  56 +++
 .../app/store/reducers/filter-history.reducers.ts  | 111 ++++++
 .../{auth.selectors.ts => app-state.selectors.ts}  |  34 +-
 .../store/selectors/audit-logs-fields.selectors.ts |  51 +++
 .../src/app/store/selectors/auth.selectors.ts      |  14 +-
 .../selectors/components.selectors.ts}             |  31 +-
 .../store/selectors/data-availability.selectors.ts |  49 +++
 .../store/selectors/filter-history.selectors.ts    |  94 +++++
 .../selectors/service-logs-fields.selectors.ts}    |  28 +-
 ambari-logsearch-web/src/app/test-config.spec.ts   |   2 +-
 ambari-logsearch-web/src/assets/i18n/en.json       |  96 +++++-
 58 files changed, 2098 insertions(+), 604 deletions(-)

diff --git a/ambari-logsearch-web/src/app/app-routing.module.ts b/ambari-logsearch-web/src/app/app-routing.module.ts
index a55e51a..5c8c4a1 100644
--- a/ambari-logsearch-web/src/app/app-routing.module.ts
+++ b/ambari-logsearch-web/src/app/app-routing.module.ts
@@ -16,14 +16,15 @@
  * limitations under the License.
  */
 
-import {NgModule} from '@angular/core';
-import {RouterModule, Routes} from '@angular/router';
-import {LogsContainerComponent} from '@app/components/logs-container/logs-container.component';
-import {LoginFormComponent} from '@app/components/login-form/login-form.component';
-import {AuthGuardService} from '@app/services/auth-guard.service';
-import {TabGuard} from '@app/services/tab.guard';
-import {LogsBreadcrumbsResolverService} from '@app/services/logs-breadcrumbs-resolver.service';
-import {LoginScreenGuardService} from '@app/services/login-screen-guard.service';
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+import { LogsContainerComponent } from '@app/components/logs-container/logs-container.component';
+import { LoginFormComponent } from '@app/components/login-form/login-form.component';
+import { AuthGuardService } from '@app/services/auth-guard.service';
+import { TabGuard } from '@app/services/tab.guard';
+import { FilterHistoryIndexGuard } from '@app/services/filter-history.guard';
+import { LogsBreadcrumbsResolverService } from '@app/services/logs-breadcrumbs-resolver.service';
+import { LoginScreenGuardService } from '@app/services/login-screen-guard.service';
 
 const appRoutes: Routes = [{
     path: 'login',
@@ -43,7 +44,7 @@ const appRoutes: Routes = [{
     resolve: {
       breadcrumbs: LogsBreadcrumbsResolverService
     },
-    canActivate: [AuthGuardService, TabGuard]
+    canActivate: [AuthGuardService, TabGuard, FilterHistoryIndexGuard]
   }, {
     path: 'logs',
     redirectTo: '/logs/serviceLogs',
diff --git a/ambari-logsearch-web/src/app/app.module.ts b/ambari-logsearch-web/src/app/app.module.ts
index 097bf04..9026f67 100644
--- a/ambari-logsearch-web/src/app/app.module.ts
+++ b/ambari-logsearch-web/src/app/app.module.ts
@@ -59,7 +59,6 @@ import {ServiceLogsFieldsService} from '@app/services/storage/service-logs-field
 import {AuditLogsFieldsService} from '@app/services/storage/audit-logs-fields.service';
 import {TabsService} from '@app/services/storage/tabs.service';
 import {AuthService} from '@app/services/auth.service';
-import {HistoryManagerService} from '@app/services/history-manager.service';
 import {reducer} from '@app/services/storage/reducers.service';
 
 import {AppComponent} from '@app/components/app.component';
@@ -109,6 +108,7 @@ import {ClusterSelectionService} from '@app/services/storage/cluster-selection.s
 import {TranslateService as AppTranslateService} from '@app/services/translate.service';
 import {RoutingUtilsService} from '@app/services/routing-utils.service';
 import {TabGuard} from '@app/services/tab.guard';
+import {FilterHistoryIndexGuard} from '@app/services/filter-history.guard';
 import {LogsBreadcrumbsResolverService} from '@app/services/logs-breadcrumbs-resolver.service';
 import {LogsFilteringUtilsService} from '@app/services/logs-filtering-utils.service';
 import {LogsStateService} from '@app/services/storage/logs-state.service';
@@ -116,6 +116,7 @@ import {LoginScreenGuardService} from '@app/services/login-screen-guard.service'
 
 import { AuthEffects } from '@app/store/effects/auth.effects';
 import { NotificationEffects } from '@app/store/effects/notification.effects';
+import { FilterHistoryManagerComponent } from './components/filter-history-manager/filter-history-manager.component';
 
 @NgModule({
   declarations: [
@@ -158,7 +159,8 @@ import { NotificationEffects } from '@app/store/effects/notification.effects';
     TimerSecondsPipe,
     ComponentLabelPipe,
     BreadcrumbsComponent,
-    ClusterFilterComponent
+    ClusterFilterComponent,
+    FilterHistoryManagerComponent
   ],
   imports: [
     BrowserModule,
@@ -217,10 +219,10 @@ import { NotificationEffects } from '@app/store/effects/notification.effects';
     AuditLogsFieldsService,
     TabsService,
     TabGuard,
+    FilterHistoryIndexGuard,
     LogsBreadcrumbsResolverService,
     AuthService,
     AuthGuardService,
-    HistoryManagerService,
     ClusterSelectionService,
     LogsFilteringUtilsService,
     LogsStateService,
diff --git a/ambari-logsearch-web/src/app/modules/shared/animations.less b/ambari-logsearch-web/src/app/classes/models/filter-url-param-change.interface.ts
similarity index 64%
copy from ambari-logsearch-web/src/app/modules/shared/animations.less
copy to ambari-logsearch-web/src/app/classes/models/filter-url-param-change.interface.ts
index 5b8a04c..2428fc0 100644
--- a/ambari-logsearch-web/src/app/modules/shared/animations.less
+++ b/ambari-logsearch-web/src/app/classes/models/filter-url-param-change.interface.ts
@@ -15,19 +15,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@keyframes rotateplane {
-  0% {
-    transform: perspective(120px) rotateX(0deg) rotateY(0deg);
-  } 50% {
-    transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
-  } 100% {
-    transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
-  }
-}
 
-.square-spinner(@size: 40px, @background: #3FAE2A, @speed: 1.2s) {
-  width: @size;
-  height: @size;
-  background: @background;
-  animation: rotateplane @speed infinite ease-in-out;
+export interface FilterUrlParamChange {
+  previousPath?: string | null;
+  currentPath: string;
+  time?: Date;
 }
diff --git a/ambari-logsearch-web/src/app/classes/models/store.ts b/ambari-logsearch-web/src/app/classes/models/store.ts
index f106b17..3811de1 100644
--- a/ambari-logsearch-web/src/app/classes/models/store.ts
+++ b/ambari-logsearch-web/src/app/classes/models/store.ts
@@ -35,6 +35,7 @@ import { LogsState } from '@app/classes/models/logs-state';
 import { DataAvaibilityStatesModel } from '@app/modules/app-load/models/data-availability-state.model';
 
 import * as auth from '@app/store/reducers/auth.reducers';
+import * as filterHistory from '@app/store/reducers/filter-history.reducers';
 
 const storeActions = {
     'ARRAY.ADD': 'ADD',
@@ -71,6 +72,7 @@ export interface AppStore {
   logsState: LogsState;
   dataAvailabilityStates: DataAvaibilityStatesModel;
   auth: auth.State;
+  filterHistory: filterHistory.FilterHistoryState;
 }
 
 export class ModelService {
diff --git a/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.html b/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.html
index e64a89c..f8c65de 100644
--- a/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.html
+++ b/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.html
@@ -14,19 +14,7 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-
-<!-- TODO use listClass="history-dropdown" for custom styling -->
-<menu-button label="{{'topMenu.undo' | translate}}" [subItems]="undoItems" iconClass="fa fa-arrow-left"
-             class="history-menu" [class.disabled]="!undoItems.length" [isDisabled]="!undoItems.length"
-             listClass="history-dropdown" (buttonClick)="undoLatest()" (selectItem)="undo($event)">
-</menu-button>
-<menu-button label="{{'topMenu.redo' | translate}}" [subItems]="redoItems" iconClass="fa fa-arrow-right"
-             class="history-menu" [class.disabled]="!redoItems.length" [isDisabled]="!redoItems.length"
-             listClass="history-dropdown" (buttonClick)="redoLatest()" (selectItem)="redo($event)">
-</menu-button>
-<menu-button label="{{'topMenu.history' | translate}}" [subItems]="historyItems" iconClass="fa fa-history"
-             class="history-menu" [class.disabled]="!historyItems.length" [isDisabled]="!historyItems.length"
-             listClass="history-dropdown" [isRightAlign]="true"></menu-button>
+<filter-history-manager></filter-history-manager>
 <menu-button label="{{'topMenu.filter' | translate}}" iconClass="fa fa-filter"
              (buttonClick)="openLogIndexFilter()"></menu-button>
 <menu-button *ngIf="!captureSeconds" label="{{'filter.capture' | translate}}" iconClass="fa fa-caret-right"
diff --git a/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.less b/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.less
index a8c6e05..db9a86b 100644
--- a/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.less
+++ b/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.less
@@ -19,81 +19,11 @@
 
 :host {
   display: block;
-  menu-button {
+  /deep/ menu-button {
     margin: 0 1em;
     /deep/ .stop-icon {
       color: @exclude-color;
     }
-    &.history-menu {
-      /deep/ ul {
-        li:not(.selection-all) {
-          margin: 0;
-          overflow: hidden;
-          position: relative;
-          transition: background-color 300ms ease-in, opacity 300ms ease-in, height 100ms 400ms ease-in;
-          &:before {
-            border-left: 1px solid darken(@unknown-color, 25%);
-            bottom: 0;
-            content: "";
-            display: block;
-            left: 12px;
-            position: absolute;
-            top: 0;
-          }
-          &:after {
-            background: #fff;
-            border: 1px solid darken(@unknown-color, 25%);
-            border-radius: 100%;
-            content: "";
-            height: 12px;
-            left: 7px;
-            position: absolute;
-            top: 6px;
-            transition: background-color 300ms;
-            width: 12px;
-          }
-
-          .list-item-label.label-container {
-            border-radius: 3px;
-            display: flex;
-            margin: 0 3px 0 25px;
-            padding: 3px 25px 3px 1em;
-            .item-label-text {
-              flex-grow: 1;
-              padding-right: 1em;
-            }
-            /deep/ history-item-controls {
-              float: none;
-              justify-self: right;
-            }
-          }
-
-          &.active > a, &:hover {
-            color: #262626;
-            text-decoration: none;
-            background-color: transparent;
-            .list-item-label.label-container {
-              background-color: #f5f5f5;
-            }
-          }
-        }
-        li:not(.selection-all):first-child {
-          &:before {
-            top: 50%;
-          }
-        }
-        li:not(.selection-all):last-child {
-          &:before {
-            bottom: 50%;
-          }
-        }
-        li:not(.selection-all):hover {
-          &:after {
-            background: @unknown-color;
-          }
-        }
-      }
-    }
   }
   /deep/ .modal-body {
     min-height: 25vh;
diff --git a/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.ts b/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.ts
index 46a0a76..721ae93 100644
--- a/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.ts
+++ b/ambari-logsearch-web/src/app/components/action-menu/action-menu.component.ts
@@ -22,14 +22,13 @@ import { ActivatedRoute, Router } from '@angular/router';
 
 import { Observable } from 'rxjs/Observable';
 import { BehaviorSubject } from 'rxjs/BehaviorSubject';
-import { Subscription } from 'rxjs/Subscription';
 
 import { LogsContainerService } from '@app/services/logs-container.service';
-import { HistoryManagerService } from '@app/services/history-manager.service';
 import { UserSettingsService } from '@app/services/user-settings.service';
 import { ListItem } from '@app/classes/list-item';
 import { ClustersService } from '@app/services/storage/clusters.service';
 import { UtilsService } from '@app/services/utils.service';
+import { Subject } from 'rxjs/Subject';
 
 @Component({
   selector: 'action-menu',
@@ -60,11 +59,10 @@ export class ActionMenuComponent  implements OnInit, OnDestroy {
 
   selectedClusterName$: BehaviorSubject<string> = new BehaviorSubject('');
 
-  subscriptions: Subscription[] = [];
+  destroyed$ = new Subject();
 
   constructor(
     private logsContainerService: LogsContainerService,
-    private historyManager: HistoryManagerService,
     private settings: UserSettingsService,
     private route: ActivatedRoute,
     private router: Router,
@@ -74,27 +72,13 @@ export class ActionMenuComponent  implements OnInit, OnDestroy {
   }
 
   ngOnInit() {
-    this.subscriptions.push(
-      this.selectedClusterName$.subscribe(
-        (clusterName: string) => this.setModalSubmitDisabled(!(!!clusterName))
-      )
+    this.selectedClusterName$.takeUntil(this.destroyed$).subscribe(
+      (clusterName: string) => this.setModalSubmitDisabled(!clusterName)
     );
   }
 
   ngOnDestroy() {
-    this.subscriptions.forEach((subscription: Subscription) => subscription.unsubscribe());
-  }
-
-  get undoItems(): ListItem[] {
-    return this.historyManager.undoItems;
-  }
-
-  get redoItems(): ListItem[] {
-    return this.historyManager.redoItems;
-  }
-
-  get historyItems(): ListItem[] {
-    return this.historyManager.activeHistory;
+    this.destroyed$.next(true);
   }
 
   get captureSeconds(): number {
@@ -105,26 +89,6 @@ export class ActionMenuComponent  implements OnInit, OnDestroy {
     this.isModalSubmitDisabled = isDisabled;
   }
 
-  undoLatest(): void {
-    if (this.undoItems.length) {
-      this.historyManager.undo(this.undoItems[0]);
-    }
-  }
-
-  redoLatest(): void {
-    if (this.redoItems.length) {
-      this.historyManager.redo(this.redoItems[0]);
-    }
-  }
-
-  undo(item: ListItem): void {
-    this.historyManager.undo(item);
-  }
-
-  redo(item: ListItem): void {
-    this.historyManager.redo(item);
-  }
-
   refresh(): void {
     this.logsContainerService.loadLogs();
   }
diff --git a/ambari-logsearch-web/src/app/components/app.component.html b/ambari-logsearch-web/src/app/components/app.component.html
index 47d461b..f788ab7 100644
--- a/ambari-logsearch-web/src/app/components/app.component.html
+++ b/ambari-logsearch-web/src/app/components/app.component.html
@@ -29,4 +29,8 @@
   <main-container *ngIf="!(isAuthorized$ | async) || (isBaseDataAvailable$ | async)"></main-container>
 
   <simple-notifications [options]="notificationServiceOptions"></simple-notifications>
+  <div class="request-indicator" [class.open]="httpClient.requestInProgress | async">
+    <i class="fa fa-spin fa-gear"></i>
+    {{ 'common.loading' | translate }}
+  </div>
 </ng-container>
diff --git a/ambari-logsearch-web/src/app/components/app.component.less b/ambari-logsearch-web/src/app/components/app.component.less
index 3e56671..5d51fa8 100644
--- a/ambari-logsearch-web/src/app/components/app.component.less
+++ b/ambari-logsearch-web/src/app/components/app.component.less
@@ -66,4 +66,29 @@
     justify-content: center;
     margin: 1rem 0;
   }
+
+  .request-indicator {
+    background: rgba(255,255,255,.7);
+    top: calc(-1 * (3em + .3em));
+    color: @info-color;
+    left: 50%;
+    margin-left: auto;
+    margin-right: auto;
+    opacity: .7;
+    padding: .3em;
+    position: fixed;
+    transition: top 500ms ease-in-out;
+    transform: translateX(-50%);
+    z-index: 1200;
+    &.open {
+      top: 0;
+    }
+    // &:before {
+    //   .circle-spinner(1em, 2px, @info-color);
+    //   content: ' ';
+    //   display: inline-block;
+    //   line-height: 1.1em;
+    // }
+  }
+
 }
diff --git a/ambari-logsearch-web/src/app/components/app.component.ts b/ambari-logsearch-web/src/app/components/app.component.ts
index 68d220e..1d6e29e 100644
--- a/ambari-logsearch-web/src/app/components/app.component.ts
+++ b/ambari-logsearch-web/src/app/components/app.component.ts
@@ -29,7 +29,9 @@ import { notificationIcons } from '@modules/shared/services/notification.service
 import { Store } from '@ngrx/store';
 import { AppStore } from '@app/classes/models/store';
 import { AuthorizationStatuses } from '@app/store/reducers/auth.reducers';
-import { isAuthorizedSelector, authStatusSelector, isCheckingAuthStatusInProgressSelector } from '@app/store/selectors/auth.selectors';
+import { isAuthorizedSelector, selectAuthStatus, isCheckingAuthStatusInProgressSelector } from '@app/store/selectors/auth.selectors';
+
+import { HttpClientService } from '@app/services/http-client.service';
 
 @Component({
   selector: 'app-root',
@@ -44,7 +46,7 @@ export class AppComponent implements OnInit, OnDestroy {
   authorizationStatuses = AuthorizationStatuses;
 
   isAuthorized$: Observable<boolean> = this.store.select(isAuthorizedSelector);
-  authorizationStatus$: Observable<AuthorizationStatuses> = this.store.select(authStatusSelector);
+  authorizationStatus$: Observable<AuthorizationStatuses> = this.store.select(selectAuthStatus);
   isCheckingAuthStatusInProgress$: Observable<boolean> = this.store.select(isCheckingAuthStatusInProgressSelector);
   authorizationCode$: Observable<number> = this.appState.getParameter('authorizationCode');
   isBaseDataAvailable$: Observable<boolean> = this.appState.getParameter('baseDataSetState')
@@ -66,6 +68,7 @@ export class AppComponent implements OnInit, OnDestroy {
 
   constructor(
     private appState: AppStateService,
+    public httpClient: HttpClientService,
     private store: Store<AppStore>
   ) {}
 
diff --git a/ambari-logsearch-web/src/app/components/cluster-filter/cluster-filter.component.ts b/ambari-logsearch-web/src/app/components/cluster-filter/cluster-filter.component.ts
index 086160b..391d117 100644
--- a/ambari-logsearch-web/src/app/components/cluster-filter/cluster-filter.component.ts
+++ b/ambari-logsearch-web/src/app/components/cluster-filter/cluster-filter.component.ts
@@ -128,10 +128,10 @@ export class ClusterFilterComponent implements OnInit, OnDestroy {
         .filter((state: DataAvailabilityValues) => state === DataAvailabilityValues.AVAILABLE)
         .first()
         .subscribe(() => {
-          this.filterDropdown.updateSelection(clusterSelection);
+          this.filterDropdown.writeValue(clusterSelection);
         });
     } else {
-      this.filterDropdown.updateSelection(null);
+      this.filterDropdown.clearSelection();
     }
   }
 
diff --git a/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.ts b/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.ts
index af14925..d6f24e5 100644
--- a/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.ts
+++ b/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.ts
@@ -19,7 +19,6 @@
 import {Component, forwardRef} from '@angular/core';
 import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
 import {ListItem} from '@app/classes/list-item';
-import {UtilsService} from '@app/services/utils.service';
 import {MenuButtonComponent} from '@app/components/menu-button/menu-button.component';
 
 @Component({
@@ -36,65 +35,23 @@ import {MenuButtonComponent} from '@app/components/menu-button/menu-button.compo
 })
 export class FilterButtonComponent extends MenuButtonComponent implements ControlValueAccessor {
 
-  private selectedItems: ListItem[] = [];
-
   private onChange: (fn: any) => void;
 
-  constructor(private utils: UtilsService) {
-    super();
-  }
-
-  get selection(): ListItem[] {
-    return this.selectedItems;
-  }
-
-  set selection(items: ListItem[]) {
-    this.selectedItems = items;
-    if (this.onChange) {
-      this.onChange(items);
+  updateSelection(items: ListItem | ListItem[], callOnChange = true): void {
+    super.updateSelection(items);
+    if (callOnChange) {
+      this._onChange(this.selection);
     }
   }
 
-  updateSelection(updates: ListItem | ListItem[]): void {
-    if (updates && (!Array.isArray(updates) || updates.length)) {
-      const items: ListItem[] = Array.isArray(updates) ? updates : [updates];
-      if (this.isMultipleChoice) {
-        items.forEach((item: ListItem) => {
-          if (this.subItems && this.subItems.length) {
-            const itemToUpdate: ListItem = this.subItems.find((option: ListItem) => this.utils.isEqual(option.value, item.value));
-            if (itemToUpdate) {
-              itemToUpdate.isChecked = item.isChecked;
-            }
-          }
-        });
-      } else {
-        const selectedItem: ListItem = items.find((item: ListItem) => item.isChecked);
-        this.subItems.forEach((item: ListItem) => {
-          item.isChecked = !!selectedItem && this.utils.isEqual(item.value, selectedItem.value);
-        });
-      }
-    } else {
-      this.subItems.forEach((item: ListItem) => item.isChecked = false);
-    }
-    const checkedItems = this.subItems.filter((option: ListItem): boolean => option.isChecked);
-    this.selection = checkedItems;
-    this.selectItem.emit(checkedItems.map((option: ListItem): any => option.value));
-    if (this.dropdownList) {
-      this.dropdownList.doItemsCheck();
+  private _onChange(value) {
+    if (this.onChange) {
+      this.onChange(value);
     }
   }
 
   writeValue(items: ListItem[]) {
-    let listItems: ListItem[] = [];
-    if (items && items.length) {
-      listItems = items.map((item: ListItem) => {
-        return {
-          ...item,
-          isChecked: true
-        };
-      });
-    }
-    this.updateSelection(listItems);
+    this.selection = items;
   }
 
   registerOnChange(callback: any): void {
diff --git a/ambari-logsearch-web/src/app/components/filter-history-manager/filter-history-manager.component.html b/ambari-logsearch-web/src/app/components/filter-history-manager/filter-history-manager.component.html
new file mode 100644
index 0000000..c34a46e
--- /dev/null
+++ b/ambari-logsearch-web/src/app/components/filter-history-manager/filter-history-manager.component.html
@@ -0,0 +1,29 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You 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.
+-->
+<menu-button label="{{'topMenu.undo' | translate}}" [subItems]="activeUndoHistoryListItems$ | async" iconClass="fa fa-arrow-left"
+  class="history-menu" [class.disabled]="!(hasActiveUndoHistoryItems$ | async)" [isDisabled]="!(hasActiveUndoHistoryItems$ | async)"
+  listClass="history-dropdown" [isRightAlign]="true" (buttonClick)="undo()" (selectItem)="onListItemClick($event)">
+</menu-button>
+
+<menu-button label="{{'topMenu.redo' | translate}}" [subItems]="activeRedoHistoryListItems$ | async" iconClass="fa fa-arrow-right"
+  class="history-menu" [class.disabled]="!(hasActiveRedoHistoryItems$ | async)" [isDisabled]="!(hasActiveRedoHistoryItems$ | async)"
+  listClass="history-dropdown" [isRightAlign]="true" (buttonClick)="redo()" (selectItem)="onListItemClick($event)">
+</menu-button>
+
+<menu-button label="{{'topMenu.history' | translate}}" [subItems]="activeHistoryListItems$ | async" iconClass="fa fa-history"
+  class="history-menu" [class.disabled]="!(hasActiveHistoryItems$ | async)" [isDisabled]="!(hasActiveHistoryItems$ | async)"
+  listClass="history-dropdown" [isRightAlign]="true" (selectItem)="onListItemClick($event)"></menu-button>
diff --git a/ambari-logsearch-web/src/app/components/filter-history-manager/filter-history-manager.component.less b/ambari-logsearch-web/src/app/components/filter-history-manager/filter-history-manager.component.less
new file mode 100644
index 0000000..1a0cd46
--- /dev/null
+++ b/ambari-logsearch-web/src/app/components/filter-history-manager/filter-history-manager.component.less
@@ -0,0 +1,114 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+ @import '../../modules/shared/variables';
+
+ @current-history-item-hover-color: @unknown-color;
+ @current-history-item-highlight-color: @form-success-color;
+
+ :host {
+  /deep/ menu-button .item-label-text {
+    display: inline-block;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    width: 100%;
+    &:first-letter {
+      text-transform: uppercase;
+    }
+  }
+
+  .history-menu {
+    /deep/ ul {
+      li:not(.selection-all) {
+        margin: 0;
+        overflow: hidden;
+        position: relative;
+        transition: background-color 300ms ease-in, opacity 300ms ease-in, height 100ms 400ms ease-in;
+        &:before {
+          border-left: 1px solid darken(@unknown-color, 25%);
+          bottom: 0;
+          content: "";
+          display: block;
+          left: 12px;
+          position: absolute;
+          top: 0;
+        }
+        &:after {
+          background: #fff;
+          border: 1px solid darken(@unknown-color, 25%);
+          border-radius: 100%;
+          content: "";
+          height: 12px;
+          left: 7px;
+          position: absolute;
+          top: 6px;
+          transition: background-color 300ms;
+          width: 12px;
+        }
+
+        .list-item-label.label-container {
+          border-radius: 3px;
+          display: flex;
+          margin: 0 3px 0 25px;
+          padding: 3px 25px 3px 1em;
+          .item-label-text {
+            flex-grow: 1;
+            padding-right: 1em;
+          }
+          /deep/ history-item-controls {
+            float: none;
+            justify-self: right;
+          }
+        }
+
+        &.active > a, &:hover {
+          color: #262626;
+          text-decoration: none;
+          background-color: transparent;
+          .list-item-label.label-container {
+            background-color: #f5f5f5;
+          }
+        }
+        &.initial {
+          color: @unknown-color;
+        }
+      }
+      li:not(.selection-all):first-child {
+        &:before {
+          top: 50%;
+        }
+      }
+      li:not(.selection-all):last-child {
+        &:before {
+          bottom: 50%;
+        }
+      }
+      li:not(.selection-all):hover {
+        &:after {
+          background: @current-history-item-hover-color;
+        }
+      }
+      li:not(.selection-all).active {
+        &:after {
+          background: @current-history-item-highlight-color;
+        }
+      }
+    }
+  }
+
+ }
diff --git a/ambari-logsearch-web/src/app/components/filter-history-manager/filter-history-manager.component.spec.ts b/ambari-logsearch-web/src/app/components/filter-history-manager/filter-history-manager.component.spec.ts
new file mode 100644
index 0000000..2f2e738
--- /dev/null
+++ b/ambari-logsearch-web/src/app/components/filter-history-manager/filter-history-manager.component.spec.ts
@@ -0,0 +1,296 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import {
+  async,
+  ComponentFixture,
+  TestBed,
+  fakeAsync
+} from '@angular/core/testing';
+
+import {
+  getCommonTestingBedConfiguration,
+  TranslationModules
+} from '@app/test-config.spec';
+import { StoreModule } from '@ngrx/store';
+
+import {
+  appState,
+  AppStateService
+} from '@app/services/storage/app-state.service';
+import { NotificationService } from '@modules/shared/services/notification.service';
+import { NotificationsService } from 'angular2-notifications/src/notifications.service';
+import { LogsFilteringUtilsService } from '@app/services/logs-filtering-utils.service';
+
+import { FilterHistoryManagerComponent } from './filter-history-manager.component';
+import { FilterUrlParamChange } from '@app/classes/models/filter-url-param-change.interface';
+import { Router, Routes, NavigationEnd } from '@angular/router';
+import { RouterTestingModule } from '@angular/router/testing';
+
+describe('FilterHistoryManagerComponent', () => {
+  let component: FilterHistoryManagerComponent;
+  let fixture: ComponentFixture<FilterHistoryManagerComponent>;
+  let router: Router;
+  const getValueLabelTestCases = {
+    level: [{
+      input: 'ERROR',
+      expectation: 'Error'
+    }, {
+      input: 'ERROR,FATAL',
+      expectation: 'Error, Fatal'
+    }],
+    levels: [{
+      input: 'ERROR',
+      expectation: 'Error'
+    }, {
+      input: 'ERROR,FATAL',
+      expectation: 'Error, Fatal'
+    }],
+    log_message: [{
+      input: 'Exception',
+      expectation: '"Exception"'
+    }],
+    type: [{
+      input: 'infra_solr',
+      expectation: 'Infra Solr'
+    }],
+    components: [{
+      input: 'infra_solr',
+      expectation: 'Infra Solr'
+    }]
+  };
+  const componentNameLabes = {
+    infra_solr: 'Infra Solr'
+  };
+
+  const getParametersFromUrlTestCases = {
+    'logs/serviceLogs': {},
+    'logs/serviceLogs;a=1;b=2': {a: '1', b: '2'},
+    'logs/serviceLogs;a=1;b=2?c=3': {a: '1', b: '2'}
+  };
+
+  const getParameterDifferencesFromUrlsTestCases = [{
+    previousUrl: 'logs/serviceLogs;a=1',
+    currentUrl: 'logs/serviceLogs;a=1;b=2',
+    expectation: [{
+      type: 'add',
+      name: 'b',
+      from: undefined,
+      to: '2'
+    }]
+  }, {
+    previousUrl: 'logs/serviceLogs;a=1;b=2',
+    currentUrl: 'logs/serviceLogs;a=1',
+    expectation: [{
+      type: 'remove',
+      name: 'b',
+      from: '2',
+      to: undefined
+    }]
+  }, {
+    previousUrl: 'logs/serviceLogs;a=1',
+    currentUrl: 'logs/serviceLogs;a=2',
+    expectation: [{
+      type: 'change',
+      name: 'a',
+      from: '1',
+      to: '2'
+    }]
+  }];
+
+  const extractParametersFromUrlSegmentGroupUseCases = [{
+    caseLabel: 'Single level parameters',
+    url: 'logs/serviceLogs;a=1;b=2',
+    expectation: {
+      a: '1',
+      b: '2'
+    }
+  }, {
+    caseLabel: 'Multi level parameters',
+    url: 'logs;a=1/serviceLogs;b=2',
+    expectation: {
+      a: '1',
+      b: '2'
+    }
+  }];
+
+  const routes: Routes = [
+    {
+      path: 'logs/:activeTab',
+      component: FilterHistoryManagerComponent
+    }
+  ];
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      ...getCommonTestingBedConfiguration({
+        imports: [
+          RouterTestingModule.withRoutes(routes),
+          ...TranslationModules,
+          StoreModule.provideStore({
+            appState
+          })
+        ],
+        providers: [
+          AppStateService,
+          NotificationsService,
+          NotificationService,
+          LogsFilteringUtilsService
+        ],
+        declarations: [FilterHistoryManagerComponent]
+      }),
+      schemas: [CUSTOM_ELEMENTS_SCHEMA]
+    }).compileComponents();
+  }));
+
+  beforeEach(() => {
+    router = TestBed.get(Router);
+    fixture = TestBed.createComponent(FilterHistoryManagerComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  it('should call the router`s `navigateByUrl` method when calling `undo` with a FilterUrlParamChange item', () => {
+    const path = '/logs/serviceLogs;_fhi=0';
+    const historyItem: FilterUrlParamChange = {
+      currentPath: path
+    };
+    spyOn(router, 'navigateByUrl').and.callThrough();
+    component.undo(historyItem);
+    expect(router.navigateByUrl).toHaveBeenCalledWith(path);
+  });
+
+  it('should navigate to the currentPath when calling `undo` with a FilterUrlParamChange item', fakeAsync(() => {
+    const path = '/logs/serviceLogs;_fhi=0';
+    const historyItem: FilterUrlParamChange = {
+      currentPath: path
+    };
+    router.events.filter(event => event instanceof NavigationEnd).first().subscribe((event) => {
+      expect(router.url).toEqual(path);
+    });
+    component.undo(historyItem);
+  }));
+
+  it('should call the router`s `navigateByUrl` method when calling `redo` with a FilterUrlParamChange item', () => {
+    const path = '/logs/serviceLogs;_fhi=0';
+    const historyItem: FilterUrlParamChange = {
+      currentPath: path
+    };
+    spyOn(router, 'navigateByUrl').and.callThrough();
+    component.redo(historyItem);
+    expect(router.navigateByUrl).toHaveBeenCalledWith(path);
+  });
+
+  it('should navigate to the currentPath when calling `redo` with a FilterUrlParamChange item', fakeAsync(() => {
+    const path = '/logs/serviceLogs;_fhi=0';
+    const historyItem: FilterUrlParamChange = {
+      currentPath: path
+    };
+    router.events.filter(event => event instanceof NavigationEnd).first().subscribe((event) => {
+      expect(router.url).toEqual(path);
+    });
+    component.redo(historyItem);
+  }));
+
+  it(
+    'should call the router`s `navigateByUrl` method when calling `navigateToFilterUrlParamChangeItem with a FilterUrlParamChange item',
+    () => {
+    const path = '/logs/serviceLogs;_fhi=0';
+    const historyItem: FilterUrlParamChange = {
+      currentPath: path
+    };
+    spyOn(router, 'navigateByUrl').and.callThrough();
+    component.navigateToFilterUrlParamChangeItem(historyItem);
+    expect(router.navigateByUrl).toHaveBeenCalledWith(path);
+  });
+
+  it(
+    'should navigate to the currentPath when calling `navigateToFilterUrlParamChangeItem` with a FilterUrlParamChange item',
+    fakeAsync(() => {
+    const path = '/logs/serviceLogs;_fhi=0';
+    const historyItem: FilterUrlParamChange = {
+      currentPath: path
+    };
+    router.events.filter(event => event instanceof NavigationEnd).first().subscribe((event) => {
+      expect(router.url).toEqual(path);
+    });
+    component.navigateToFilterUrlParamChangeItem(historyItem);
+  }));
+
+  describe('testing `getValueLabel`', () => {
+    Object.keys(getValueLabelTestCases).forEach((key) => {
+      const cases: {input: any, expectation: any}[] = getValueLabelTestCases[key];
+      cases.forEach((currentCase: {input: any, expectation: any}) => {
+        it(`should give correct value label for ${key} field when the value is ${currentCase.input}`, () => {
+          component.componentsLabelsLocalCopy$.next(componentNameLabes);
+          const valueLabel = component.getValueLabel(key, currentCase.input);
+          expect(valueLabel).toEqual(currentCase.expectation);
+        });
+      });
+    });
+  });
+
+  describe('testing `getParametersFromUrl`', () => {
+    Object.keys(getParametersFromUrlTestCases).forEach((url: string) => {
+      const expectation = getParametersFromUrlTestCases[url];
+      Object.keys(expectation).forEach((paramKey) => {
+        it(`should parse parameter ${paramKey} with value ${expectation[paramKey]}`, () => {
+          const result = component.getParametersFromUrl(url);
+          expect(result[paramKey]).toEqual(expectation[paramKey]);
+        });
+      });
+    });
+  });
+
+  describe('testing `getParameterDifferencesFromUrls', () => {
+    getParameterDifferencesFromUrlsTestCases.forEach((useCase) => {
+      describe(`should return with correct diff for ${useCase.previousUrl} vs ${useCase.currentUrl}`, () => {
+        const expectation = useCase.expectation;
+        expectation.forEach((expectationDiff) => {
+          it(`should find difference ${expectationDiff.type} - ${expectationDiff.from} -> ${expectationDiff.to}`, fakeAsync(() => {
+            const diff = component.getParameterDifferencesFromUrls(useCase.currentUrl, useCase.previousUrl, 'serviceLogs');
+            const found = diff.some((foundDiff) => (
+              foundDiff.type === expectationDiff.type
+              && foundDiff.from === expectationDiff.from
+              && foundDiff.to === expectationDiff.to
+              && foundDiff.name === expectationDiff.name
+            ));
+            expect(found).toEqual(true);
+          }));
+        });
+      });
+    });
+  });
+
+  describe('testing `extractParametersFromUrlSegmentGroup`', () => {
+    extractParametersFromUrlSegmentGroupUseCases.forEach((useCase) => {
+      it(`should find parameters for ${useCase.caseLabel}`, () => {
+        const urlSegmentGroup = router.parseUrl(useCase.url);
+        const foundParameters = component.extractParametersFromUrlSegmentGroup(urlSegmentGroup.root);
+        Object.keys(useCase.expectation).forEach((paramKey) => {
+          expect(foundParameters[paramKey]).toEqual(useCase.expectation[paramKey]);
+        });
+      });
+    });
+  });
+
+});
diff --git a/ambari-logsearch-web/src/app/components/filter-history-manager/filter-history-manager.component.ts b/ambari-logsearch-web/src/app/components/filter-history-manager/filter-history-manager.component.ts
new file mode 100644
index 0000000..ddf0df5
--- /dev/null
+++ b/ambari-logsearch-web/src/app/components/filter-history-manager/filter-history-manager.component.ts
@@ -0,0 +1,381 @@
+import { Component, OnInit, OnDestroy, Input } from '@angular/core';
+import { Subject } from 'rxjs/Subject';
+import { Observable } from 'rxjs/Observable';
+import { Store } from '@ngrx/store';
+import { AppStore } from '@app/classes/models/store';
+import {
+  selectActiveFilterHistoryChangesUndoItems,
+  selectActiveFilterHistoryChangesRedoItems,
+  selectActiveFilterHistoryChanges,
+  selectActiveFilterHistoryChangeIndex
+} from '@app/store/selectors/filter-history.selectors';
+import { FilterUrlParamChange } from '@app/classes/models/filter-url-param-change.interface';
+import { Router, UrlTree, UrlSegmentGroup } from '@angular/router';
+import {
+  LogsFilteringUtilsService,
+  defaultUrlParamsForFiltersByLogsType,
+  UrlParamDifferences,
+  UrlParamsDifferenceType
+} from '@app/services/logs-filtering-utils.service';
+import { selectActiveLogsType } from '@app/store/selectors/app-state.selectors';
+import { LogsType } from '@app/classes/string';
+import { TranslateService } from '@ngx-translate/core';
+import { selectComponentsLabels } from '@app/store/selectors/components.selectors';
+import { selectDefaultAuditLogsFields } from '@app/store/selectors/audit-logs-fields.selectors';
+import { selectServiceLogsFieldState } from '@app/store/selectors/service-logs-fields.selectors';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { ListItem } from '@app/classes/list-item';
+
+import * as moment from 'moment';
+import { SearchBoxParameter } from '@app/classes/filtering';
+import { LogField } from '@app/classes/object';
+
+export const urlParamsActionType = {
+  clusters: 'multiple',
+  timeRange: 'single',
+  components: 'multiple',
+  levels: 'multiple',
+  hosts: 'multiple',
+  sortingKey: 'single',
+  sortingType: 'single',
+  pageSize: 'single',
+  page: 'single',
+  query: 'query',
+  users: 'multiple'
+};
+
+@Component({
+  selector: 'filter-history-manager',
+  templateUrl: './filter-history-manager.component.html',
+  styleUrls: ['./filter-history-manager.component.less']
+})
+export class FilterHistoryManagerComponent implements OnInit, OnDestroy {
+
+  @Input()
+  labelSeparator = ' | ';
+
+  activeLogsType$: Observable<LogsType> = this.store.select(selectActiveLogsType);
+
+  componentsLabels$: Observable<{[key: string]: string}> = this.store.select(selectComponentsLabels);
+  componentsLabelsLocalCopy$: BehaviorSubject<{[key: string]: string}> = new BehaviorSubject({});
+
+  activeHistoryChangeIndex$: Observable<number> = this.store.select(selectActiveFilterHistoryChangeIndex);
+
+  activeHistoryItems$: Observable<FilterUrlParamChange[]> = this.store.select(selectActiveFilterHistoryChanges);
+  hasActiveHistoryItems$: Observable<boolean> = this.activeHistoryItems$
+    .map(items => items && items.length > 0).startWith(false);
+  activeHistoryItemLabels$: Observable<{[key: string]: string}[]> = Observable.combineLatest(
+    this.activeHistoryItems$,
+    this.activeLogsType$,
+    this.componentsLabels$ // this is just to recalculate the labels when the components arrived
+  ).map(result => this.mapHistoryItemsToHistoryItemLabels(result));
+  activeHistoryListItems$: Observable<ListItem[]> = Observable.combineLatest(
+    this.activeHistoryItemLabels$.map((items) => this.mapHistoryItemLabelsToListItems(items, this.labelSeparator)),
+    this.store.select(selectActiveFilterHistoryChangeIndex)
+  ).map(([listItems, changeIndex]: [ListItem[], number]): ListItem[] => listItems.map((item, index) => {
+    item.cssClass = index === changeIndex ? 'active' : (index === 0 ? 'initial' : '');
+    return item;
+  }));
+
+  activeUndoHistoryItems$: Observable<FilterUrlParamChange[]> = this.store.select(selectActiveFilterHistoryChangesUndoItems);
+  hasActiveUndoHistoryItems$: Observable<boolean> = this.activeUndoHistoryItems$
+    .map(items => items && items.length > 0).startWith(false);
+  activeUndoHistoryListItems$: Observable<ListItem[]> = Observable.combineLatest(
+    this.activeHistoryListItems$,
+    this.store.select(selectActiveFilterHistoryChangeIndex)
+  ).map(([listItems, activeChangeIndex]: [ListItem[], number]): ListItem[] => listItems.slice(0, activeChangeIndex).reverse());
+
+  activeRedoHistoryItems$: Observable<FilterUrlParamChange[]> = this.store.select(selectActiveFilterHistoryChangesRedoItems);
+  hasActiveRedoHistoryItems$: Observable<boolean> = this.activeRedoHistoryItems$
+    .map(items => items && items.length > 0).startWith(false);
+  activeRedoHistoryListItems$: Observable<ListItem[]> = Observable.combineLatest(
+    this.activeHistoryListItems$,
+    this.store.select(selectActiveFilterHistoryChangeIndex)
+  ).map(([listItems, activeChangeIndex]: [ListItem[], number]): ListItem[] => listItems.slice(activeChangeIndex + 1));
+
+  activeQueryFieldsLabels$: Observable<{[key: string]: string}> = Observable.combineLatest(
+    this.store.select(selectServiceLogsFieldState),
+    this.store.select(selectDefaultAuditLogsFields),
+    this.activeLogsType$
+  ).map(
+    (
+      [serviceLogsFields, auditLogsFields, activeLogsType]: [LogField[], LogField[], LogsType]
+    ) => activeLogsType === 'serviceLogs' ? serviceLogsFields : auditLogsFields
+  ).map(
+    (fields: LogField[]) => fields ? fields.reduce(
+      (fieldLabels: {[key: string]: string}, field: LogField): {[key: string]: string} => ({
+        ...fieldLabels,
+        [field.name]: field.label || field.name
+      }),
+      {}
+    ) : []
+  );
+  activeQueryFieldsLocalCopy$: BehaviorSubject<{[key: string]: string}> = new BehaviorSubject({});
+
+  destroyed$ = new Subject();
+
+  constructor(
+    private store: Store<AppStore>,
+    private router: Router,
+    private logsFilteringUtilsService: LogsFilteringUtilsService,
+    private translateService: TranslateService
+  ) { }
+
+  ngOnInit() {
+    this.componentsLabels$.takeUntil(this.destroyed$).subscribe(componentsLabels => this.componentsLabelsLocalCopy$.next(componentsLabels));
+    this.activeQueryFieldsLabels$.takeUntil(this.destroyed$).subscribe(fieldLabels => this.activeQueryFieldsLocalCopy$.next(fieldLabels));
+
+    this.activeHistoryItemLabels$.takeUntil(this.destroyed$).map((historyLabels) => {
+      return historyLabels.map((historyLabel) => {
+        return {
+          value: historyLabel.url,
+          label: Object.keys(historyLabel.labels).map((url) => historyLabel.labels[url]).join(this.labelSeparator)
+        };
+      });
+    });
+  }
+
+  ngOnDestroy() {
+    this.destroyed$.next(true);
+  }
+
+  onListItemClick(item: ListItem) {
+    this.navigateToFilterUrlParamChangeItem({
+      currentPath: item.value
+    });
+  }
+
+  navigateToFilterUrlParamChangeItem = (item: FilterUrlParamChange) => {
+    if (item) {
+      this.router.navigateByUrl(item.currentPath);
+    }
+  }
+
+  undo(item?: FilterUrlParamChange): void {
+    ( item ?
+      Observable.of(item)
+      : this.activeUndoHistoryItems$.map((changes: FilterUrlParamChange[]) => changes[changes.length - 1])
+    ).first().subscribe(this.navigateToFilterUrlParamChangeItem);
+  }
+
+  redo(item?) {
+    ( item ?
+      Observable.of(item)
+      : this.activeRedoHistoryItems$.map((changes: FilterUrlParamChange[]) => changes[0])
+    ).first().subscribe(this.navigateToFilterUrlParamChangeItem);
+  }
+
+  getValueLabel(paramName, value) {
+    switch (paramName) {
+      case 'level':
+      case 'levels': {
+        return value.toLowerCase().split(',').map(level => level[0].toUpperCase() + level.slice(1)).join(', ');
+      }
+      case 'log_message': {
+        return `"${value}"`;
+      }
+      case 'type': // query
+      case 'components': {
+        const componentLabels = this.componentsLabelsLocalCopy$.getValue();
+        return value.split(/,/g).map((component) => `${componentLabels[component] || component}`).join(', ');
+      }
+      default: {
+        return value;
+      }
+    }
+  }
+
+  private _getMultipleUrlParamDifferenceLabel(difference: UrlParamDifferences): string {
+
+    const fieldLabelTranslateKey: string =  /^timeRange/.test(difference.name) ? 'timeRange' : difference.name;
+    const fieldLabel: string = this.translateService.instant(`filterHistory.paramNames.${fieldLabelTranslateKey}`);
+
+    const actionLabelTranslateKey: UrlParamsDifferenceType = difference.to ? UrlParamsDifferenceType.CHANGE : UrlParamsDifferenceType.CLEAR;
+
+    const valueLabel = difference.to ? this.getValueLabel(fieldLabelTranslateKey, difference.to) : '';
+
+    return this.translateService.instant(`filterHistory.${urlParamsActionType[difference.name]}.changeLabel.${actionLabelTranslateKey}`, {
+      fieldLabel,
+      valueLabel
+    });
+  }
+
+  private _getTimeRangeUrlParamDifferenceLabel(
+    differences: UrlParamDifferences[],
+    parameters: {[key: string]: any},
+    dateTimeFormat: string
+  ): string | undefined {
+
+    let timeRangeTypeValue: string = parameters.timeRangeType.toLowerCase();
+
+    if (!timeRangeTypeValue) {
+      return undefined;
+    }
+
+    const timeRangeUnitValue: string = parameters.timeRangeUnit;
+
+    const timeRangeIntervalValue = parameters.timeRangeInterval;
+
+    let valueLabel: string;
+    const typeLabel = this.translateService.instant(`filterHistory.timeRange.type.${timeRangeTypeValue}`);
+    const unitLabel = this.translateService.instant(`filterHistory.timeRange.unit.${timeRangeUnitValue}`);
+    const fieldLabel = this.translateService.instant(`filterHistory.paramNames.timeRange`);
+
+    if (timeRangeTypeValue.toLowerCase() === 'custom') {
+      const timeRangeStart = differences.find(diff => diff.name === 'timeRangeStart');
+      const timeRangeStartValue: string = timeRangeStart && moment(timeRangeStart.to).format(dateTimeFormat);
+      const timeRangeEnd = differences.find(diff => diff.name === 'timeRangeEnd');
+      const timeRangeEndValue: string = timeRangeEnd && moment(timeRangeEnd.to).format(dateTimeFormat);
+      valueLabel = this.translateService.instant(`filterHistory.timeRange.valueLabel.${timeRangeTypeValue}`, {
+        valueStart: timeRangeStartValue,
+        valueEnd: timeRangeEndValue
+      });
+    } else {
+      if (timeRangeTypeValue.toLowerCase() === 'current' && timeRangeUnitValue === 'd') {
+        timeRangeTypeValue = 'today';
+      }
+      if (timeRangeTypeValue.toLowerCase() === 'past' && timeRangeUnitValue === 'd') {
+        timeRangeTypeValue = 'yesterday';
+      }
+      valueLabel = this.translateService.instant(`filterHistory.timeRange.valueLabel.${timeRangeTypeValue}`, {
+        typeLabel,
+        unitLabel: parseInt(timeRangeIntervalValue, 10) > 1 ? unitLabel[1] : unitLabel[0],
+        value: timeRangeIntervalValue || ''
+      });
+    }
+
+    return this.translateService.instant(`filterHistory.timeRange.changeLabel`, { valueLabel, fieldLabel });
+  }
+
+  private _getQueryUrlParamDifferenceLabel(query): string | undefined {
+    const fromQuery: SearchBoxParameter[] | null = query.from ? JSON.parse(query.from) : null;
+    const toQuery: SearchBoxParameter[] | null = query.to ? JSON.parse(query.to) : null;
+    let addedQueries, removedQueries;
+
+    if (fromQuery && !toQuery) {
+      return this.translateService.instant(`filterHistory.query.changeLabel.clear`);
+    } else if (!fromQuery && toQuery) {
+      addedQueries = toQuery;
+    } else {
+      addedQueries = toQuery.filter((queryItem) => fromQuery.findIndex((fromQueryItem) => (
+        fromQueryItem.name === queryItem.name && fromQueryItem.value === queryItem.value
+      )));
+      removedQueries = fromQuery.filter((queryItem) => toQuery.findIndex((toQueryItem) => (
+        toQueryItem.name === queryItem.name && toQueryItem.value === queryItem.value
+      )));
+    }
+
+    const addedLabels = addedQueries ? addedQueries.reduce((labels: string[], addQuery: SearchBoxParameter): string[] => {
+      const queryLabel = this.translateService.instant('filterHistory.query.changeLabel.add', {
+        queryType: this.translateService.instant(`filterHistory.query.type.${addQuery.isExclude ? 'exclude' : 'include'}`),
+        fieldLabel: this.activeQueryFieldsLocalCopy$.getValue()[addQuery.name] || addQuery.name,
+        valueLabel: this.getValueLabel(addQuery.name, addQuery.value)
+      });
+      return queryLabel ? [...labels, queryLabel] : labels;
+    }, []) : [];
+
+    const removedLabels = removedQueries ? removedQueries.reduce((labels: string[], removedQuery: SearchBoxParameter): string[] => {
+      const queryLabel = this.translateService.instant('filterHistory.query.changeLabel.remove', {
+        queryType: this.translateService.instant(`filterHistory.query.type.${removedQuery.isExclude ? 'exclude' : 'include'}`),
+        fieldLabel: this.activeQueryFieldsLocalCopy$.getValue()[removedQuery.name] || removedQuery.name,
+        valueLabel: this.getValueLabel(removedQuery.name, removedQuery.value)
+      });
+      return queryLabel ? [...labels, queryLabel] : labels;
+    }, []) : [];
+
+    return [...addedLabels, ...removedLabels].join(this.labelSeparator);
+  }
+
+  extractParametersFromUrlSegmentGroup(group: UrlSegmentGroup): {[key: string]: string} {
+    return {
+      ...group.segments.reduce((segmentParams, segment) => ({...segmentParams, ...segment.parameters}), {}),
+      ...Object.keys(group.children).reduce(
+        (segmentsParams, key): {[key: string]: string} => {
+          return {
+            ...segmentsParams,
+            ...this.extractParametersFromUrlSegmentGroup(group.children[key])
+          };
+        },
+        {}
+      )
+    };
+  }
+
+  getParametersFromUrl(url: string): {[key: string]: string} {
+    const urlTree: UrlTree = this.router.parseUrl(url);
+    return this.extractParametersFromUrlSegmentGroup(urlTree.root);
+  }
+
+  getParameterDifferencesFromUrls(currentPath: string, previousPath: string, logsType: LogsType): UrlParamDifferences[] {
+    const currentParameters = this.getParametersFromUrl(currentPath);
+    const previousParameters = previousPath ? this.getParametersFromUrl(previousPath) : {};
+    return this.logsFilteringUtilsService.getUrlParamsDifferences(
+      {
+        ...defaultUrlParamsForFiltersByLogsType[logsType],
+        ...previousParameters
+      },
+      {
+        ...defaultUrlParamsForFiltersByLogsType[logsType],
+        ...currentParameters
+      }
+    );
+  }
+
+  getHistoryItemChangeLabels(
+    item: FilterUrlParamChange,
+    logsType:  LogsType,
+    isInitial: boolean
+  ): {url: string, labels: {[key: string]: string}} {
+    if (isInitial) {
+      return {
+        url: item.currentPath,
+        labels: {
+          'initial': this.translateService.instant(`filterHistory.initialState`)
+        }
+      };
+    }
+    const parameterDifferences = this.getParameterDifferencesFromUrls(item.currentPath, item.previousPath, logsType);
+    const differenciesLabels = parameterDifferences.reduce((labels: {[key: string]: string}, change): {[key: string]: string} => {
+      const changeKey = /^timeRange/.test(change.name) ? 'timeRange' : change.name;
+      if (labels[changeKey] !== undefined || urlParamsActionType[changeKey] === undefined) {
+        return labels;
+      }
+      let changeLabel: string;
+      if (/^timeRange/.test(change.name)) { // create time range label
+        changeLabel = this._getTimeRangeUrlParamDifferenceLabel(
+          parameterDifferences,
+          this.getParametersFromUrl(item.currentPath),
+          this.translateService.instant(`filterHistory.timeRange.dateTimeFormat`)
+        );
+      } else if (change.name === 'query') { // create query label
+        changeLabel = this._getQueryUrlParamDifferenceLabel(change);
+      } else {
+        changeLabel = this._getMultipleUrlParamDifferenceLabel(change);
+      }
+      return changeLabel ? {
+        ...labels,
+        [changeKey]: changeLabel
+      } : labels;
+    }, {});
+    return {
+      url: item.currentPath,
+      labels: differenciesLabels
+    };
+  }
+
+  mapHistoryItemsToHistoryItemLabels(
+    [items, activeLogsType, components]: [FilterUrlParamChange[], LogsType, {[key: string]: string}]
+  ): {[key: string]: any}[] {
+    return (items || []).map((item, index): {[key: string]: any} => this.getHistoryItemChangeLabels(item, activeLogsType, index === 0));
+  }
+
+  mapHistoryItemLabelsToListItems(historyLabels, labelSeparator = this.labelSeparator) {
+    return historyLabels.map((historyLabel) => {
+      return {
+        value: historyLabel.url,
+        label: Object.keys(historyLabel.labels).map((url) => historyLabel.labels[url]).join(labelSeparator)
+      };
+    });
+  }
+
+}
diff --git a/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html b/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html
index 657d1ea..724b0a4 100644
--- a/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html
+++ b/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html
@@ -24,7 +24,7 @@
     <time-range-picker *ngIf="isFilterConditionDisplayed('timeRange')" formControlName="timeRange"
                        class="filter-input"></time-range-picker>
     <timezone-picker class="filter-input"></timezone-picker>
-    <button class="btn btn-success search-button" type="button" (click)="updateSearchBoxValue()">
+    <button class="btn btn-success search-button" type="button" (click)="onSearchBtnClick()">
       <span class="fa fa-search"></span>
     </button>
   </div>
diff --git a/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts b/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts
index df863a3..90fef77 100644
--- a/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts
+++ b/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-import {Component, OnDestroy, Input, ViewContainerRef, OnInit} from '@angular/core';
+import {Component, OnDestroy, Input, ViewContainerRef, OnInit, Output, EventEmitter} from '@angular/core';
 import {FormGroup} from '@angular/forms';
 import {Observable} from 'rxjs/Observable';
 import {Subject} from 'rxjs/Subject';
@@ -41,6 +41,12 @@ export class FiltersPanelComponent implements OnDestroy, OnInit {
   @Input()
   filtersForm: FormGroup;
 
+  @Output()
+  submit = new EventEmitter();
+
+  @Output()
+  clear = new EventEmitter();
+
   private subscriptions: Subscription[] = [];
 
   searchBoxItems$: Observable<ListItem[]>;
@@ -133,12 +139,18 @@ export class FiltersPanelComponent implements OnDestroy, OnInit {
     });
   }
 
-  private onClearBtnClick = (): void => {
+  onClearBtnClick = (): void => {
     const defaults = this.logsContainerService.isServiceLogsFileView ? {
-      components: this.logsContainerService.filtersForm.controls['components'].value,
-      hosts: this.logsContainerService.filtersForm.controls['hosts'].value
+      components: this.filtersForm.controls['components'].value,
+      hosts: this.filtersForm.controls['hosts'].value
     } : {};
     this.logsContainerService.resetFiltersForms(defaults);
+    this.clear.emit();
+  }
+
+  onSearchBtnClick(): void {
+    this.updateSearchBoxValue();
+    this.submit.emit(this.filtersForm.getRawValue());
   }
 
 }
diff --git a/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.ts b/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.ts
index 73c8604..d026419 100644
--- a/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.ts
+++ b/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.ts
@@ -192,7 +192,7 @@ export class LogIndexFilterComponent implements OnInit, OnDestroy, OnChanges, Co
 
   writeValue(filters: HomogeneousObject<LogIndexFilterComponentConfig[]>): void {
     this.configs = filters;
-    this.updateValue();
+    this.setCurrentConfig();
   }
 
   registerOnChange(callback: any): void {
diff --git a/ambari-logsearch-web/src/app/components/login-form/login-form.component.ts b/ambari-logsearch-web/src/app/components/login-form/login-form.component.ts
index 8d5070b..6365514 100644
--- a/ambari-logsearch-web/src/app/components/login-form/login-form.component.ts
+++ b/ambari-logsearch-web/src/app/components/login-form/login-form.component.ts
@@ -26,7 +26,7 @@ import { AppStore } from '@app/classes/models/store';
 import {
   isLoginInProgressSelector,
   isCheckingAuthStatusInProgressSelector,
-  authMessageSelector
+  selectAuthMessage
 } from '@app/store/selectors/auth.selectors';
 import { LogInAction } from '@app/store/actions/auth.actions';
 
@@ -45,7 +45,7 @@ export class LoginFormComponent {
 
   password: string;
 
-  authorizationMessage$: Observable<string> = this.store.select(authMessageSelector);
+  authorizationMessage$: Observable<string> = this.store.select(selectAuthMessage);
   isLoginInProgress$: Observable<boolean> = this.store.select(isLoginInProgressSelector);
   isCheckingAuthStatusInProgress$: Observable<boolean> = this.store.select(isCheckingAuthStatusInProgressSelector);
 
diff --git a/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.html b/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.html
index c319ca9..1add8a3 100644
--- a/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.html
+++ b/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.html
@@ -27,7 +27,7 @@
 </div>
 <div #container
   [ngClass]="{'container-fluid': true, 'logs-container': true, 'fixed-filterbar': isFilterPanelFixedPostioned}">
-  <filters-panel class="row" [filtersForm]="filtersForm" #filtersPanel></filters-panel>
+  <filters-panel class="row" [filtersForm]="filtersForm" #filtersPanel (clear)="onFilterPanelClear()"></filters-panel>
   <div class="row events-count">
     <div *ngIf="captureTimeRangeCache" class="panel panel-default panel-capture-view col-md-2 col-md-offset-5">
       <i class="fa fa-play"></i>
@@ -45,23 +45,23 @@
             (totalEventsFoundMessageParams.totalCount === 1 ? 'logs.oneEventFound' : 'logs.totalEventFound')) | translate: totalEventsFoundMessageParams}}</header>
         <time-histogram (selectArea)="setCustomTimeRange($event[0], $event[1])" [data]="serviceLogsHistogramData"
                         [colors]="serviceLogsHistogramColors" [allowFractionalYTicks]="false"
-                        svgId="service-logs-histogram"></time-histogram>
+                        svgId="service-logs-histogram" [class.loading]="logsContainerService.isGraphRequestInProgress$ | async"></time-histogram>
       </collapsible-panel>
       <service-logs-table [totalCount]="totalCount" [logs]="serviceLogs | async" [columns]="serviceLogsColumns | async"
-                          [filtersForm]="filtersForm"></service-logs-table>
+                          [filtersForm]="filtersForm" [class.loading]="logsContainerService.isLogsRequestInProgress$ | async"></service-logs-table>
     </ng-container>
     <ng-container *ngSwitchCase="'auditLogs'">
       <collapsible-panel commonTitle="logs.duration">
         <time-line-graph (selectArea)="setCustomTimeRange($event[0], $event[1])" [data]="auditLogsGraphData"
-                         [allowFractionalYTicks]="false" [skipZeroValuesInTooltip]="false"
-                         svgId="audit-logs-graph"></time-line-graph>
+          [allowFractionalYTicks]="false" [skipZeroValuesInTooltip]="false" svgId="audit-logs-graph"
+          [class.loading]="logsContainerService.isGraphRequestInProgress$ | async"></time-line-graph>
       </collapsible-panel>
       <audit-logs-entries [totalCount]="totalCount" [logs]="auditLogs | async" [columns]="auditLogsColumns | async"
-                          [filtersForm]="filtersForm"></audit-logs-entries>
+          [filtersForm]="filtersForm" [class.loading]="logsContainerService.isLogsRequestInProgress$ | async"></audit-logs-entries>
     </ng-container>
   </ng-container>
   <log-context *ngIf="isServiceLogContextView" [id]="activeLog.id" [hostName]="activeLog.host_name"
-               [componentName]="activeLog.component_name"></log-context>
+    [componentName]="activeLog.component_name"></log-context>
 </div>
 <modal-dialog
   title="{{'filter.capture' | translate}}"
diff --git a/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.less b/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.less
index ef61abe..f0f4765 100644
--- a/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.less
+++ b/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.less
@@ -18,6 +18,7 @@
 
 @import '../../modules/shared/mixins';
 @import '../../modules/shared/variables';
+@import '../../modules/shared/animations';
 
 :host {
   display: block;
@@ -96,4 +97,30 @@
     }
   }
 
+  /deep/ time-histogram,
+  /deep/ time-line-graph,
+  /deep/ service-logs-table {
+    display: block;
+    &.loading {
+      opacity: .8;
+      position: relative;
+      &:before {
+        .line-progress(1px);
+        content: ' ';
+        display: block;
+        opacity: .8;
+      }
+    }
+  }
+  /deep/ audit-logs-entries.loading > *:not(tabs) {
+    opacity: .8;
+    position: relative;
+    &:before {
+      .line-progress(1px);
+      content: ' ';
+      display: block;
+      opacity: .8;
+    }
+  }
+
 }
diff --git a/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts b/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts
index 34eb2a4..842218c 100644
--- a/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts
+++ b/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts
@@ -16,29 +16,28 @@
  * limitations under the License.
  */
 
-import {Component, OnInit, ElementRef, ViewChild, HostListener, Input, OnDestroy, ChangeDetectorRef} from '@angular/core';
-import {FormGroup} from '@angular/forms';
-import {Observable} from 'rxjs/Observable';
+import { Component, OnInit, ElementRef, ViewChild, Input, OnDestroy } from '@angular/core';
+import { FormGroup } from '@angular/forms';
+import { Observable } from 'rxjs/Observable';
 import 'rxjs/add/operator/debounceTime';
-import {LogsContainerService} from '@app/services/logs-container.service';
-import {ServiceLogsHistogramDataService} from '@app/services/storage/service-logs-histogram-data.service';
-import {AuditLogsGraphDataService} from '@app/services/storage/audit-logs-graph-data.service';
-import {AppStateService} from '@app/services/storage/app-state.service';
-import {TabsService} from '@app/services/storage/tabs.service';
-import {AuditLog} from '@app/classes/models/audit-log';
-import {ServiceLog} from '@app/classes/models/service-log';
-import {LogTypeTab} from '@app/classes/models/log-type-tab';
-import {BarGraph} from '@app/classes/models/bar-graph';
-import {ActiveServiceLogEntry} from '@app/classes/active-service-log-entry';
-import {ListItem} from '@app/classes/list-item';
-import {HomogeneousObject, LogLevelObject} from '@app/classes/object';
-import {LogsType, LogLevel} from '@app/classes/string';
-import {FiltersPanelComponent} from '@app/components/filters-panel/filters-panel.component';
-import {Subscription} from 'rxjs/Subscription';
-import {LogsFilteringUtilsService} from '@app/services/logs-filtering-utils.service';
-import {ActivatedRoute, Router} from '@angular/router';
-import {BehaviorSubject} from 'rxjs/BehaviorSubject';
-import {LogsStateService} from '@app/services/storage/logs-state.service';
+import { LogsContainerService } from '@app/services/logs-container.service';
+import { ServiceLogsHistogramDataService } from '@app/services/storage/service-logs-histogram-data.service';
+import { AuditLogsGraphDataService } from '@app/services/storage/audit-logs-graph-data.service';
+import { AppStateService } from '@app/services/storage/app-state.service';
+import { TabsService } from '@app/services/storage/tabs.service';
+import { AuditLog } from '@app/classes/models/audit-log';
+import { ServiceLog } from '@app/classes/models/service-log';
+import { LogTypeTab } from '@app/classes/models/log-type-tab';
+import { BarGraph } from '@app/classes/models/bar-graph';
+import { ActiveServiceLogEntry } from '@app/classes/active-service-log-entry';
+import { ListItem } from '@app/classes/list-item';
+import { HomogeneousObject, LogLevelObject } from '@app/classes/object';
+import { LogsType, LogLevel } from '@app/classes/string';
+import { FiltersPanelComponent } from '@app/components/filters-panel/filters-panel.component';
+import { Subscription } from 'rxjs/Subscription';
+import { LogsFilteringUtilsService } from '@app/services/logs-filtering-utils.service';
+import { ActivatedRoute, Router } from '@angular/router';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
 
 @Component({
   selector: 'logs-container',
@@ -92,13 +91,12 @@ export class LogsContainerComponent implements OnInit, OnDestroy {
   constructor(
     private appState: AppStateService,
     private tabsStorage: TabsService,
-    private logsContainerService: LogsContainerService,
+    public logsContainerService: LogsContainerService,
     private logsFilteringUtilsService: LogsFilteringUtilsService,
     private serviceLogsHistogramStorage: ServiceLogsHistogramDataService,
     private auditLogsGraphStorage: AuditLogsGraphDataService,
     private router: Router,
-    private activatedRoute: ActivatedRoute,
-    private logsStateService: LogsStateService
+    private activatedRoute: ActivatedRoute
   ) {}
 
   ngOnInit() {
@@ -143,7 +141,7 @@ export class LogsContainerComponent implements OnInit, OnDestroy {
     // Sync from form to params on form values change
     this.subscriptions.push(
       this.filtersForm.valueChanges
-        .filter(() => !this.logsContainerService.filtersFormSyncInProgress.getValue())
+        .filter(() => !this.logsContainerService.filtersFormSyncInProgress$.getValue())
         .subscribe(this.onFiltersFormChange)
     );
     //// SYNC BETWEEN PARAMS AND FORM END
@@ -262,13 +260,15 @@ export class LogsContainerComponent implements OnInit, OnDestroy {
    * @param filters
    */
   private syncFiltersToParams(filters): void {
-    const params = this.logsFilteringUtilsService.getParamsFromActiveFilter(
-      filters, this.logsContainerService.activeLogsType
-    );
-    this.paramsSyncStart(); // turn on the 'sync in progress' flag
-    this.router.navigate([params], { relativeTo: this.activatedRoute })
-      .then(this.paramsSyncStop, this.paramsSyncStop) // turn off the 'sync in progress' flag
-      .catch(this.paramsSyncStop); // turn off the 'sync in progress' flag
+    this.activatedRoute.params.first().subscribe((routeParams) => {
+      const params = this.logsFilteringUtilsService.getParamsFromActiveFilter(
+        filters, this.logsContainerService.activeLogsType
+      );
+      this.paramsSyncStart(); // turn on the 'sync in progress' flag
+      this.router.navigate([params], { relativeTo: this.activatedRoute })
+        .then(this.paramsSyncStop, this.paramsSyncStop) // turn off the 'sync in progress' flag
+        .catch(this.paramsSyncStop); // turn off the 'sync in progress' flag
+    });
   }
 
   /**
@@ -277,12 +277,9 @@ export class LogsContainerComponent implements OnInit, OnDestroy {
    * @param values {[key: string]: any} The new values for the filter form
    */
   private resetFiltersForm(values: {[key: string]: any}): void {
-    if (Object.keys(values).length) {
-      this.logsContainerService.resetFiltersForms({
-        ...this.logsFilteringUtilsService.defaultFilterSelections,
-        ...values
-      });
-    }
+    this.logsContainerService.resetFiltersForms({
+      ...values
+    });
   }
 
   /**
@@ -305,25 +302,49 @@ export class LogsContainerComponent implements OnInit, OnDestroy {
 
   private onParamsChange = (params: {[key: string]: any}) => {
     const {activeTab, ...filtersParams} = params;
-    this.tabsStorage.findInCollection((tab: LogTypeTab) => tab.id === params.activeTab)
+
+    if (activeTab !== this.activeTabId$.getValue()) { // tab change
+      this.tabsStorage.findInCollection((tab: LogTypeTab) => tab.id === params.activeTab)
       .first()
       .subscribe((tab) => {
         if (tab) {
-          const filtersFromParams: {[key: string]: any} = this.logsFilteringUtilsService.getFilterFromParams(
-            filtersParams,
-            tab.appState.activeLogsType
-          );
-          // we don't have to reset the form with the new values when there is tab changes
-          // because the onActiveTabIdChange will call the setActiveTabById on LogsContainerService
-          // which will reset the form to the tab's activeFilters prop.
-          // If we do reset wvery time then the form will be reseted twice with every tab changes... not a big deal anyway
-          if (this.activeTabId$.getValue() === activeTab) {
-            this.resetFiltersForm(filtersFromParams);
-          }
-          this.syncFilterToTabStore(filtersFromParams, activeTab);
           this.activeTabId$.next(activeTab);
         }
       });
+    } else { // filter change
+      const filtersFromParams: {[key: string]: any} = this.logsFilteringUtilsService.getFilterFromParams(
+        filtersParams,
+        this.logsContainerService.activeLogsType
+      );
+      const currentFormParams = this.logsFilteringUtilsService.getParamsFromActiveFilter(
+        this.filtersForm.value, this.logsContainerService.activeLogsType
+      );
+      const filtersFormControlNames = Object.keys(this.filtersForm.controls);
+      const hasChange = filtersFormControlNames.reduce(
+        (changed, key) => {
+          if (currentFormParams[key] === undefined && filtersParams[key] === undefined) {
+            return changed;
+          }
+          return (
+            changed
+            || (currentFormParams[key] === undefined && filtersParams[key] !== undefined)
+            || (currentFormParams[key] !== undefined && filtersParams[key] === undefined)
+            || currentFormParams[key].toString() !== filtersParams[key].toString()
+          );
+        },
+        false
+      );
+      if (hasChange) {
+        // we don't have to reset the form with the new values when there is tab changes
+        // because the onActiveTabIdChange will call the setActiveTabById on LogsContainerService
+        // which will reset the form to the tab's activeFilters prop.
+        // If we do reset wvery time then the form will be reseted twice with every tab changes... not a big deal anyway
+        if (this.activeTabId$.getValue() === activeTab) {
+          this.resetFiltersForm(filtersFromParams);
+        }
+        this.syncFilterToTabStore(filtersFromParams, activeTab);
+      }
+    }
   }
 
   //
@@ -381,4 +402,8 @@ export class LogsContainerComponent implements OnInit, OnDestroy {
     }
   }
 
+  onFilterPanelClear() {
+    this.syncFiltersToParams(this.filtersForm.getRawValue());
+  }
+
 }
diff --git a/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts b/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts
index 4e77db5..2691273 100644
--- a/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts
+++ b/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts
@@ -40,6 +40,7 @@ import {LogsContainerService} from '@app/services/logs-container.service';
 import {AuthService} from '@app/services/auth.service';
 
 import {MenuButtonComponent} from './menu-button.component';
+import { UtilsService } from '@app/services/utils.service';
 
 describe('MenuButtonComponent', () => {
   let component: MenuButtonComponent;
@@ -93,7 +94,8 @@ describe('MenuButtonComponent', () => {
           useValue: httpClient
         },
         LogsContainerService,
-        AuthService
+        AuthService,
+        UtilsService
       ],
       schemas: [NO_ERRORS_SCHEMA]
     })
diff --git a/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts b/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts
index 788494c..faf2165 100644
--- a/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts
+++ b/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts
@@ -19,6 +19,7 @@
 import {Component, Input, Output, ViewChild, ElementRef, EventEmitter} from '@angular/core';
 import {ListItem} from '@app/classes/list-item';
 import {DropdownListComponent} from '@modules/shared/components/dropdown-list/dropdown-list.component';
+import {UtilsService} from '@app/services/utils.service';
 
 @Component({
   selector: 'menu-button',
@@ -46,13 +47,13 @@ export class MenuButtonComponent {
   subItems?: ListItem[];
 
   @Input()
-  isMultipleChoice: boolean = false;
+  isMultipleChoice = false;
 
   @Input()
-  hideCaret: boolean = false;
+  hideCaret = false;
 
   @Input()
-  isRightAlign: boolean = false;
+  isRightAlign = false;
 
   @Input()
   additionalLabelComponentSetter?: string;
@@ -61,10 +62,10 @@ export class MenuButtonComponent {
   badge: string;
 
   @Input()
-  caretClass: string = 'fa-caret-down';
+  caretClass = 'fa-caret-down';
 
   @Input()
-  useDropDownLocalFilter: boolean = false;
+  useDropDownLocalFilter = false;
 
   /**
    * The minimum time to handle a mousedown as a longclick. Default is 500 ms (0.5sec)
@@ -72,7 +73,7 @@ export class MenuButtonComponent {
    * @type {number}
    */
   @Input()
-  minLongClickDelay: number = 500;
+  minLongClickDelay = 500;
 
   /**
    * The maximum milliseconds to wait for longclick ends. The default is 0 which means no upper limit.
@@ -80,13 +81,13 @@ export class MenuButtonComponent {
    * @type {number}
    */
   @Input()
-  maxLongClickDelay: number = 0;
+  maxLongClickDelay = 0;
 
   @Input()
-  isDisabled: boolean = false;
+  isDisabled = false;
 
   @Input()
-  listClass: string = '';
+  listClass = '';
 
   @Output()
   buttonClick: EventEmitter<void> = new EventEmitter();
@@ -104,7 +105,7 @@ export class MenuButtonComponent {
    * Indicates if the dropdown list is open or not. So that we use internal state to display or hide the dropdown.
    * @type {boolean}
    */
-  private dropdownIsOpen: boolean = false;
+  private dropdownIsOpen = false;
 
   get hasSubItems(): boolean {
     return Boolean(this.subItems && this.subItems.length);
@@ -114,6 +115,26 @@ export class MenuButtonComponent {
     return this.hasSubItems && !this.hideCaret;
   }
 
+  set selection(items: ListItem[] | null) {
+    const selectedItems = items ? (Array.isArray(items) ? items : [items]) : [];
+    this.subItems.forEach((subItem: ListItem) => {
+      const indexInSelection = this.findItemIndexInList(subItem, selectedItems);
+      subItem.isChecked = indexInSelection > -1;
+    });
+    this.refreshDropdownList();
+  }
+  get selection(): ListItem[] {
+    return this.subItems && this.subItems.filter((option: ListItem): boolean => option.isChecked);
+  }
+
+  constructor(private utils: UtilsService) {}
+
+  findItemIndexInList(item: ListItem, itemList: ListItem[] = this.subItems): number {
+    return itemList.findIndex((subItem) => (
+      item === subItem || this.utils.isEqual(item.value, subItem.value)
+    ));
+  }
+
   /**
    * Handling the click event on the component element.
    * Two goal:
@@ -214,7 +235,7 @@ export class MenuButtonComponent {
 
   /**
    * The main goal if this function is tho handle the item change event on the child dropdown list.
-   * Should update the value and close the dropdown if it is not multiple choice type.
+   * Should update the value and close the dropdown.
    * @param {ListItem} item The selected item(s) from the dropdown list.
    */
   onDropdownItemChange(item: ListItem | ListItem[]) {
@@ -224,11 +245,22 @@ export class MenuButtonComponent {
     }
   }
 
-  updateSelection(item: ListItem | ListItem[]) {
-    this.selectItem.emit(item);
+  refreshDropdownList() {
     if (this.dropdownList) {
       this.dropdownList.doItemsCheck();
     }
   }
 
+  updateSelection(item: ListItem | ListItem[]) {
+    const changes = Array.isArray(item) ? item : [item];
+    changes.forEach((change: ListItem): void => {
+      const subItemIndex = this.findItemIndexInList(change);
+      if (subItemIndex > -1) {
+        this.subItems[subItemIndex].isChecked = change.isChecked;
+      }
+    });
+    this.selectItem.emit(item);
+    this.refreshDropdownList();
+  }
+
 }
diff --git a/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.ts b/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.ts
index 5f85da7..b476bf3 100644
--- a/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.ts
+++ b/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.ts
@@ -54,9 +54,6 @@ export class PaginationControlsComponent implements ControlValueAccessor {
     if (this.isValidValue(newValue)) { // this is the last validation check
       this.currentPage = newValue;
       this.currentPageChange.emit(newValue);
-      if (this.onChange) {
-        this.onChange(newValue);
-      }
     } else {
       throw new Error(`Invalid value ${newValue}. The currentPage should be between 0 and ${this.pagesCount}.`);
     }
@@ -75,14 +72,14 @@ export class PaginationControlsComponent implements ControlValueAccessor {
    * The goal is to set the value to the first page... obviously to zero. It is just to have a centralized api for that.
    */
   setFirstPage(): void {
-    this.value = 0;
+    this._setValueByUserInput(0);
   }
 
   /**
    * The goal is to set the value to the last page which is the pagesCount property anyway.
    */
   setLastPage(): void {
-    this.value = this.pagesCount - 1;
+    this._setValueByUserInput(this.pagesCount - 1);
   }
 
   /**
@@ -91,7 +88,7 @@ export class PaginationControlsComponent implements ControlValueAccessor {
    */
   setPreviousPage(): number {
     if (this.hasPreviousPage()) {
-      this.value -= 1;
+      this._setValueByUserInput(this.value - 1);
     }
     return this.value;
   }
@@ -101,8 +98,8 @@ export class PaginationControlsComponent implements ControlValueAccessor {
    * @returns {number} The new value of the currentPage
    */
   setNextPage(): number {
-    if (this.hasNextPage()){
-      this.value += 1;
+    if (this.hasNextPage()) {
+      this._setValueByUserInput(this.value + 1);
     }
     return this.value;
   }
@@ -123,6 +120,17 @@ export class PaginationControlsComponent implements ControlValueAccessor {
     return this.pagesCount > 0 && this.value > 0;
   }
 
+  private _setValueByUserInput(value) {
+    this.value = value;
+    this._onChange(this.value);
+  }
+
+  private _onChange(value) {
+    if (this.onChange) {
+      this.onChange(value);
+    }
+  }
+
   writeValue(value: number) {
     this.value = value;
   }
diff --git a/ambari-logsearch-web/src/app/components/search-box/search-box.component.ts b/ambari-logsearch-web/src/app/components/search-box/search-box.component.ts
index 62835bb..da33f60 100644
--- a/ambari-logsearch-web/src/app/components/search-box/search-box.component.ts
+++ b/ambari-logsearch-web/src/app/components/search-box/search-box.component.ts
@@ -97,7 +97,7 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess
    * @type {boolean}
    */
   @Input()
-  updateValueImmediately: boolean = true;
+  updateValueImmediately = true;
 
   @ViewChild('parameterInput')
   parameterInputRef: ElementRef;
@@ -121,26 +121,22 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess
    */
   parameters: SearchBoxParameterProcessed[] = [];
 
-  private subscriptions: Subscription[] = [];
+  private onChange;
+
+  private destroyed$ = new Subject();
 
   constructor(private utils: UtilsService) {}
 
   ngOnInit(): void {
     this.parameterInput = this.parameterInputRef.nativeElement;
     this.valueInput = this.valueInputRef.nativeElement;
-    this.subscriptions.push(
-      this.parameterNameChangeSubject.subscribe(this.onParameterNameChange)
-    );
-    this.subscriptions.push(
-      this.parameterAddSubject.subscribe(this.onParameterAdd)
-    );
-    this.subscriptions.push(
-      this.updateValueSubject.subscribe(this.updateValue)
-    );
+    this.parameterNameChangeSubject.takeUntil(this.destroyed$).subscribe(this.onParameterNameChange);
+    this.parameterAddSubject.takeUntil(this.destroyed$).subscribe(this.onParameterAdd);
+    this.updateValueSubject.takeUntil(this.destroyed$).subscribe(this.updateValue);
   }
 
   ngOnDestroy(): void {
-    this.subscriptions.forEach((subscription: Subscription) => subscription.unsubscribe());
+    this.destroyed$.next(true);
   }
 
   /**
@@ -152,8 +148,6 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess
       this.itemsOptions[this.activeItem.value] : [];
   }
 
-  private onChange: (fn: any) => void;
-
   @HostListener('click')
   private onRootClick(): void {
     if (!this.isActive) {
@@ -310,7 +304,7 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess
   updateValue = (): void => {
     this.currentValue = '';
     if (this.onChange) {
-      this.onChange(this.parameters.slice());
+      this.onChange([...this.parameters]);
     }
   }
 
@@ -331,8 +325,9 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess
   }
 
   writeValue(parameters: SearchBoxParameterProcessed[] = []): void {
-    this.parameters = parameters.slice();
-    this.updateValueSubject.next();
+    this.currentValue = '';
+    this.parameters = [...parameters];
+    // this.updateValueSubject.next();
   }
 
   registerOnChange(callback: any): void {
diff --git a/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts b/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts
index 757b4a0..bfb6068 100644
--- a/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts
+++ b/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts
@@ -53,6 +53,12 @@ export enum ListLayout {
 export class ServiceLogsTableComponent extends LogsTableComponent implements AfterViewChecked, OnInit, OnDestroy {
 
   /**
+   * Extra css class which can be applied to the container element
+   */
+  @Input()
+  cssClass: string;
+
+  /**
    * The element reference is used to check if the table is broken or not.
    */
   @ViewChild('tableListEl', {
diff --git a/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.ts b/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.ts
index e4e146f..3d031b2 100644
--- a/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.ts
+++ b/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.ts
@@ -61,9 +61,6 @@ export class TimeRangePickerComponent implements ControlValueAccessor {
 
   set selection(newValue: TimeUnitListItem) {
     this.timeRange = newValue;
-    if (this.onChange) {
-      this.onChange(newValue);
-    }
     this.setEndTime(this.logsFilteringUtilsService.getEndTimeMomentFromTimeUnitListItem(newValue, this.logsContainer.timeZone));
     this.setStartTime(this.logsFilteringUtilsService.getStartTimeMomentFromTimeUnitListItem(
       newValue, this.endTime, this.logsContainer.timeZone
@@ -80,6 +77,7 @@ export class TimeRangePickerComponent implements ControlValueAccessor {
 
   setTimeRange(value: any, label: string): void {
     this.selection = {label, value};
+    this._onChange(this.selection);
   }
 
   setCustomTimeRange(): void {
@@ -91,6 +89,13 @@ export class TimeRangePickerComponent implements ControlValueAccessor {
         end: this.endTime
       }
     };
+    this._onChange(this.selection);
+  }
+
+  private _onChange(value: TimeUnitListItem): void {
+    if (this.onChange) {
+      this.onChange(value);
+    }
   }
 
   writeValue(selection: TimeUnitListItem): void {
diff --git a/ambari-logsearch-web/src/app/modules/app-load/app-load.module.ts b/ambari-logsearch-web/src/app/modules/app-load/app-load.module.ts
index 2f93cb3..c717b32 100644
--- a/ambari-logsearch-web/src/app/modules/app-load/app-load.module.ts
+++ b/ambari-logsearch-web/src/app/modules/app-load/app-load.module.ts
@@ -25,7 +25,7 @@ import { DataAvailabilityStatesStore } from '@app/modules/app-load/stores/data-a
 import { Store } from '@ngrx/store';
 import { AppStore } from '@app/classes/models/store';
 import { CheckAuthorizationStatusAction } from '@app/store/actions/auth.actions';
-import { authStatusSelector } from '@app/store/selectors/auth.selectors';
+import { selectAuthStatus } from '@app/store/selectors/auth.selectors';
 import { AuthorizationStatuses } from '@app/store/reducers/auth.reducers';
 
 export function set_translation_service(appLoadService: AppLoadService) {
@@ -34,7 +34,7 @@ export function set_translation_service(appLoadService: AppLoadService) {
 
 export function check_auth_status(store: Store<AppStore>) {
   return () => new Promise((resolve) => {
-    store.select(authStatusSelector)
+    store.select(selectAuthStatus)
       .filter(
         (status: AuthorizationStatuses): boolean => (status !== null && AuthorizationStatuses.CHEKCING_AUTHORIZATION_STATUS !== status)
       ).first().subscribe(resolve);
diff --git a/ambari-logsearch-web/src/app/modules/app-load/models/data-availability-state.model.ts b/ambari-logsearch-web/src/app/modules/app-load/models/data-availability-state.model.ts
index d819dec..9a512df 100644
--- a/ambari-logsearch-web/src/app/modules/app-load/models/data-availability-state.model.ts
+++ b/ambari-logsearch-web/src/app/modules/app-load/models/data-availability-state.model.ts
@@ -22,6 +22,7 @@ export interface DataAvaibilityStatesModel {
   hostsDataState: DataAvailabilityValues;
   componentsDataState: DataAvailabilityValues;
   logFieldsDataState: DataAvailabilityValues;
+  [key: string]: DataAvailabilityValues;
 }
 
 export const initialDataAvaibilityStates: DataAvaibilityStatesModel = {
diff --git a/ambari-logsearch-web/src/app/modules/shared/animations.less b/ambari-logsearch-web/src/app/modules/shared/animations.less
index 5b8a04c..5f33c46 100644
--- a/ambari-logsearch-web/src/app/modules/shared/animations.less
+++ b/ambari-logsearch-web/src/app/modules/shared/animations.less
@@ -25,9 +25,40 @@
   }
 }
 
+@keyframes rotatecircle {
+  0% {
+    transform: rotate(0deg);
+  }
+  100% {
+    transform: rotate(360deg);
+  }
+}
+
+@keyframes running-progress {
+  0% { margin-left: 0px; margin-right: 100%; }
+  50% { margin-left: 25%; margin-right: 0%; }
+  100% { margin-left: 100%; margin-right: 0; }
+}
+
 .square-spinner(@size: 40px, @background: #3FAE2A, @speed: 1.2s) {
   width: @size;
   height: @size;
   background: @background;
   animation: rotateplane @speed infinite ease-in-out;
 }
+
+.circle-spinner(@size: 40px, @thickness: 3px, @color: #3FAE2A, @speed: 1.2s) {
+  animation: rotatecircle @speed  infinite ease-in-out;
+  border: @thickness solid @color;
+  border-right-color: transparent;
+  border-radius: 50%;
+  display: inline-block;
+  height: @size;
+  width: @size;
+}
+
+.line-progress(@height: 3px, @color: #3FAE2A, @speed: 1.2s) {
+  height: @height;
+  background-color: @color;
+  animation: running-progress @speed cubic-bezier(0.4, 0, 0.2, 1) infinite;
+}
diff --git a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-button/dropdown-button.component.ts b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-button/dropdown-button.component.ts
index ab519d0..74341ae 100644
--- a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-button/dropdown-button.component.ts
+++ b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-button/dropdown-button.component.ts
@@ -91,7 +91,19 @@ export class DropdownButtonComponent {
     protected utils: UtilsService
   ) {}
 
-  updateSelection(updates: ListItem | ListItem[]): void {
+  clearSelection(silent: boolean = false) {
+    let hasChange = false;
+    this.options.forEach((item: ListItem) => {
+      hasChange = hasChange || item.isChecked;
+      item.isChecked = false;
+    });
+    if (!silent && hasChange) {
+      this.selectItem.emit(this.isMultipleChoice ? [] : undefined);
+    }
+  }
+
+  updateSelection(updates: ListItem | ListItem[], callOnChange: boolean = true): boolean {
+    let hasChange = false;
     if (updates && (!Array.isArray(updates) || updates.length)) {
       const items: ListItem[] = Array.isArray(updates) ? updates : [updates];
       if (this.isMultipleChoice) {
@@ -99,6 +111,7 @@ export class DropdownButtonComponent {
           if (this.options && this.options.length) {
             const itemToUpdate: ListItem = this.options.find((option: ListItem) => this.utils.isEqual(option.value, item.value));
             if (itemToUpdate) {
+              hasChange = hasChange || itemToUpdate.isChecked !== item.isChecked;
               itemToUpdate.isChecked = item.isChecked;
             }
           }
@@ -106,7 +119,9 @@ export class DropdownButtonComponent {
       } else {
         const selectedItem: ListItem = Array.isArray(updates) ? updates[0] : updates;
         this.options.forEach((item: ListItem) => {
+          const checkedStateBefore = item.isChecked;
           item.isChecked = this.utils.isEqual(item.value, selectedItem.value);
+          hasChange = hasChange || checkedStateBefore !== item.isChecked;
         });
       }
     } else {
@@ -114,8 +129,11 @@ export class DropdownButtonComponent {
     }
     const checkedItems = this.options.filter((option: ListItem): boolean => option.isChecked);
     this.selection = checkedItems;
-    const selectedValues = checkedItems.map((option: ListItem): any => option.value);
-    this.selectItem.emit(this.isMultipleChoice ? selectedValues : selectedValues.shift());
+    if (hasChange) {
+      const selectedValues = checkedItems.map((option: ListItem): any => option.value);
+      this.selectItem.emit(this.isMultipleChoice ? selectedValues : selectedValues.shift());
+    }
+    return hasChange;
   }
 
 }
diff --git a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.html b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.html
index fac626f..89a794b 100644
--- a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.html
+++ b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.html
@@ -16,7 +16,7 @@
 -->
 <ng-template #listItem let-item let-isMultipleChoice="isMultipleChoice">
   <li [class.divider]="item.isDivider" [class.filtered]="isFiltered(item)"
-      [attr.role]="item.isDivider ? 'separator' : null" [class]="(item.cssClass || '')">
+      [attr.role]="item.isDivider ? 'separator' : null" [ngClass]="(item.cssClass || '')">
     <ng-container *ngIf="!item.isDivider">
       <span class="list-item-label" *ngIf="isMultipleChoice">
         <input type="checkbox" [attr.id]="(instanceId) + '-' + (item.id || item.value)" [(ngModel)]="item.isChecked"
diff --git a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.less b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.less
index 5ce1061..7461da3 100644
--- a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.less
+++ b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.less
@@ -20,11 +20,14 @@
 
 :host {
   max-height: @dropdown-max-height;
-  overflow-y: auto;
+  max-width: @dropdown-max-width;
+  overflow-y: hidden;
+  z-index: 1100;
 
   > li {
     .dropdown-item-default;
     transition: opacity 300ms ease-in, height 100ms 400ms ease-in;
+    text-overflow: ellipsis;
     &.filtered {
       overflow: hidden;
       opacity: 0;
diff --git a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.ts b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.ts
index 1809637..9967c80 100644
--- a/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.ts
+++ b/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.ts
@@ -23,6 +23,7 @@ import {
 import {Subscription} from 'rxjs/Subscription';
 import {ListItem} from '@app/classes/list-item';
 import {ComponentGeneratorService} from '@app/services/component-generator.service';
+import { Subject } from 'rxjs/Subject';
 
 @Component({
   selector: 'ul[data-component="dropdown-list"]',
@@ -77,7 +78,7 @@ export class DropdownListComponent implements OnInit, OnChanges, AfterViewChecke
 
   private filterRegExp: RegExp;
 
-  private subscriptions: Subscription[] = [];
+  private destroyed$ = new Subject();
 
   instanceId: string;
 
@@ -95,13 +96,11 @@ export class DropdownListComponent implements OnInit, OnChanges, AfterViewChecke
     if (this.items.some((item: ListItem) => item.isChecked)) {
       this.selectedItemChange.emit(this.items);
     }
-    this.subscriptions.push(
-      this.selectedItemChange.subscribe(this.separateSelections)
-    );
+    this.selectedItemChange.takeUntil(this.destroyed$).subscribe(this.separateSelections)
   }
 
   ngOnDestroy() {
-    this.subscriptions.forEach((subscription: Subscription) => subscription.unsubscribe());
+    this.destroyed$.next(true);
   }
 
   ngOnChanges(changes: SimpleChanges): void {
@@ -186,9 +185,6 @@ export class DropdownListComponent implements OnInit, OnChanges, AfterViewChecke
   unSelectAll() {
     this.items.forEach((item: ListItem) => {
       item.isChecked = false;
-      if (item.onSelect) {
-        item.onSelect(...this.actionArguments);
-      }
     });
     this.selectedItemChange.emit(this.items);
   }
diff --git a/ambari-logsearch-web/src/app/modules/shared/components/filter-dropdown/filter-dropdown.component.ts b/ambari-logsearch-web/src/app/modules/shared/components/filter-dropdown/filter-dropdown.component.ts
index 6140e7d..669fcc9 100644
--- a/ambari-logsearch-web/src/app/modules/shared/components/filter-dropdown/filter-dropdown.component.ts
+++ b/ambari-logsearch-web/src/app/modules/shared/components/filter-dropdown/filter-dropdown.component.ts
@@ -34,7 +34,7 @@ import {ListItem} from '@app/classes/list-item';
 })
 export class FilterDropdownComponent extends DropdownButtonComponent implements ControlValueAccessor {
 
-  private onChange: (fn: any) => void;
+  private onChange;
 
   get selection(): ListItem[] {
     return this.selectedItems;
@@ -48,9 +48,20 @@ export class FilterDropdownComponent extends DropdownButtonComponent implements
         option.isChecked = Boolean(selectionItem);
       });
     }
+  }
+
+  private _onChange(value) {
     if (this.onChange) {
-      this.onChange(items);
+      this.onChange(value);
+    }
+  }
+
+  updateSelection(updates: ListItem | ListItem[], callOnChange: boolean = true): boolean {
+    const hasChange = super.updateSelection(updates);
+    if (hasChange && callOnChange) {
+      this._onChange(this.selection);
     }
+    return hasChange;
   }
 
   writeValue(items: ListItem[]) {
diff --git a/ambari-logsearch-web/src/app/modules/shared/components/modal-dialog/modal-dialog.component.spec.ts b/ambari-logsearch-web/src/app/modules/shared/components/modal-dialog/modal-dialog.component.spec.ts
index f13872c..7879377 100644
--- a/ambari-logsearch-web/src/app/modules/shared/components/modal-dialog/modal-dialog.component.spec.ts
+++ b/ambari-logsearch-web/src/app/modules/shared/components/modal-dialog/modal-dialog.component.spec.ts
@@ -32,6 +32,7 @@ import { AuthEffects } from '@app/store/effects/auth.effects';
 import { NotificationEffects } from '@app/store/effects/notification.effects';
 
 import { ModalDialogComponent } from './modal-dialog.component';
+import { RouterTestingModule } from '@angular/router/testing';
 
 describe('ModalDialogComponent', () => {
   let component: ModalDialogComponent;
@@ -41,6 +42,7 @@ describe('ModalDialogComponent', () => {
     TestBed.configureTestingModule(getCommonTestingBedConfiguration({
       imports: [
         ...TranslationModules,
+        RouterTestingModule,
         StoreModule.provideStore({
           appState,
           auth: auth.reducer
diff --git a/ambari-logsearch-web/src/app/modules/shared/shared.module.ts b/ambari-logsearch-web/src/app/modules/shared/shared.module.ts
index 8269852..c520c38 100644
--- a/ambari-logsearch-web/src/app/modules/shared/shared.module.ts
+++ b/ambari-logsearch-web/src/app/modules/shared/shared.module.ts
@@ -16,27 +16,27 @@
  * limitations under the License.
  */
 
-import {NgModule} from '@angular/core';
-import {CommonModule} from '@angular/common';
-import {BrowserModule, Title} from '@angular/platform-browser';
-import {FormsModule} from '@angular/forms';
-import {Http} from '@angular/http';
-import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
-import {NotificationsService as Angular2NotificationsService} from 'angular2-notifications/src/notifications.service';
-import {TranslateModule, TranslateLoader} from '@ngx-translate/core';
-import {NgObjectPipesModule} from 'angular-pipes';
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { BrowserModule, Title } from '@angular/platform-browser';
+import { FormsModule } from '@angular/forms';
+import { Http } from '@angular/http';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { NotificationsService as Angular2NotificationsService } from 'angular2-notifications/src/notifications.service';
+import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
+import { NgObjectPipesModule } from 'angular-pipes';
 
-import {TranslateService as AppTranslateService} from '@app/services/translate.service';
+import { TranslateService as AppTranslateService } from '@app/services/translate.service';
 
-import {NotificationService} from './services/notification.service';
+import { NotificationService } from './services/notification.service';
 
-import {CanDeactivateGuardService} from './services/can-deactivate-guard.service';
-import {DisableControlDirective} from './directives/disable-control.directive';
+import { CanDeactivateGuardService } from './services/can-deactivate-guard.service';
+import { DisableControlDirective } from './directives/disable-control.directive';
 
-import {DropdownButtonComponent} from './components/dropdown-button/dropdown-button.component';
-import {DropdownListComponent} from './components/dropdown-list/dropdown-list.component';
-import {FilterDropdownComponent} from './components/filter-dropdown/filter-dropdown.component';
-import {ModalComponent} from './components/modal/modal.component';
+import { DropdownButtonComponent } from './components/dropdown-button/dropdown-button.component';
+import { DropdownListComponent } from './components/dropdown-list/dropdown-list.component';
+import { FilterDropdownComponent } from './components/filter-dropdown/filter-dropdown.component';
+import { ModalComponent } from './components/modal/modal.component';
 import { DataLoadingIndicatorComponent } from '@app/modules/shared/components/data-loading-indicator/data-loading-indicator.component';
 import { ModalDialogComponent } from './components/modal-dialog/modal-dialog.component';
 import { LoadingIndicatorComponent } from './components/loading-indicator/loading-indicator.component';
diff --git a/ambari-logsearch-web/src/app/modules/shared/variables.less b/ambari-logsearch-web/src/app/modules/shared/variables.less
index b917527..437c556 100644
--- a/ambari-logsearch-web/src/app/modules/shared/variables.less
+++ b/ambari-logsearch-web/src/app/modules/shared/variables.less
@@ -46,6 +46,7 @@
 @checkbox-top: 4px;
 @dropdown-min-width: 160px;
 @dropdown-max-height: 60vh; // TODO get rid of magic number, base on actual design
+@dropdown-max-width: 50vw; // TODO get rid of magic number, base on actual design
 @dropdown-border-radius: 2px;
 @input-height: 34px;
 @input-padding: 10px;
diff --git a/ambari-logsearch-web/src/app/services/filter-history.guard.ts b/ambari-logsearch-web/src/app/services/filter-history.guard.ts
new file mode 100644
index 0000000..95997cd
--- /dev/null
+++ b/ambari-logsearch-web/src/app/services/filter-history.guard.ts
@@ -0,0 +1,128 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+import { Injectable } from '@angular/core';
+import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
+import { Observable } from 'rxjs/Observable';
+
+import { Store } from '@ngrx/store';
+import { AppStore } from '@app/classes/models/store';
+
+import * as dataAvailabilitySelectors from '@app/store/selectors/data-availability.selectors';
+
+import * as fromFilterHistoryReducers from '@app/store/reducers/filter-history.reducers';
+import * as filterHistorySelectors from '@app/store/selectors/filter-history.selectors';
+import { AddFilterHistoryAction, SetCurrentFilterHistoryByIndexAction } from '@app/store/actions/filter-history.actions';
+
+@Injectable()
+export class FilterHistoryIndexGuard implements CanActivate {
+
+  private currentUrl: string;
+
+  currentFilterHistory$: Observable<fromFilterHistoryReducers.FilterHistoryState> = this.store.select(
+    filterHistorySelectors.selectFilterHistoryState
+  );
+
+  filterHistoryIndexUrlParamName = '_fhi';
+  logsTypeUrlParamName = 'activeTab';
+
+  constructor (
+    private router: Router,
+    private store: Store<AppStore>
+  ) {}
+
+  removeFilterHistoryIndexFromUrl(url) {
+    const regexp = new RegExp(`;${this.filterHistoryIndexUrlParamName}=\\d{1,}`, 'g');
+    return url.replace(regexp, '');
+  }
+
+  addFilterHistoryIndexToUrl(url, index) {
+    return `${url};${this.filterHistoryIndexUrlParamName}=${index}`;
+  }
+
+  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
+    let filterHistoryIndex = next.params[this.filterHistoryIndexUrlParamName];
+    if (typeof filterHistoryIndex === 'string') {
+      filterHistoryIndex = parseInt(filterHistoryIndex, 0);
+    }
+    const logsType = next.params[this.logsTypeUrlParamName];
+    return  Observable.combineLatest(
+      this.currentFilterHistory$,
+      this.store.select(dataAvailabilitySelectors.isBaseDataAvailable)
+    ).first().map(([historyState, isBaseDataAvailable]: [fromFilterHistoryReducers.FilterHistoryState, boolean]): boolean => {
+      const history = historyState[logsType];
+      let canActivate = true;
+      const requestedUrlWithoutFilterHistoryIndex = this.removeFilterHistoryIndexFromUrl(state.url);
+      const lastChangeUrlWithoutFilterHistoryIndex = history && history.changes.length && (
+        this.removeFilterHistoryIndexFromUrl(history.changes[ history.changes.length - 1].currentPath)
+      );
+      const isUrlChanged = lastChangeUrlWithoutFilterHistoryIndex !== requestedUrlWithoutFilterHistoryIndex;
+      // check if the filter history index is correct
+      if (isUrlChanged && filterHistoryIndex !== undefined) {
+        const currentUrlAtIndex = (
+          history
+          && history.changes[filterHistoryIndex]
+          && this.removeFilterHistoryIndexFromUrl(history.changes[filterHistoryIndex].currentPath)
+        );
+        if (requestedUrlWithoutFilterHistoryIndex !== currentUrlAtIndex) {
+          filterHistoryIndex = undefined;
+          // correct the filter history index if we already have history and the URL exists in the list
+          if (history && history.changes.length) {
+            const existingIndex = history.changes.findIndex(
+              (change) => this.removeFilterHistoryIndexFromUrl(change.currentPath) ===  requestedUrlWithoutFilterHistoryIndex
+            );
+            if (existingIndex > -1) {
+              filterHistoryIndex = existingIndex;
+            }
+          }
+        }
+      }
+      if (!history || filterHistoryIndex === undefined) { // new History URL
+        const nextFilterHistoryIndex: number = history ? history.currentChangeIndex + 1 : 0;
+        const indexedUrl = this.addFilterHistoryIndexToUrl(requestedUrlWithoutFilterHistoryIndex, nextFilterHistoryIndex);
+        if (isUrlChanged) {
+          this.store.dispatch( new AddFilterHistoryAction({
+            logType: logsType,
+            change: {
+              previousPath: this.currentUrl,
+              currentPath: indexedUrl,
+              time: new Date()
+            }
+          }));
+          this.currentUrl = indexedUrl;
+        }
+        this.router.navigateByUrl(indexedUrl);
+        canActivate = false;
+      } else if (history.currentChangeIndex !== filterHistoryIndex) {
+        // set the current index in the store
+        this.store.dispatch(
+          new SetCurrentFilterHistoryByIndexAction({
+            logType: logsType,
+            index: filterHistoryIndex
+          })
+        );
+      }
+      // if we found the requested URL in the history but the recorded index is not the same as it is in the URL
+      // we add it and navigate to the indexed URL
+      if (filterHistoryIndex !== undefined && next.params[this.filterHistoryIndexUrlParamName] !== filterHistoryIndex.toString()) {
+        this.router.navigateByUrl( this.addFilterHistoryIndexToUrl(state.url, filterHistoryIndex) );
+        canActivate = false;
+      }
+      return canActivate;
+    });
+  }
+}
diff --git a/ambari-logsearch-web/src/app/services/history-manager.service.ts b/ambari-logsearch-web/src/app/services/history-manager.service.ts
index 2a3f533..b484cf1 100644
--- a/ambari-logsearch-web/src/app/services/history-manager.service.ts
+++ b/ambari-logsearch-web/src/app/services/history-manager.service.ts
@@ -43,22 +43,14 @@ export class HistoryManagerService {
    * Maximal number of displayed history items
    * @type {number}
    */
-  private readonly maxHistoryItemsCount: number = 25;
-
-  /**
-   * Indicates whether there is no changes being applied to filters that are triggered by undo or redo action.
-   * Since user can undo or redo several filters changes at once, and they are applied to form controls step-by-step,
-   * this flag is needed to avoid recording intermediate items to history.
-   * @type {boolean}
-   */
-  private hasNoPendingUndoOrRedo: boolean = true;
+  private readonly maxHistoryItemsCount = 25;
 
   /**
    * Id of currently active history item.
    * Generally speaking, it isn't id of the latest one because it can be shifted by undo or redo action.
    * @type {number}
    */
-  private currentHistoryItemId: number = -1;
+  private currentHistoryItemId = -1;
 
   /**
    * Contains i18n labels for filtering form control names
@@ -125,7 +117,7 @@ export class HistoryManagerService {
     });
 
     this.logsContainerService.filtersForm.valueChanges
-      .filter(() => !this.logsContainerService.filtersFormSyncInProgress.getValue())
+      .filter(() => !this.logsContainerService.filtersFormSyncInProgress$.getValue())
       .distinctUntilChanged(this.isHistoryUnchanged)
       .subscribe(this.onFormValueChanges);
   }
@@ -146,41 +138,33 @@ export class HistoryManagerService {
   }
 
   onFormValueChanges = (value): void => {
-    if (this.hasNoPendingUndoOrRedo) {
-      const defaultState = this.logsContainerService.getFiltersData(this.logsContainerService.activeLogsType);
-      const currentHistory = this.activeHistory;
-      const previousValue = this.activeHistory.length ? this.activeHistory[0].value.currentValue : defaultState;
-      const isUndoOrRedo = value.isUndoOrRedo;
-      const previousChangeId = this.currentHistoryItemId;
-      if (isUndoOrRedo) {
-        this.hasNoPendingUndoOrRedo = false;
-        this.logsContainerService.filtersForm.patchValue({
-          isUndoOrRedo: false
-        });
-        this.hasNoPendingUndoOrRedo = true;
-      } else {
-        this.currentHistoryItemId = currentHistory.length;
-      }
-      const newItem = {
-        value: {
-          currentValue: Object.assign({}, value),
-          previousValue: Object.assign({}, previousValue),
-          changeId: this.currentHistoryItemId,
-          previousChangeId,
-          isUndoOrRedo
-        },
-        label: this.getHistoryItemLabel(previousValue, value)
-      };
-      if (newItem.label) {
-        this.activeHistory = [
-          newItem,
-          ...currentHistory
-        ].slice(0, this.maxHistoryItemsCount);
-        this.appState.setParameter('history', {
-          items: this.activeHistory.slice(),
-          currentId: this.currentHistoryItemId
-        });
-      }
+    const defaultState = this.logsContainerService.getFiltersData(this.logsContainerService.activeLogsType);
+    const currentHistory = this.activeHistory;
+    const previousValue = this.activeHistory.length ? this.activeHistory[0].value.currentValue : defaultState;
+    const previousChangeId = this.currentHistoryItemId;
+    this.currentHistoryItemId = currentHistory.length;
+    const newItem = {
+      value: {
+        currentValue: Object.assign({}, value),
+        previousValue: Object.assign({}, previousValue),
+        changeId: this.currentHistoryItemId,
+        previousChangeId
+      },
+      label: this.getHistoryItemLabel(previousValue, value)
+    };
+    if (newItem.label) {
+      this.activeHistory = [
+        newItem,
+        ...currentHistory
+      ].slice(0, this.maxHistoryItemsCount);
+      this.activeHistory = this.activeHistory.map((item) => {
+        item.cssClass = item.value.changeId === this.currentHistoryItemId ? 'current-history-item' : '';
+        return item;
+      });
+      this.appState.setParameter('history', {
+        items: [...this.activeHistory],
+        currentId: this.currentHistoryItemId
+      });
     }
   }
 
@@ -191,15 +175,11 @@ export class HistoryManagerService {
   get undoItems(): ListItem[] {
     const allItems = this.activeHistory;
     const startIndex = allItems.findIndex((item: ListItem): boolean => {
-        return item.value.changeId === this.currentHistoryItemId && !item.value.isUndoOrRedo;
-      });
-    let endIndex = allItems.slice(startIndex + 1).findIndex((item: ListItem): boolean => item.value.isUndoOrRedo);
+      return item.value.changeId === this.currentHistoryItemId;
+    });
     let items = [];
     if (startIndex > -1) {
-      if (endIndex === -1) {
-        endIndex = allItems.length;
-      }
-      items = allItems.slice(startIndex, startIndex + endIndex + 1);
+      items = allItems.slice(startIndex);
     }
     return items;
   }
@@ -209,18 +189,14 @@ export class HistoryManagerService {
    * @returns {ListItem[]}
    */
   get redoItems(): ListItem[] {
-    const allItems = this.activeHistory.slice().reverse();
+    const allItems = [...this.activeHistory].reverse();
     let startIndex = allItems.findIndex((item: ListItem): boolean => {
-        return item.value.previousChangeId === this.currentHistoryItemId && !item.value.isUndoOrRedo;
-      }),
-      endIndex = allItems.slice(startIndex + 1).findIndex((item: ListItem): boolean => item.value.isUndoOrRedo);
+      return item.value.previousChangeId === this.currentHistoryItemId;
+    });
     if (startIndex === -1) {
       startIndex = allItems.length;
     }
-    if (endIndex === -1) {
-      endIndex = allItems.length;
-    }
-    return allItems.slice(startIndex, endIndex + startIndex + 1);
+    return allItems.slice(startIndex);
   }
 
   /**
@@ -292,24 +268,11 @@ export class HistoryManagerService {
    * @param {object} value
    */
   private handleUndoOrRedo(value: object): void {
-    const filtersForm = this.logsContainerService.filtersForm;
-    this.hasNoPendingUndoOrRedo = false;
-    this.logsContainerService.filtersFormSyncInProgress.next(true);
-    this.filterParameters.filter(controlName => this.ignoredParameters.indexOf(controlName) === -1)
-      .forEach((controlName: string): void => {
-        filtersForm.controls[controlName].setValue(value[controlName], {
-          emitEvent: false,
-          onlySelf: true
-        });
-      });
-    this.logsContainerService.filtersFormSyncInProgress.next(false);
-    this.hasNoPendingUndoOrRedo = true;
-    filtersForm.controls.isUndoOrRedo.setValue(true);
+    this.logsContainerService.resetFiltersForms(value);
   }
 
   undo(item: ListItem): void {
     if (item) {
-      this.hasNoPendingUndoOrRedo = false;
       this.currentHistoryItemId = item.value.previousChangeId;
       this.handleUndoOrRedo(item.value.previousValue);
     }
@@ -317,7 +280,6 @@ export class HistoryManagerService {
 
   redo(item: ListItem): void {
     if (item) {
-      this.hasNoPendingUndoOrRedo = false;
       this.currentHistoryItemId = item.value.changeId;
       this.handleUndoOrRedo(item.value.currentValue);
     }
diff --git a/ambari-logsearch-web/src/app/services/http-client.service.ts b/ambari-logsearch-web/src/app/services/http-client.service.ts
index 2d68977..4b2152c 100644
--- a/ambari-logsearch-web/src/app/services/http-client.service.ts
+++ b/ambari-logsearch-web/src/app/services/http-client.service.ts
@@ -37,6 +37,8 @@ import { AppStore } from '@app/classes/models/store';
 import { HttpAuthorizationErrorResponseAction } from '@app/store/actions/auth.actions';
 import { isAuthorizedSelector } from '@app/store/selectors/auth.selectors';
 
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+
 @Injectable()
 export class HttpClientService extends Http {
 
@@ -105,6 +107,9 @@ export class HttpClientService extends Http {
 
   private readonly unauthorizedStatuses = [401, 403, 419];
 
+  requestsPending: BehaviorSubject<number> = new BehaviorSubject(0);
+  requestInProgress: Observable<boolean> = this.requestsPending.map((totalRequest: number) => totalRequest > 0);
+
   constructor(
     backend: XHRBackend,
     defaultOptions: RequestOptions,
@@ -185,11 +190,14 @@ export class HttpClientService extends Http {
       }
       return handled;
     };
-    return super.request(this.generateUrl(url), options).first().share()
+    const req = super.request(this.generateUrl(url), options).first().share()
       .map(response => response)
       .catch((error: any) => {
         return handleResponseError(error) ? Observable.of(error) : Observable.throw(error);
       });
+    req.subscribe(() => this.requestsPending.next(this.requestsPending.getValue() - 1));
+    this.requestsPending.next(this.requestsPending.getValue() + 1);
+    return req;
   }
 
   get(url: string, params?: HomogeneousObject<string>, urlVariables?: HomogeneousObject<string>): Observable<Response> {
diff --git a/ambari-logsearch-web/src/app/services/log-index-filter.service.spec.ts b/ambari-logsearch-web/src/app/services/log-index-filter.service.spec.ts
index eb4bf66..287b1dc 100644
--- a/ambari-logsearch-web/src/app/services/log-index-filter.service.spec.ts
+++ b/ambari-logsearch-web/src/app/services/log-index-filter.service.spec.ts
@@ -28,12 +28,14 @@ import {NotificationService} from '@modules/shared/services/notification.service
 import {NotificationsService} from 'angular2-notifications/src/notifications.service';
 
 import { LogIndexFilterService } from './log-index-filter.service';
+import { RouterTestingModule } from '@angular/router/testing';
 
 describe('LogIndexFilterService', () => {
   beforeEach(() => {
     TestBed.configureTestingModule(getCommonTestingBedConfiguration({
       imports: [
-        ...TranslationModules
+        ...TranslationModules,
+        RouterTestingModule,
       ],
       providers: [
         AppStateService,
diff --git a/ambari-logsearch-web/src/app/services/logs-container.service.ts b/ambari-logsearch-web/src/app/services/logs-container.service.ts
index d550fbb..0612744 100644
--- a/ambari-logsearch-web/src/app/services/logs-container.service.ts
+++ b/ambari-logsearch-web/src/app/services/logs-container.service.ts
@@ -59,7 +59,7 @@ import { NodeItem } from '@app/classes/models/node-item';
 import { CommonEntry } from '@app/classes/models/common-entry';
 import { ClusterSelectionService } from '@app/services/storage/cluster-selection.service';
 import { Router } from '@angular/router';
-import { LogsFilteringUtilsService } from '@app/services/logs-filtering-utils.service';
+import { LogsFilteringUtilsService, defaultFilterSelections } from '@app/services/logs-filtering-utils.service';
 import { BehaviorSubject } from 'rxjs/BehaviorSubject';
 import { LogsStateService } from '@app/services/storage/logs-state.service';
 import { LogLevelComponent } from '@app/components/log-level/log-level.component';
@@ -69,6 +69,8 @@ import { Store } from '@ngrx/store';
 import { AppStore } from '@app/classes/models/store';
 import { isAuthorizedSelector } from '@app/store/selectors/auth.selectors';
 
+import { Subscription } from 'rxjs/Subscription';
+
 @Injectable()
 export class LogsContainerService {
 
@@ -118,19 +120,19 @@ export class LogsContainerService {
     clusters: {
       label: 'filter.clusters',
       options: [],
-      defaultSelection: this.logsFilteringUtilsService.defaultFilterSelections.clusters,
+      defaultSelection: defaultFilterSelections.clusters,
       fieldName: 'cluster'
     },
     timeRange: { // @ToDo remove duplication, this options are in the LogsFilteringUtilsService too
       label: 'logs.duration',
       options: this.logsFilteringUtilsService.getTimeRandeOptionsByGroup(),
-      defaultSelection: this.logsFilteringUtilsService.defaultFilterSelections.timeRange
+      defaultSelection: defaultFilterSelections.timeRange
     },
     components: {
       label: 'filter.components',
       iconClass: 'fa fa-cubes',
       options: [],
-      defaultSelection: this.logsFilteringUtilsService.defaultFilterSelections.components,
+      defaultSelection: defaultFilterSelections.components,
       fieldName: 'type'
     },
     levels: {
@@ -145,14 +147,14 @@ export class LogsContainerService {
           iconClass: `fa ${LogLevelComponent.classMap[cssClass]}`
         };
       }),
-      defaultSelection: this.logsFilteringUtilsService.defaultFilterSelections.levels,
+      defaultSelection: defaultFilterSelections.levels,
       fieldName: 'level'
     },
     hosts: {
       label: 'filter.hosts',
       iconClass: 'fa fa-server',
       options: [],
-      defaultSelection: this.logsFilteringUtilsService.defaultFilterSelections.hosts,
+      defaultSelection: defaultFilterSelections.hosts,
       fieldName: 'host'
     },
     auditLogsSorting: {
@@ -173,7 +175,7 @@ export class LogsContainerService {
           }
         }
       ],
-      defaultSelection: this.logsFilteringUtilsService.defaultFilterSelections.auditLogsSorting
+      defaultSelection: defaultFilterSelections.auditLogsSorting
     },
     serviceLogsSorting: {
       label: 'sorting.title',
@@ -193,7 +195,7 @@ export class LogsContainerService {
           }
         }
       ],
-      defaultSelection: this.logsFilteringUtilsService.defaultFilterSelections.serviceLogsSorting
+      defaultSelection: defaultFilterSelections.serviceLogsSorting
     },
     pageSize: {
       label: 'pagination.title',
@@ -203,23 +205,20 @@ export class LogsContainerService {
           value: option
         };
       }),
-      defaultSelection: this.logsFilteringUtilsService.defaultFilterSelections.pageSize
+      defaultSelection: defaultFilterSelections.pageSize
     },
     page: {
-      defaultSelection: this.logsFilteringUtilsService.defaultFilterSelections.page
+      defaultSelection: defaultFilterSelections.page
     },
     query: {
-      defaultSelection: this.logsFilteringUtilsService.defaultFilterSelections.query
+      defaultSelection: defaultFilterSelections.query
     },
     users: {
       label: 'filter.users',
       iconClass: 'fa fa-server',
       options: [],
-      defaultSelection: this.logsFilteringUtilsService.defaultFilterSelections.users,
+      defaultSelection: defaultFilterSelections.users,
       fieldName: 'reqUser'
-    },
-    isUndoOrRedo: {
-      defaultSelection: this.logsFilteringUtilsService.defaultFilterSelections.isUndoOrRedo
     }
   };
 
@@ -369,7 +368,11 @@ export class LogsContainerService {
     excludeQuery: this.logsFilteringUtilsService.getQuery(true)
   };
 
-  filtersFormSyncInProgress: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
+  filtersFormSyncInProgress$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
+
+  private pendingLogRequests: {[key: string]: Subscription[]} = {};
+  isLogsRequestInProgress$: BehaviorSubject<boolean> = new BehaviorSubject(false);
+  isGraphRequestInProgress$: BehaviorSubject<boolean> = new BehaviorSubject(false);
 
   constructor(
     private httpClient: HttpClientService, private utils: UtilsService,
@@ -392,7 +395,7 @@ export class LogsContainerService {
       const item = {
         [key]: formControl
       };
-      formControl.setValue(this.logsFilteringUtilsService.defaultFilterSelections[key]);
+      formControl.setValue(defaultFilterSelections[key]);
       return Object.assign(currentObject, item);
     }, {});
     this.filtersForm = new FormGroup(formItems);
@@ -401,9 +404,9 @@ export class LogsContainerService {
     this.clustersStorage.getAll().subscribe(this.setClustersFilters);
     this.hostsStorage.getAll().subscribe(this.setHostsFilters);
 
-    appState.getParameter('activeLog').subscribe((value: ActiveServiceLogEntry | null) => this.activeLog = value);
-    appState.getParameter('isServiceLogsFileView').subscribe((value: boolean) => this.isServiceLogsFileView = value);
-    appState.getParameter('activeLogsType').subscribe((value: LogsType) => {
+    appState.getParameter('activeLog').distinctUntilChanged().subscribe((value: ActiveServiceLogEntry | null) => this.activeLog = value);
+    appState.getParameter('isServiceLogsFileView').distinctUntilChanged().subscribe((value: boolean) => this.isServiceLogsFileView = value);
+    appState.getParameter('activeLogsType').distinctUntilChanged().subscribe((value: LogsType) => {
       if (this.isLogsTypeSupported(value)) {
         this.activeLogsType = value;
       }
@@ -425,7 +428,7 @@ export class LogsContainerService {
       });
     });
 
-    this.filtersForm.valueChanges.filter(() => !this.filtersFormSyncInProgress.getValue()).subscribe(this.onFiltersFormValueChange);
+    this.filtersForm.valueChanges.filter(() => !this.filtersFormSyncInProgress$.getValue()).subscribe(this.onFiltersFormValueChange);
 
     this.auditLogsSource.subscribe((logs: AuditLog[]): void => {
       const userNames = logs.map((log: AuditLog): string => log.reqUser);
@@ -460,12 +463,12 @@ export class LogsContainerService {
       .filter((dataSetState: DataAvailabilityValues) => dataSetState === DataAvailabilityValues.AVAILABLE)
       .first()
       .subscribe(() => {
-        this.filtersFormSyncInProgress.next(true);
+        this.filtersFormSyncInProgress$.next(true);
         this.filtersForm.reset(
-          {...this.logsFilteringUtilsService.defaultFilterSelections, ...filters},
+          {...defaultFilterSelections, ...filters},
           {emitEvent: false}
         );
-        this.filtersFormSyncInProgress.next(false);
+        this.filtersFormSyncInProgress$.next(false);
         this.onFiltersFormValueChange();
       });
   }
@@ -589,59 +592,76 @@ export class LogsContainerService {
 
   loadLogs = (logsType: LogsType = this.activeLogsType): void => {
     if (this.isLogsTypeSupported(logsType)) {
-      this.httpClient.get(logsType, this.getParams('listFilters', {}, logsType)).subscribe((response: Response): void => {
-        const jsonResponse = response.json(),
-          model = this.logsTypeMap[logsType].logsModel;
-        model.clear();
-        if (jsonResponse) {
-          const logs = jsonResponse.logList,
-            count = jsonResponse.totalCount || 0;
-          if (logs) {
-            model.addInstances(logs);
-          }
-          this.totalCount = count;
-        }
-      });
-      this.httpClient.get(this.logsTypeMap[logsType].graphRequestName, this.getParams('graphFilters', {}, logsType))
-        .subscribe((response: Response): void => {
+      let pendingLogRequests = this.pendingLogRequests[logsType] || [];
+      pendingLogRequests.forEach((subscription: Subscription) => subscription.unsubscribe());
+      pendingLogRequests = [];
+
+      this.isLogsRequestInProgress$.next(true);
+      pendingLogRequests.push(
+        this.httpClient.get(logsType, this.getParams('listFilters', {}, logsType)).subscribe((response: Response): void => {
           const jsonResponse = response.json(),
-            model = this.logsTypeMap[logsType].graphModel;
+            model = this.logsTypeMap[logsType].logsModel;
           model.clear();
           if (jsonResponse) {
-            const graphData = jsonResponse.graphData;
-            if (graphData) {
-              model.addInstances(graphData);
+            const logs = jsonResponse.logList,
+              count = jsonResponse.totalCount || 0;
+            if (logs) {
+              model.addInstances(logs);
             }
+            this.totalCount = count;
           }
-        });
+          this.isLogsRequestInProgress$.next(false);
+        })
+      );
+      this.isGraphRequestInProgress$.next(true);
+      pendingLogRequests.push(
+        this.httpClient.get(this.logsTypeMap[logsType].graphRequestName, this.getParams('graphFilters', {}, logsType))
+          .subscribe((response: Response): void => {
+            const jsonResponse = response.json(),
+              model = this.logsTypeMap[logsType].graphModel;
+            model.clear();
+            if (jsonResponse) {
+              const graphData = jsonResponse.graphData;
+              if (graphData) {
+                model.addInstances(graphData);
+              }
+            }
+            this.isGraphRequestInProgress$.next(false);
+          })
+      );
       if (logsType === 'auditLogs') {
-        this.httpClient.get('topAuditLogsResources', this.getParams('topResourcesFilters', {
-          field: 'resource'
-        }, logsType), {
-          number: this.topResourcesCount
-        }).subscribe((response: Response): void => {
-          const jsonResponse = response.json();
-          if (jsonResponse) {
-            const data = jsonResponse.graphData;
-            if (data) {
-              this.topResourcesGraphData = this.parseAuditLogsTopData(data);
+        pendingLogRequests.push(
+          this.httpClient.get('topAuditLogsResources', this.getParams('topResourcesFilters', {
+            field: 'resource'
+          }, logsType), {
+            number: this.topResourcesCount
+          }).subscribe((response: Response): void => {
+            const jsonResponse = response.json();
+            if (jsonResponse) {
+              const data = jsonResponse.graphData;
+              if (data) {
+                this.topResourcesGraphData = this.parseAuditLogsTopData(data);
+              }
             }
-          }
-        });
-        this.httpClient.get('topAuditLogsResources', this.getParams('topResourcesFilters', {
-          field: 'reqUser'
-        }, logsType), {
-          number: this.topUsersCount
-        }).subscribe((response: Response): void => {
-          const jsonResponse = response.json();
-          if (jsonResponse) {
-            const data = jsonResponse.graphData;
-            if (data) {
-              this.topUsersGraphData = this.parseAuditLogsTopData(data);
+          })
+        );
+        pendingLogRequests.push(
+          this.httpClient.get('topAuditLogsResources', this.getParams('topResourcesFilters', {
+            field: 'reqUser'
+          }, logsType), {
+            number: this.topUsersCount
+          }).subscribe((response: Response): void => {
+            const jsonResponse = response.json();
+            if (jsonResponse) {
+              const data = jsonResponse.graphData;
+              if (data) {
+                this.topUsersGraphData = this.parseAuditLogsTopData(data);
+              }
             }
-          }
-        });
+          })
+        );
       }
+      this.pendingLogRequests[logsType] = pendingLogRequests;
     } else {
       console.error(`Logs Type does not supported: ${logsType}`);
     }
@@ -883,7 +903,7 @@ export class LogsContainerService {
     const keys = Object.keys(this.filters).filter((key: string): boolean => itemsList.indexOf(key) > -1);
     return keys.reduce((currentObject: object, key: string): object => {
       return Object.assign(currentObject, {
-        [key]: this.logsFilteringUtilsService.defaultFilterSelections[key]
+        [key]: defaultFilterSelections[key]
       });
     }, {});
   }
@@ -909,6 +929,7 @@ export class LogsContainerService {
       .subscribe((componentName) => {
         const tab = {
           id: log.id || `${log.host}-${log.type}`,
+          logsType: <LogsType>'serviceLogs',
           isCloseable: true,
           isActive: false,
           label: `${log.host} >> ${componentName || log.type}`,
diff --git a/ambari-logsearch-web/src/app/services/logs-filtering-utils.service.ts b/ambari-logsearch-web/src/app/services/logs-filtering-utils.service.ts
index 9fddade..d6bf06d 100644
--- a/ambari-logsearch-web/src/app/services/logs-filtering-utils.service.ts
+++ b/ambari-logsearch-web/src/app/services/logs-filtering-utils.service.ts
@@ -4,27 +4,97 @@
  * distributed with this work for additional information
  * regarding copyright ownership.  The ASF licenses this file
  * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
+ * '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,
+ * 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.
  */
-import {Injectable} from '@angular/core';
-import {ListItem} from '@app/classes/list-item';
-import {CustomTimeRange, SearchBoxParameter, SortingListItem, TimeUnit, TimeUnitListItem} from '@app/classes/filtering';
+import { Injectable } from '@angular/core';
+import { ListItem } from '@app/classes/list-item';
+import { CustomTimeRange, SearchBoxParameter, SortingListItem, TimeUnit, TimeUnitListItem } from '@app/classes/filtering';
 import * as moment from 'moment-timezone';
-import {HomogeneousObject} from '@app/classes/object';
-import {LogsType, SortingType} from '@app/classes/string';
-import {UtilsService} from '@app/services/utils.service';
+import { HomogeneousObject } from '@app/classes/object';
+import { LogsType, SortingType } from '@app/classes/string';
+import { UtilsService } from '@app/services/utils.service';
 import { LogTypeTab } from '@app/classes/models/log-type-tab';
 
-// @ToDo remove duplication, this options are in the LogsContainerService
+export enum UrlParamsDifferenceType {
+  ADD = 'add',
+  REMOVE = 'remove',
+  CHANGE = 'change',
+  CLEAR = 'clear'
+};
+
+export interface UrlParamDifferences {
+  name: string;
+  type: UrlParamsDifferenceType;
+  from: any;
+  to: any;
+};
+
+export const defaultFilterSelections = {
+  clusters: [],
+  timeRange: {
+    value: {
+      type: 'LAST',
+      unit: 'h',
+      interval: 1
+    },
+    label: 'filter.timeRange.1hr'
+  },
+  components: [],
+  levels: [],
+  hosts: [],
+  auditLogsSorting: {
+    label: 'sorting.time.desc',
+    value: {
+      key: 'evtTime',
+      type: 'desc'
+    }
+  },
+  serviceLogsSorting: {
+    label: 'sorting.time.desc',
+    value: {
+      key: 'logtime',
+      type: 'desc'
+    }
+  },
+  pageSize: [{
+    label: '100',
+    value: '100'
+  }],
+  page: 0,
+  query: [],
+  users: []
+};
+
+export const defaultUrlParamsForFiltersByLogsType = {
+  serviceLogs: {
+    timeRangeType: 'LAST',
+    timeRangeUnit: 'h',
+    timeRangeInterval: 1,
+    sortingKey: 'logtime',
+    sortingType: 'desc',
+    pageSize: '100',
+    page: '0'
+  },
+  auditLogs: {
+    timeRangeType: 'LAST',
+    timeRangeUnit: 'h',
+    timeRangeInterval: 1,
+    sortingKey: 'evtTime',
+    sortingType: 'desc',
+    pageSize: '100',
+    page: '0'
+  }
+};
+
 export const timeRangeFilterOptions = [{
     label: 'filter.timeRange.7d',
     value: {
@@ -237,46 +307,9 @@ export const timeRangeFilterOptions = [{
 @Injectable()
 export class LogsFilteringUtilsService {
 
-  readonly defaultFilterSelections = {
-    clusters: [],
-    timeRange: {
-      value: {
-        type: 'LAST',
-        unit: 'h',
-        interval: 1
-      },
-      label: 'filter.timeRange.1hr'
-    },
-    components: [],
-    levels: [],
-    hosts: [],
-    auditLogsSorting: {
-      label: 'sorting.time.desc',
-      value: {
-        key: 'evtTime',
-        type: 'desc'
-      }
-    },
-    serviceLogsSorting: {
-      label: 'sorting.time.desc',
-      value: {
-        key: 'logtime',
-        type: 'desc'
-      }
-    },
-    pageSize: [{
-      label: '100',
-      value: '100'
-    }],
-    page: 0,
-    query: [],
-    users: [],
-    isUndoOrRedo: false
-  };
-
   constructor(
     private utilsService: UtilsService
-  ) { }
+  ) {}
 
   getTimeRandeOptionsByGroup() {
     return timeRangeFilterOptions.reduce((groups: any, item: any) => {
@@ -395,7 +428,6 @@ export class LogsFilteringUtilsService {
 
   getParamsFromActiveFilter(activeFilter: any, activeLogsType: LogsType): {[key: string]: string} {
     const {...filters} = activeFilter;
-    delete filters.isUndoOrRedo;
     return Object.keys(filters).reduce((currentParams, key) => {
       const newParams = {
         ...currentParams
@@ -554,4 +586,34 @@ export class LogsFilteringUtilsService {
     return [tab.id, this.getParamsFromActiveFilter(tab.activeFilters || {}, logsType)];
   }
 
+  getUrlParamsDifferences(previousParams, currentParams): UrlParamDifferences[] {
+    const allKeys = [
+      ...Object.keys(previousParams),
+      ...Object.keys(currentParams)
+    ].reduce((uniques: string[], key: string) => {
+      return uniques.indexOf(key) === -1 ? [...uniques, key] : uniques;
+    }, []);
+    return allKeys.reduce((changes: UrlParamDifferences[], key: string) => {
+      const change: {[key: string]: any} = {};
+      if (previousParams[key] === undefined && currentParams[key] !== undefined) {
+        change.type = 'add';
+      } else if (previousParams[key] !== undefined && currentParams[key] === undefined) {
+        change.type = 'remove';
+      } else if (previousParams[key].toString() !== currentParams[key].toString()) {
+        change.type = 'change';
+      }
+      if (change.type) {
+        change.name = key;
+        change.from = previousParams[key];
+        change.to = currentParams[key];
+        return [...changes, <UrlParamDifferences>change];
+      }
+      return [...changes];
+    }, []);
+  }
+
+  private _getUrlParamValue(name: string, value: string): any[] | undefined {
+    return value !== undefined ? (name === 'query' ? JSON.parse(value) : value.split(',')) : undefined;
+  }
+
 }
diff --git a/ambari-logsearch-web/src/app/services/storage/reducers.service.ts b/ambari-logsearch-web/src/app/services/storage/reducers.service.ts
index 1d18406..4f72487 100644
--- a/ambari-logsearch-web/src/app/services/storage/reducers.service.ts
+++ b/ambari-logsearch-web/src/app/services/storage/reducers.service.ts
@@ -37,6 +37,7 @@ import {logsState} from '@app/services/storage/logs-state.service';
 import {dataAvailabilityStates} from '@app/modules/app-load/stores/data-availability-state.store';
 
 import * as auth from '@app/store/reducers/auth.reducers';
+import * as filterHistory from '@app/store/reducers/filter-history.reducers';
 
 export const reducers = {
   appSettings,
@@ -57,7 +58,8 @@ export const reducers = {
   clusterSelections,
   logsState,
   dataAvailabilityStates,
-  auth: auth.reducer
+  auth: auth.reducer,
+  filterHistory: filterHistory.reducer
 };
 
 export function reducer(state: any, action: any) {
diff --git a/ambari-logsearch-web/src/app/store/actions/filter-history.actions.ts b/ambari-logsearch-web/src/app/store/actions/filter-history.actions.ts
new file mode 100644
index 0000000..5fa278b
--- /dev/null
+++ b/ambari-logsearch-web/src/app/store/actions/filter-history.actions.ts
@@ -0,0 +1,56 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+import { Action } from '@ngrx/store';
+import { FilterUrlParamChange } from '@app/classes/models/filter-url-param-change.interface';
+import { LogsType } from '@app/classes/string';
+
+export enum FilterHistoryActionTypes {
+  ADD_FILTER_HISTORY = '[Filter History] Add',
+  SET_CURRENT_FILTER_HISTORY_BY_INDEX = '[Filter History] Set Current By Index',
+  SET_CURRENT_FILTER_HISTORY_BY_URL_PARAM = '[Filter History] Set Current By Url Param'
+}
+
+export class AddFilterHistoryAction implements Action {
+  readonly type = FilterHistoryActionTypes.ADD_FILTER_HISTORY;
+  constructor(public payload: {
+    logType: LogsType,
+    change: FilterUrlParamChange
+  }) {}
+}
+
+export class SetCurrentFilterHistoryByIndexAction implements Action {
+  readonly type = FilterHistoryActionTypes.SET_CURRENT_FILTER_HISTORY_BY_INDEX;
+  constructor(public payload: {
+    logType: LogsType,
+    index: number
+  }) {}
+}
+
+export class SetCurrentFilterHistoryByUrlParamAction implements Action {
+  readonly type = FilterHistoryActionTypes.SET_CURRENT_FILTER_HISTORY_BY_URL_PARAM;
+  constructor(public payload: {
+    logType: LogsType,
+    url: string
+  }) {}
+}
+
+export type FilterHistoryActions =
+  | AddFilterHistoryAction
+  | SetCurrentFilterHistoryByIndexAction
+  | SetCurrentFilterHistoryByUrlParamAction;
diff --git a/ambari-logsearch-web/src/app/store/reducers/filter-history.reducers.ts b/ambari-logsearch-web/src/app/store/reducers/filter-history.reducers.ts
new file mode 100644
index 0000000..41418bb
--- /dev/null
+++ b/ambari-logsearch-web/src/app/store/reducers/filter-history.reducers.ts
@@ -0,0 +1,111 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+import { FilterUrlParamChange } from '@app/classes/models/filter-url-param-change.interface';
+
+import { FilterHistoryActions, FilterHistoryActionTypes } from '../actions/filter-history.actions';
+
+export interface LogTypeFilterHistory {
+  changes: FilterUrlParamChange[];
+  currentChangeIndex: number;
+};
+
+export interface FilterHistoryState {
+  [key: string]: LogTypeFilterHistory;
+}
+
+export const initialState: FilterHistoryState = {};
+
+export function reducer(state = initialState, action: FilterHistoryActions): FilterHistoryState {
+  switch (action.type) {
+      case FilterHistoryActionTypes.ADD_FILTER_HISTORY: {
+        const payload = action.payload;
+        const history: LogTypeFilterHistory = state[payload.logType] ? {...state[payload.logType]} : {
+          changes: [],
+          currentChangeIndex: -1
+        };
+        // we throw away the 'rest' of the history changes when the user create a new filter
+        // while he/she did undo/redo so when the current index is pointing other than the last element
+        if (history.currentChangeIndex > -1 && history.currentChangeIndex < history.changes.length - 1) {
+          history.changes = [...history.changes.slice(0, history.currentChangeIndex + 1)];
+        }
+        history.changes = [...history.changes, payload.change];
+        history.currentChangeIndex = history.changes.length - 1;
+        return {
+          ...state,
+          [payload.logType]: history
+        };
+      }
+      case FilterHistoryActionTypes.SET_CURRENT_FILTER_HISTORY_BY_INDEX: {
+        const payload = action.payload;
+        const history: LogTypeFilterHistory = state[payload.logType];
+        const subState = {};
+        if (history && history.changes.length > payload.index) {
+          subState[payload.logType] = {
+            ...history,
+            currentChangeIndex: payload.index
+          };
+          return {
+            ...state,
+            ...subState
+          };
+        }
+        return state;
+      }
+      case FilterHistoryActionTypes.SET_CURRENT_FILTER_HISTORY_BY_URL_PARAM: {
+        const payload = action.payload;
+        const {logType, url} = payload;
+        const history: LogTypeFilterHistory = state[logType];
+        const subState = {};
+        if (history && history.changes.length) {
+          const index = history.changes.findIndex((change) => change.currentPath === url);
+          subState[logType] = {
+            ...history,
+            currentChangeIndex: index > -1 ? index : history.currentChangeIndex
+          };
+          return {
+            ...state,
+            ...subState
+          };
+        }
+        return state;
+      }
+      default: {
+        return state;
+      }
+  };
+}
+
+export const createFilterHistoryGetterById = (id: string): (state: FilterHistoryState) => LogTypeFilterHistory => {
+  return (state: FilterHistoryState): LogTypeFilterHistory => state[id];
+};
+export const getServiceLogsFilterHistory = createFilterHistoryGetterById('serviceLogs');
+export const getAuditLogsFilterHistory = createFilterHistoryGetterById('auditLogs');
+
+export const getFilterHistoryList = (filterHistory: LogTypeFilterHistory): FilterUrlParamChange[] => filterHistory.changes;
+export const getFilterHistoryCurrentChangeIndex = (filterHistory: LogTypeFilterHistory): number => filterHistory.currentChangeIndex;
+
+export const getUndoHistoryList = (filterHistory: LogTypeFilterHistory): FilterUrlParamChange[] => (
+  getFilterHistoryList(filterHistory).slice(0, getFilterHistoryCurrentChangeIndex(filterHistory))
+);
+export const getRedoHistoryList = (filterHistory: LogTypeFilterHistory): FilterUrlParamChange[] => (
+  getFilterHistoryList(filterHistory).slice(getFilterHistoryCurrentChangeIndex(filterHistory) + 1)
+);
+export const getCurrentFilterUrlParamChange = (filterHistory: LogTypeFilterHistory): FilterUrlParamChange => (
+  getFilterHistoryList(filterHistory)[getFilterHistoryCurrentChangeIndex(filterHistory)]
+);
diff --git a/ambari-logsearch-web/src/app/store/selectors/auth.selectors.ts b/ambari-logsearch-web/src/app/store/selectors/app-state.selectors.ts
similarity index 52%
copy from ambari-logsearch-web/src/app/store/selectors/auth.selectors.ts
copy to ambari-logsearch-web/src/app/store/selectors/app-state.selectors.ts
index 23decd9..4262489 100644
--- a/ambari-logsearch-web/src/app/store/selectors/auth.selectors.ts
+++ b/ambari-logsearch-web/src/app/store/selectors/app-state.selectors.ts
@@ -16,32 +16,26 @@
  * limitations under the License.
  */
 
-import { createSelector } from 'reselect';
+import { createSelector, Selector } from 'reselect';
 
-import * as fromAuth from '@app/store/reducers/auth.reducers';
 import { AppStore } from '@app/classes/models/store';
+import { AppState } from '@app/classes/models/app-state';
+import { LogsType } from '@app/classes/string';
+import { ActiveServiceLogEntry } from '@app/classes/active-service-log-entry';
 
-export const getAuthState = (state: AppStore): fromAuth.State => state.auth;
+export const selectAppState = (state: AppStore): AppState => state.appState;
 
-export const authStatusSelector = createSelector( getAuthState, fromAuth.getStatus );
-export const authMessageSelector = createSelector( getAuthState, fromAuth.getMessage );
-
-export const isAuthorizedSelector = createSelector(
-  authStatusSelector,
-  fromAuth.isAuthorized
-);
-
-export const isLoginInProgressSelector = createSelector(
-  authStatusSelector,
-  fromAuth.isLoginInProgress
+export const selectActiveLogsType: Selector<AppStore, LogsType> = createSelector(
+  selectAppState,
+  (appState: AppState): LogsType => appState.activeLogsType
 );
 
-export const isLoggedOutSelector = createSelector(
-  authStatusSelector,
-  fromAuth.isLoggedOut
+export const selectActiveLog: Selector<AppStore, ActiveServiceLogEntry> = createSelector(
+  selectAppState,
+  (appState: AppState): ActiveServiceLogEntry => appState.activeLog
 );
 
-export const isCheckingAuthStatusInProgressSelector = createSelector(
-  authStatusSelector,
-  fromAuth.isCheckingAuthInProgress
+export const selectActiveLogId: Selector<AppStore, string> = createSelector(
+  selectActiveLog,
+  (activeLog: ActiveServiceLogEntry): string => activeLog.id
 );
diff --git a/ambari-logsearch-web/src/app/store/selectors/audit-logs-fields.selectors.ts b/ambari-logsearch-web/src/app/store/selectors/audit-logs-fields.selectors.ts
new file mode 100644
index 0000000..60eddcc
--- /dev/null
+++ b/ambari-logsearch-web/src/app/store/selectors/audit-logs-fields.selectors.ts
@@ -0,0 +1,51 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+import { createSelector, Selector } from 'reselect';
+import { AppStore } from '@app/classes/models/store';
+import { LogField } from '@app/classes/object';
+import { ResponseRootProperties } from '@app/services/storage/audit-logs-fields.service';
+
+export const selectAuditLogsFieldState = (state: AppStore): LogField[] => state.auditLogsFields;
+
+export const selectDefaultAuditLogsFields = createSelector(
+  selectAuditLogsFieldState,
+  (root) => root && root[ResponseRootProperties.DEFAULTS]
+);
+
+export const createAuditLogsFieldComponentFieldsSelectorByComponentName = (componentName: string): Selector<AppStore, LogField[]> => (
+    createSelector(
+    selectAuditLogsFieldState,
+    (root): LogField[] => {
+      const overrides = root[ResponseRootProperties.OVERRIDES];
+      return (overrides && overrides[componentName]) || root[ResponseRootProperties.DEFAULTS];
+    }
+  )
+);
+
+export const createAuditLogsFieldLabelSelectorByComponentNameAndFieldName = (
+  componentName: string,
+  fieldName: string
+): Selector<AppStore, string> => createSelector(
+  selectAuditLogsFieldState,
+  createAuditLogsFieldComponentFieldsSelectorByComponentName(componentName),
+  (fields: LogField[]): string => {
+    const field: LogField = fields.find((nextField: LogField) => nextField.name === fieldName);
+    return field ? field.label : fieldName;
+  }
+);
diff --git a/ambari-logsearch-web/src/app/store/selectors/auth.selectors.ts b/ambari-logsearch-web/src/app/store/selectors/auth.selectors.ts
index 23decd9..1899bee 100644
--- a/ambari-logsearch-web/src/app/store/selectors/auth.selectors.ts
+++ b/ambari-logsearch-web/src/app/store/selectors/auth.selectors.ts
@@ -21,27 +21,27 @@ import { createSelector } from 'reselect';
 import * as fromAuth from '@app/store/reducers/auth.reducers';
 import { AppStore } from '@app/classes/models/store';
 
-export const getAuthState = (state: AppStore): fromAuth.State => state.auth;
+export const selectAuthState = (state: AppStore): fromAuth.State => state.auth;
 
-export const authStatusSelector = createSelector( getAuthState, fromAuth.getStatus );
-export const authMessageSelector = createSelector( getAuthState, fromAuth.getMessage );
+export const selectAuthStatus = createSelector( selectAuthState, fromAuth.getStatus );
+export const selectAuthMessage = createSelector( selectAuthState, fromAuth.getMessage );
 
 export const isAuthorizedSelector = createSelector(
-  authStatusSelector,
+  selectAuthStatus,
   fromAuth.isAuthorized
 );
 
 export const isLoginInProgressSelector = createSelector(
-  authStatusSelector,
+  selectAuthStatus,
   fromAuth.isLoginInProgress
 );
 
 export const isLoggedOutSelector = createSelector(
-  authStatusSelector,
+  selectAuthStatus,
   fromAuth.isLoggedOut
 );
 
 export const isCheckingAuthStatusInProgressSelector = createSelector(
-  authStatusSelector,
+  selectAuthStatus,
   fromAuth.isCheckingAuthInProgress
 );
diff --git a/ambari-logsearch-web/src/app/modules/shared/animations.less b/ambari-logsearch-web/src/app/store/selectors/components.selectors.ts
similarity index 59%
copy from ambari-logsearch-web/src/app/modules/shared/animations.less
copy to ambari-logsearch-web/src/app/store/selectors/components.selectors.ts
index 5b8a04c..6788493 100644
--- a/ambari-logsearch-web/src/app/modules/shared/animations.less
+++ b/ambari-logsearch-web/src/app/store/selectors/components.selectors.ts
@@ -15,19 +15,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@keyframes rotateplane {
-  0% {
-    transform: perspective(120px) rotateX(0deg) rotateY(0deg);
-  } 50% {
-    transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
-  } 100% {
-    transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
-  }
-}
 
-.square-spinner(@size: 40px, @background: #3FAE2A, @speed: 1.2s) {
-  width: @size;
-  height: @size;
-  background: @background;
-  animation: rotateplane @speed infinite ease-in-out;
-}
+import { createSelector, Selector } from 'reselect';
+
+import { AppStore } from '@app/classes/models/store';
+import { NodeItem } from '@app/classes/models/node-item';
+
+export const selectComponentsList = (state: AppStore): NodeItem[] => state.components;
+
+export const selectComponentsLabels = createSelector(
+  selectComponentsList,
+  (components: NodeItem[]) => (components || []).reduce((labels: {[key: string]: string}, component: NodeItem) => (
+    {
+      ...labels,
+      [component.name]: component.label || component.name
+    }
+  ), {})
+);
diff --git a/ambari-logsearch-web/src/app/store/selectors/data-availability.selectors.ts b/ambari-logsearch-web/src/app/store/selectors/data-availability.selectors.ts
new file mode 100644
index 0000000..0b0fde5
--- /dev/null
+++ b/ambari-logsearch-web/src/app/store/selectors/data-availability.selectors.ts
@@ -0,0 +1,49 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+import { createSelector } from 'reselect';
+
+import { AppStore } from '@app/classes/models/store';
+import { DataAvaibilityStatesModel } from '@modules/app-load/models/data-availability-state.model';
+import { DataAvailabilityValues } from '@app/classes/string';
+import { DataStateStoreKeys, baseDataKeys } from '@app/modules/app-load/services/app-load.service';
+
+export const selectDataAvailabilityState = (state: AppStore): DataAvaibilityStatesModel => state.dataAvailabilityStates;
+
+export const selectBaseDataAvailability = createSelector(
+  selectDataAvailabilityState,
+  (dataAvailabilityState: DataAvaibilityStatesModel): DataAvailabilityValues => {
+    const values: DataAvailabilityValues[] = Object.keys(dataAvailabilityState)
+          .filter((key: DataStateStoreKeys): boolean => baseDataKeys.indexOf(key) > -1)
+          .map((key): DataAvailabilityValues => dataAvailabilityState[key]);
+        let nextDataState: DataAvailabilityValues = DataAvailabilityValues.NOT_AVAILABLE;
+        if (values.indexOf(DataAvailabilityValues.ERROR) > -1) {
+          nextDataState = DataAvailabilityValues.ERROR;
+        } else if (values.indexOf(DataAvailabilityValues.LOADING) > -1) {
+          nextDataState = DataAvailabilityValues.LOADING;
+        } else if ( values.filter((value: DataAvailabilityValues) => value !== DataAvailabilityValues.AVAILABLE).length === 0 ) {
+          nextDataState = DataAvailabilityValues.AVAILABLE;
+        }
+        return nextDataState;
+  }
+);
+
+export const isBaseDataAvailable = createSelector(
+  selectBaseDataAvailability,
+  (baseDataAvailabilityState: DataAvailabilityValues) => baseDataAvailabilityState === DataAvailabilityValues.AVAILABLE
+);
diff --git a/ambari-logsearch-web/src/app/store/selectors/filter-history.selectors.ts b/ambari-logsearch-web/src/app/store/selectors/filter-history.selectors.ts
new file mode 100644
index 0000000..fd88c4a
--- /dev/null
+++ b/ambari-logsearch-web/src/app/store/selectors/filter-history.selectors.ts
@@ -0,0 +1,94 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+import { createSelector, Selector } from 'reselect';
+
+import { AppStore } from '@app/classes/models/store';
+import * as fromFilterHistoryReducers from '@app/store/reducers/filter-history.reducers';
+import { FilterUrlParamChange } from '@app/classes/models/filter-url-param-change.interface';
+import * as fromAppStateSelector from '@app/store/selectors/app-state.selectors';
+
+
+export const selectFilterHistoryState = (state: AppStore): fromFilterHistoryReducers.FilterHistoryState => state.filterHistory;
+
+export const selectActiveFilterHistory = createSelector(
+  selectFilterHistoryState,
+  fromAppStateSelector.selectActiveLogsType,
+  (filterHistoryState, activeLogsType): fromFilterHistoryReducers.LogTypeFilterHistory => (
+    filterHistoryState && filterHistoryState[activeLogsType]
+  )
+);
+
+export const selectActiveFilterHistoryChangeIndex = createSelector(
+  selectActiveFilterHistory,
+  (logTypeFilterHistory: fromFilterHistoryReducers.LogTypeFilterHistory): number => (
+    logTypeFilterHistory && logTypeFilterHistory.currentChangeIndex
+  )
+);
+
+export const selectActiveFilterHistoryChanges = createSelector(
+  selectActiveFilterHistory,
+  (logTypeFilterHistory: fromFilterHistoryReducers.LogTypeFilterHistory): FilterUrlParamChange[] => (
+    logTypeFilterHistory && logTypeFilterHistory.changes
+  )
+);
+
+export const selectActiveFilterHistoryChangesUndoItems = createSelector(
+  selectActiveFilterHistoryChanges,
+  selectActiveFilterHistoryChangeIndex,
+  (items: FilterUrlParamChange[], changeIndex: number): FilterUrlParamChange[] => items && items.slice(0, changeIndex)
+);
+
+export const selectActiveFilterHistoryChangesRedoItems = createSelector(
+  selectActiveFilterHistoryChanges,
+  selectActiveFilterHistoryChangeIndex,
+  (items: FilterUrlParamChange[], changeIndex: number): FilterUrlParamChange[] => items && items.slice(changeIndex + 1)
+);
+
+export const createFilterHistorySelectorById = (id: string): Selector<AppStore, fromFilterHistoryReducers.LogTypeFilterHistory> => (
+  createSelector(
+    selectFilterHistoryState,
+    (filterHistoryState): fromFilterHistoryReducers.LogTypeFilterHistory => filterHistoryState[id]
+  )
+);
+
+export const createFilterHistoryChangeIndexSelectorById = (id: string): Selector<AppStore, number> => createSelector(
+  createFilterHistorySelectorById(id),
+  (logTypeFilterHistory: fromFilterHistoryReducers.LogTypeFilterHistory): number => (
+    logTypeFilterHistory && logTypeFilterHistory.currentChangeIndex
+  )
+);
+
+export const createFilterHistoryChangesSelectorById = (id: string): Selector<AppStore, FilterUrlParamChange[]> => createSelector(
+  createFilterHistorySelectorById(id),
+  (logTypeFilterHistory: fromFilterHistoryReducers.LogTypeFilterHistory): FilterUrlParamChange[] => (
+    logTypeFilterHistory && logTypeFilterHistory.changes
+  )
+);
+
+export const createFilterHistoryChangesUndoItemsSelectorById = (id: string): Selector<AppStore, FilterUrlParamChange[]> => createSelector(
+  createFilterHistoryChangesSelectorById(id),
+  createFilterHistoryChangeIndexSelectorById(id),
+  (items: FilterUrlParamChange[], changeIndex: number): FilterUrlParamChange[] => items && items.slice(0, changeIndex)
+);
+
+export const createFilterHistoryChangesRedoItemsSelectorById = (id: string): Selector<AppStore, FilterUrlParamChange[]> => createSelector(
+  createFilterHistoryChangesSelectorById(id),
+  createFilterHistoryChangeIndexSelectorById(id),
+  (items: FilterUrlParamChange[], changeIndex: number): FilterUrlParamChange[] => items && items.slice(changeIndex + 1)
+);
diff --git a/ambari-logsearch-web/src/app/modules/shared/animations.less b/ambari-logsearch-web/src/app/store/selectors/service-logs-fields.selectors.ts
similarity index 57%
copy from ambari-logsearch-web/src/app/modules/shared/animations.less
copy to ambari-logsearch-web/src/app/store/selectors/service-logs-fields.selectors.ts
index 5b8a04c..6fa28af 100644
--- a/ambari-logsearch-web/src/app/modules/shared/animations.less
+++ b/ambari-logsearch-web/src/app/store/selectors/service-logs-fields.selectors.ts
@@ -15,19 +15,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@keyframes rotateplane {
-  0% {
-    transform: perspective(120px) rotateX(0deg) rotateY(0deg);
-  } 50% {
-    transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
-  } 100% {
-    transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
-  }
-}
 
-.square-spinner(@size: 40px, @background: #3FAE2A, @speed: 1.2s) {
-  width: @size;
-  height: @size;
-  background: @background;
-  animation: rotateplane @speed infinite ease-in-out;
-}
+import { createSelector, Selector } from 'reselect';
+import { AppStore } from '@app/classes/models/store';
+import { LogField } from '@app/classes/object';
+
+export const selectServiceLogsFieldState = (state: AppStore): LogField[] => state.serviceLogsFields;
+
+export const createServiceLogsFieldLabelSelectorByFieldName = (fieldName: string): Selector<AppStore, string> => createSelector(
+  selectServiceLogsFieldState,
+  (fields: LogField[]): string => {
+    const field: LogField = fields.find((nextField: LogField) => nextField.name === fieldName);
+    return field ? field.label : fieldName;
+  }
+);
diff --git a/ambari-logsearch-web/src/app/test-config.spec.ts b/ambari-logsearch-web/src/app/test-config.spec.ts
index 0bad5f8..bc77044 100644
--- a/ambari-logsearch-web/src/app/test-config.spec.ts
+++ b/ambari-logsearch-web/src/app/test-config.spec.ts
@@ -67,7 +67,7 @@ export const getCommonTestingBedConfiguration = (
 ) => ({
   imports: [
     ...TranslationModules,
-    RouterTestingModule,
+    // RouterTestingModule,
     StoreModule.provideStore({
       clusters,
       auth: auth.reducer
diff --git a/ambari-logsearch-web/src/assets/i18n/en.json b/ambari-logsearch-web/src/assets/i18n/en.json
index d8b3801..770f5e9 100644
--- a/ambari-logsearch-web/src/assets/i18n/en.json
+++ b/ambari-logsearch-web/src/assets/i18n/en.json
@@ -7,6 +7,7 @@
   "common.name": "Name",
   "common.value": "Value",
   "common.settings": "Settings",
+  "common.loading": "Loading...",
 
   "common.form.errors.required": "This field is required",
 
@@ -101,6 +102,85 @@
 
   "filter.timeRange.error.tooShort": "The selected time range is too short.",
 
+  "filterHistory": {
+    "initialState": "Initial filter values",
+    "paramNames": {
+      "clusters": "Cluster",
+      "timeRange": "Time Range",
+      "components": "Component",
+      "levels": "Level",
+      "hosts": "Host",
+      "auditLogsSorting": "Sort by",
+      "serviceLogsSorting": "Sort by",
+      "pageSize": "Result Per Page",
+      "page": "Page Number",
+      "query": "Query",
+      "users": "User",
+      "sortingType": "Sorting Direction",
+      "sortingKey": "Sorting Field"
+    },
+    "single": {
+      "actionLabel": {
+        "change": "changed"
+      },
+      "changeLabel": {
+        "change": "{{fieldLabel}}: {{valueLabel}}"
+      }
+    },
+    "multiple": {
+      "actionLabel": {
+        "add": "selected",
+        "change": "changed",
+        "remove": "unselected",
+        "clear": "selection cleared"
+      },
+      "changeLabel": {
+        "add": "{{fieldLabel}}: {{valueLabel}}",
+        "change": "{{fieldLabel}}: {{valueLabel}}",
+        "remove": "{{fieldLabel}}: {{valueLabel}}",
+        "clear": "{{fieldLabel}}: {{valueLabel}}"
+      }
+    },
+    "query": {
+      "type": {
+        "include": "include",
+        "exclude": "exclude"
+      },
+      "changeLabel": {
+        "add": "{{queryType}} query added: {{fieldLabel}} - {{valueLabel}}",
+        "remove": "{{queryType}} query removed: {{fieldLabel}} - {{valueLabel}}",
+        "clear": "Query cleared"
+      }
+    },
+    "timeRange": {
+      "type": {
+        "last": "last",
+        "past": "previous",
+        "current": "this",
+        "today": "today",
+        "custom": ""
+      },
+      "unit": {
+        "d": ["day", "days"],
+        "M": ["month", "months"],
+        "y": ["year", "years"],
+        "w": ["week", "weeks"],
+        "m": ["minute", "minutes"],
+        "h": ["hour", "hours"]
+      },
+      "valueLabel": {
+        "last": "{{typeLabel}} {{value}} {{unitLabel}}",
+        "past": "{{typeLabel}} {{value}} {{unitLabel}}",
+        "yesterday": "yesterday",
+        "current": "{{typeLabel}} {{value}} {{unitLabel}}",
+        "today": "today",
+        "custom": "`{{valueStart}} - {{valueEnd}}`"
+      },
+      "changeLabel": "{{fieldLabel}}: {{valueLabel}}",
+      "dateTimeFormat": "YYYY-MM-DD hh:mm A zz"
+    }
+  },
+
   "levels.fatal": "Fatal",
   "levels.error": "Error",
   "levels.warn": "Warn",
@@ -279,6 +359,20 @@
   "dataAvaibilityState.clustersDataState.label": "Loading clusters",
   "dataAvaibilityState.hostsDataState.label": "Loading hosts",
   "dataAvaibilityState.componentsDataState.label": "Loading components",
-  "dataAvaibilityState.hasError.message": "We were not able to load the data. Please check your internet connection and reload the page!"
+  "dataAvaibilityState.hasError.message": "We were not able to load the data. Please check your internet connection and reload the page!",
+
+  "urlParamsLabels": {
+    "clusters": "Clusters",
+    "timeRange": "Time range",
+    "components": "Components",
+    "levels": "Levels",
+    "hosts": "Hosts",
+    "auditLogsSorting": "Sort by",
+    "serviceLogsSorting": "Sort by",
+    "pageSize": "# of logs",
+    "page": "Page",
+    "query": "Query",
+    "users": "Users"
+  }
 
 }