You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by ab...@apache.org on 2017/10/19 12:47:08 UTC

[2/2] ambari git commit: AMBARI-22269 Log Search UI: provide navigation between Service and Audit Logs. (ababiichuk)

AMBARI-22269 Log Search UI: provide navigation between Service and Audit Logs. (ababiichuk)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/15cec1cb
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/15cec1cb
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/15cec1cb

Branch: refs/heads/trunk
Commit: 15cec1cb7d6a43361c82e2de31bf320a33005b37
Parents: b4eddc9
Author: ababiichuk <ab...@hortonworks.com>
Authored: Thu Oct 19 15:11:48 2017 +0300
Committer: ababiichuk <ab...@hortonworks.com>
Committed: Thu Oct 19 15:11:48 2017 +0300

----------------------------------------------------------------------
 .../ambari-logsearch-web/src/app/app.module.ts  |   4 +
 .../src/app/classes/filtering.ts                | 369 +++++++++++++++++++
 .../src/app/classes/models/app-state.ts         |  14 +-
 .../src/app/classes/models/store.ts             |  12 +-
 .../src/app/classes/models/tab.ts               |  53 +++
 .../collapsible-panel.component.spec.ts         |  14 +-
 .../dropdown-button.component.spec.ts           |  18 +-
 .../dropdown-list.component.spec.ts             |   7 +-
 .../filter-button.component.spec.ts             |  18 +-
 .../filter-dropdown.component.spec.ts           |  18 +-
 .../filters-panel/filters-panel.component.html  |  17 +-
 .../filters-panel.component.spec.ts             |   5 +-
 .../filters-panel/filters-panel.component.ts    |  44 ++-
 .../log-context/log-context.component.spec.ts   |   5 +-
 .../logs-container.component.html               |  17 +-
 .../logs-container.component.less               |   7 +-
 .../logs-container.component.spec.ts            |  11 +-
 .../logs-container/logs-container.component.ts  | 108 +++---
 .../logs-list/logs-list.component.html          |  24 +-
 .../components/logs-list/logs-list.component.ts |  25 +-
 .../main-container.component.html               |  10 +-
 .../main-container.component.less               |   4 -
 .../main-container.component.spec.ts            |  15 +-
 .../main-container/main-container.component.ts  |  51 +--
 .../menu-button/menu-button.component.spec.ts   |  18 +-
 .../pagination/pagination.component.html        |   2 +-
 .../pagination/pagination.component.ts          |   6 +-
 .../src/app/components/tabs/tabs.component.html |  25 ++
 .../src/app/components/tabs/tabs.component.less |  22 ++
 .../app/components/tabs/tabs.component.spec.ts  | 125 +++++++
 .../src/app/components/tabs/tabs.component.ts   |  48 +++
 .../time-histogram.component.less               |   1 +
 .../time-range-picker.component.spec.ts         |  13 +-
 .../time-range-picker.component.ts              |   4 +-
 .../timezone-picker.component.spec.ts           |  18 +-
 .../services/component-actions.service.spec.ts  |   5 +-
 .../app/services/component-actions.service.ts   |  33 +-
 .../component-generator.service.spec.ts         |   7 +-
 .../src/app/services/filtering.service.spec.ts  |   3 +
 .../src/app/services/filtering.service.ts       | 341 +----------------
 .../app/services/logs-container.service.spec.ts |   5 +-
 .../src/app/services/logs-container.service.ts  |  60 ++-
 .../app/services/storage/reducers.service.ts    |   4 +-
 .../src/app/services/storage/tabs.service.ts    |  33 ++
 .../src/assets/i18n/en.json                     |   2 +
 45 files changed, 1080 insertions(+), 565 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts b/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts
index 12b95a7..37cd869 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts
@@ -53,6 +53,7 @@ import {ClustersService} from '@app/services/storage/clusters.service';
 import {ComponentsService} from '@app/services/storage/components.service';
 import {ServiceLogsFieldsService} from '@app/services/storage/service-logs-fields.service';
 import {AuditLogsFieldsService} from '@app/services/storage/audit-logs-fields.service';
+import {TabsService} from '@app/services/storage/tabs.service';
 import {reducer} from '@app/services/storage/reducers.service';
 
 import {AppComponent} from '@app/components/app.component';
@@ -80,6 +81,7 @@ import {TimeRangePickerComponent} from '@app/components/time-range-picker/time-r
 import {DatePickerComponent} from '@app/components/date-picker/date-picker.component';
 import {LogContextComponent} from '@app/components/log-context/log-context.component';
 import {LogFileEntryComponent} from '@app/components/log-file-entry/log-file-entry.component';
+import {TabsComponent} from '@app/components/tabs/tabs.component';
 
 import {TimeZoneAbbrPipe} from '@app/pipes/timezone-abbr.pipe';
 import {TimerSecondsPipe} from '@app/pipes/timer-seconds.pipe';
@@ -131,6 +133,7 @@ export function getXHRBackend(injector: Injector, browser: BrowserXhr, xsrf: XSR
     DatePickerComponent,
     LogContextComponent,
     LogFileEntryComponent,
+    TabsComponent,
     TimeZoneAbbrPipe,
     TimerSecondsPipe
   ],
@@ -173,6 +176,7 @@ export function getXHRBackend(injector: Injector, browser: BrowserXhr, xsrf: XSR
     ComponentsService,
     ServiceLogsFieldsService,
     AuditLogsFieldsService,
+    TabsService,
     {
       provide: XHRBackend,
       useFactory: getXHRBackend,

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/classes/filtering.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/filtering.ts b/ambari-logsearch/ambari-logsearch-web/src/app/classes/filtering.ts
new file mode 100644
index 0000000..dde144b
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/filtering.ts
@@ -0,0 +1,369 @@
+/**
+ * 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 {FormGroup, FormControl} from '@angular/forms';
+import {ListItem} from '@app/classes/list-item';
+
+export interface TimeUnit {
+  type: 'CURRENT' | 'LAST' | 'PAST';
+  unit: 'ms' | 's' | 'm' | 'h' | 'd' | 'w' | 'M' | 'Y';
+  interval?: number;
+}
+
+export interface TimeUnitListItem extends ListItem {
+  value: TimeUnit;
+}
+
+export interface FilterCondition {
+  label?: string;
+  options?: (ListItem | TimeUnitListItem[])[];
+  defaultValue?: string | number | {[key: string]: any};
+  defaultLabel?: string;
+  iconClass?: string;
+}
+
+const paginationOptions: string[] = ['10', '25', '50', '100'];
+
+export const filters: {[key: string]: FilterCondition} = {
+  clusters: {
+    label: 'filter.clusters',
+    options: [],
+    defaultValue: ''
+  },
+  timeRange: {
+    options: [
+      [
+        {
+          label: 'filter.timeRange.7d',
+          value: {
+            type: 'LAST',
+            unit: 'd',
+            interval: 7
+          }
+        },
+        {
+          label: 'filter.timeRange.30d',
+          value: {
+            type: 'LAST',
+            unit: 'd',
+            interval: 30
+          }
+        },
+        {
+          label: 'filter.timeRange.60d',
+          value: {
+            type: 'LAST',
+            unit: 'd',
+            interval: 60
+          }
+        },
+        {
+          label: 'filter.timeRange.90d',
+          value: {
+            type: 'LAST',
+            unit: 'd',
+            interval: 90
+          }
+        },
+        {
+          label: 'filter.timeRange.6m',
+          value: {
+            type: 'LAST',
+            unit: 'M',
+            interval: 6
+          }
+        },
+        {
+          label: 'filter.timeRange.1y',
+          value: {
+            type: 'LAST',
+            unit: 'Y',
+            interval: 1
+          }
+        },
+        {
+          label: 'filter.timeRange.2y',
+          value: {
+            type: 'LAST',
+            unit: 'Y',
+            interval: 2
+          }
+        },
+        {
+          label: 'filter.timeRange.5y',
+          value: {
+            type: 'LAST',
+            unit: 'Y',
+            interval: 5
+          }
+        }
+      ],
+      [
+        {
+          label: 'filter.timeRange.yesterday',
+          value: {
+            type: 'PAST',
+            unit: 'd'
+          }
+        },
+        // TODO implement time range calculation
+        /*
+         {
+         label: 'filter.timeRange.beforeYesterday',
+         value: {
+         type: 'PAST',
+         unit: 'd'
+         }
+         },
+         {
+         label: 'filter.timeRange.thisDayLastWeek',
+         value: {
+         type: 'PAST',
+         unit: 'd'
+         }
+         },
+         */
+        {
+          label: 'filter.timeRange.previousWeek',
+          value: {
+            type: 'PAST',
+            unit: 'w'
+          }
+        },
+        {
+          label: 'filter.timeRange.previousMonth',
+          value: {
+            type: 'PAST',
+            unit: 'M'
+          }
+        },
+        {
+          label: 'filter.timeRange.previousYear',
+          value: {
+            type: 'PAST',
+            unit: 'Y'
+          }
+        }
+      ],
+      [
+        {
+          label: 'filter.timeRange.today',
+          value: {
+            type: 'CURRENT',
+            unit: 'd'
+          }
+        },
+        {
+          label: 'filter.timeRange.thisWeek',
+          value: {
+            type: 'CURRENT',
+            unit: 'w'
+          }
+        },
+        {
+          label: 'filter.timeRange.thisMonth',
+          value: {
+            type: 'CURRENT',
+            unit: 'M'
+          }
+        },
+        {
+          label: 'filter.timeRange.thisYear',
+          value: {
+            type: 'CURRENT',
+            unit: 'Y'
+          }
+        }
+      ],
+      [
+        {
+          label: 'filter.timeRange.5min',
+          value: {
+            type: 'LAST',
+            unit: 'm',
+            interval: 5
+          }
+        },
+        {
+          label: 'filter.timeRange.15min',
+          value: {
+            type: 'LAST',
+            unit: 'm',
+            interval: 15
+          }
+        },
+        {
+          label: 'filter.timeRange.30min',
+          value: {
+            type: 'LAST',
+            unit: 'm',
+            interval: 30
+          }
+        },
+        {
+          label: 'filter.timeRange.1hr',
+          value: {
+            type: 'LAST',
+            unit: 'h',
+            interval: 1
+          }
+        },
+        {
+          label: 'filter.timeRange.3hr',
+          value: {
+            type: 'LAST',
+            unit: 'h',
+            interval: 3
+          }
+        },
+        {
+          label: 'filter.timeRange.6hr',
+          value: {
+            type: 'LAST',
+            unit: 'h',
+            interval: 6
+          }
+        },
+        {
+          label: 'filter.timeRange.12hr',
+          value: {
+            type: 'LAST',
+            unit: 'h',
+            interval: 12
+          }
+        },
+        {
+          label: 'filter.timeRange.24hr',
+          value: {
+            type: 'LAST',
+            unit: 'h',
+            interval: 24
+          }
+        },
+      ]
+    ],
+    defaultValue: {
+      type: 'LAST',
+      unit: 'h',
+      interval: 1
+    },
+    defaultLabel: 'filter.timeRange.1hr'
+  },
+  components: {
+    label: 'filter.components',
+    iconClass: 'fa fa-cubes',
+    options: [],
+    defaultValue: ''
+  },
+  levels: {
+    label: 'filter.levels',
+    iconClass: 'fa fa-sort-amount-asc',
+    options: [
+      {
+        label: 'levels.fatal',
+        value: 'FATAL'
+      },
+      {
+        label: 'levels.error',
+        value: 'ERROR'
+      },
+      {
+        label: 'levels.warn',
+        value: 'WARN'
+      },
+      {
+        label: 'levels.info',
+        value: 'INFO'
+      },
+      {
+        label: 'levels.debug',
+        value: 'DEBUG'
+      },
+      {
+        label: 'levels.trace',
+        value: 'TRACE'
+      },
+      {
+        label: 'levels.unknown',
+        value: 'UNKNOWN'
+      }
+    ],
+    defaultValue: ''
+  },
+  hosts: {
+    label: 'filter.hosts',
+    iconClass: 'fa fa-server',
+    options: [],
+    defaultValue: ''
+  },
+  sorting: {
+    label: 'sorting.title',
+    options: [
+      {
+        label: 'sorting.time.asc',
+        value: {
+          key: 'logtime',
+          type: 'asc'
+        }
+      },
+      {
+        label: 'sorting.time.desc',
+        value: {
+          key: 'logtime',
+          type: 'desc'
+        }
+      }
+    ],
+    defaultValue: '',
+    defaultLabel: ''
+  },
+  pageSize: {
+    label: 'pagination.title',
+    options: paginationOptions.map((option: string): ListItem => {
+      return {
+        label: option,
+        value: option
+      }
+    }),
+    defaultValue: '10',
+    defaultLabel: '10'
+  },
+  page: {
+    defaultValue: 0
+  },
+  query: {}
+};
+
+export const filtersFormItemsMap: {[key: string]: string[]} = {
+  serviceLogs: ['clusters', 'timeRange', 'components', 'levels', 'hosts', 'sorting', 'pageSize', 'page', 'query'],
+  auditLogs: ['clusters', 'timeRange', 'sorting', 'pageSize', 'page', 'query'] // TODO add all the required fields
+};
+
+export const getFiltersForm = (listType: string): FormGroup => {
+  const itemsList = filtersFormItemsMap[listType],
+    keys = Object.keys(filters).filter((key: string): boolean => itemsList.indexOf(key) > -1),
+    items = keys.reduce((currentObject: any, key: string): any => {
+      let formControl = new FormControl(),
+        item = {
+          [key]: formControl
+        };
+      formControl.setValue(filters[key].defaultValue);
+      return Object.assign(currentObject, item);
+    }, {});
+  return new FormGroup(items);
+};

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/app-state.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/app-state.ts b/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/app-state.ts
index beeb670..2c5c083 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/app-state.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/app-state.ts
@@ -16,28 +16,28 @@
  * limitations under the License.
  */
 
+import {FormGroup} from '@angular/forms';
 import {ActiveServiceLogEntry} from '@app/classes/active-service-log-entry';
+import {Tab, initialTabs} from '@app/classes/models/tab';
 
 export interface AppState {
   isAuthorized: boolean;
   isInitialLoading: boolean;
   isLoginInProgress: boolean;
-  isAuditLogsSet: boolean;
-  isServiceLogsSet: boolean;
   activeLogsType?: string;
   isServiceLogsFileView: boolean;
   isServiceLogContextView: boolean;
   activeLog: ActiveServiceLogEntry | null;
+  activeFiltersForm: FormGroup;
 }
 
 export const initialState: AppState = {
   isAuthorized: false,
   isInitialLoading: false,
   isLoginInProgress: false,
-  isAuditLogsSet: false,
-  isServiceLogsSet: false,
-  activeLogsType: 'serviceLogs', // TODO implement setting the parameter depending on user's navigation
+  activeLogsType: 'serviceLogs',
   isServiceLogsFileView: false,
   isServiceLogContextView: false,
-  activeLog: null
-}
+  activeLog: null,
+  activeFiltersForm: initialTabs.find((tab: Tab): boolean => tab.isActive).appState.activeFiltersForm
+};

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/store.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/store.ts b/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/store.ts
index c62d3ee..d912b35 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/store.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/store.ts
@@ -29,6 +29,7 @@ import {UserConfig} from '@app/classes/models/user-config';
 import {Filter} from '@app/classes/models/filter';
 import {AuditLogField} from '@app/classes/models/audit-log-field';
 import {ServiceLogField} from '@app/classes/models/service-log-field';
+import {Tab} from '@app/classes/models/tab';
 
 export const storeActions = {
   'ARRAY.ADD': 'ADD',
@@ -56,6 +57,7 @@ export interface AppStore {
   components: Node[];
   serviceLogsFields: ServiceLogField[];
   auditLogsFields: AuditLogField[];
+  tabs: Tab[];
 }
 
 export class ModelService {
@@ -115,7 +117,7 @@ export class CollectionModelService extends ModelService {
     });
   }
 
-  mapCollection(modifier: (item: any) => {}): void {
+  mapCollection(modifier: (item: any) => any): void {
     this.store.dispatch({
       type: `${storeActions['ARRAY.MAP']}_${this.modelName}`,
       payload: {
@@ -124,6 +126,14 @@ export class CollectionModelService extends ModelService {
     });
   }
 
+  findInCollection(findFunction): Observable<any> {
+    return this.getAll().map((result: any[]): any => result.find(findFunction));
+  }
+
+  filterCollection(filterFunction): Observable<any[]> {
+    return this.getAll().map((result: any[]): any[] => result.filter(filterFunction));
+  }
+
 }
 
 export class ObjectModelService extends ModelService {

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/tab.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/tab.ts b/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/tab.ts
new file mode 100644
index 0000000..bb8028a
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/tab.ts
@@ -0,0 +1,53 @@
+/**
+ * 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 {getFiltersForm} from '@app/classes/filtering';
+
+export interface Tab {
+  id: string;
+  type: string;
+  isActive: boolean;
+  isCloseable?: boolean;
+  label: string;
+  appState: any;
+}
+
+export const initialTabs: Tab[] = [
+  {
+    id: 'serviceLogs',
+    type: 'serviceLogs',
+    isActive: true,
+    label: 'common.serviceLogs',
+    appState: {
+      activeLogsType: 'serviceLogs',
+      isServiceLogsFileView: false,
+      activeFiltersForm: getFiltersForm('serviceLogs')
+    }
+  },
+  {
+    id: 'auditLogs',
+    type: 'auditLogs',
+    isActive: false,
+    label: 'common.auditLogs',
+    appState: {
+      activeLogsType: 'auditLogs',
+      isServiceLogsFileView: false,
+      activeFiltersForm: getFiltersForm('auditLogs')
+    }
+  }
+];

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/collapsible-panel/collapsible-panel.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/collapsible-panel/collapsible-panel.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/collapsible-panel/collapsible-panel.component.spec.ts
index 60b7d63..5f5f1b0 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/collapsible-panel/collapsible-panel.component.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/collapsible-panel/collapsible-panel.component.spec.ts
@@ -21,7 +21,6 @@ import {By} from '@angular/platform-browser';
 import {TranslationModules} from '@app/test-config.spec';
 import {HttpClientService} from '@app/services/http-client.service';
 
-//import {AppModule} from '@app/app.module';
 import {CollapsiblePanelComponent} from './collapsible-panel.component';
 
 describe('CollapsiblePanelComponent', () => {
@@ -31,11 +30,22 @@ describe('CollapsiblePanelComponent', () => {
   let el: HTMLElement;
 
   beforeEach(async(() => {
+    const httpClient = {
+      get: () => {
+        return {
+          subscribe: () => {
+          }
+        }
+      }
+    };
     TestBed.configureTestingModule({
       declarations: [CollapsiblePanelComponent],
       imports: TranslationModules,
       providers: [
-        HttpClientService
+        {
+          provide: HttpClientService,
+          useValue: httpClient
+        }
       ]
     })
     .compileComponents();

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.spec.ts
index e795986..f11ca09 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.spec.ts
@@ -31,6 +31,7 @@ import {ServiceLogsService, serviceLogs} from '@app/services/storage/service-log
 import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.service';
 import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/services/storage/service-logs-histogram-data.service';
 import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service';
+import {TabsService, tabs} from '@app/services/storage/tabs.service';
 import {FilteringService} from '@app/services/filtering.service';
 import {UtilsService} from '@app/services/utils.service';
 import {ComponentActionsService} from '@app/services/component-actions.service';
@@ -44,6 +45,14 @@ describe('DropdownButtonComponent', () => {
   let fixture: ComponentFixture<DropdownButtonComponent>;
 
   beforeEach(async(() => {
+    const httpClient = {
+      get: () => {
+        return {
+          subscribe: () => {
+          }
+        }
+      }
+    };
     TestBed.configureTestingModule({
       declarations: [DropdownButtonComponent],
       imports: [
@@ -58,7 +67,8 @@ describe('DropdownButtonComponent', () => {
           serviceLogs,
           serviceLogsFields,
           serviceLogsHistogramData,
-          serviceLogsTruncated
+          serviceLogsTruncated,
+          tabs
         }),
         ...TranslationModules
       ],
@@ -74,10 +84,14 @@ describe('DropdownButtonComponent', () => {
         ServiceLogsFieldsService,
         ServiceLogsHistogramDataService,
         ServiceLogsTruncatedService,
+        TabsService,
         FilteringService,
         UtilsService,
         ComponentActionsService,
-        HttpClientService,
+        {
+          provide: HttpClientService,
+          useValue: httpClient
+        },
         LogsContainerService
       ],
       schemas: [NO_ERRORS_SCHEMA]

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.spec.ts
index 759a0e1..5455e67 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.spec.ts
@@ -30,6 +30,7 @@ import {AppStateService, appState} from '@app/services/storage/app-state.service
 import {ClustersService, clusters} from '@app/services/storage/clusters.service';
 import {ComponentsService, components} from '@app/services/storage/components.service';
 import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service';
+import {TabsService, tabs} from '@app/services/storage/tabs.service';
 import {ComponentGeneratorService} from '@app/services/component-generator.service';
 import {LogsContainerService} from '@app/services/logs-container.service';
 import {HttpClientService} from '@app/services/http-client.service';
@@ -66,7 +67,8 @@ describe('DropdownListComponent', () => {
           appState,
           clusters,
           components,
-          serviceLogsTruncated
+          serviceLogsTruncated,
+          tabs
         })
       ],
       providers: [
@@ -88,7 +90,8 @@ describe('DropdownListComponent', () => {
         AppStateService,
         ClustersService,
         ComponentsService,
-        ServiceLogsTruncatedService
+        ServiceLogsTruncatedService,
+        TabsService
       ]
     })
     .compileComponents();

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.spec.ts
index 4e6f460..3e40455 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.spec.ts
@@ -31,6 +31,7 @@ import {ServiceLogsService, serviceLogs} from '@app/services/storage/service-log
 import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.service';
 import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/services/storage/service-logs-histogram-data.service';
 import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service';
+import {TabsService, tabs} from '@app/services/storage/tabs.service';
 import {ComponentActionsService} from '@app/services/component-actions.service';
 import {FilteringService} from '@app/services/filtering.service';
 import {UtilsService} from '@app/services/utils.service';
@@ -44,6 +45,14 @@ describe('FilterButtonComponent', () => {
   let fixture: ComponentFixture<FilterButtonComponent>;
 
   beforeEach(async(() => {
+    const httpClient = {
+      get: () => {
+        return {
+          subscribe: () => {
+          }
+        }
+      }
+    };
     TestBed.configureTestingModule({
       declarations: [FilterButtonComponent],
       imports: [
@@ -58,7 +67,8 @@ describe('FilterButtonComponent', () => {
           serviceLogs,
           serviceLogsFields,
           serviceLogsHistogramData,
-          serviceLogsTruncated
+          serviceLogsTruncated,
+          tabs
         }),
         ...TranslationModules
       ],
@@ -74,10 +84,14 @@ describe('FilterButtonComponent', () => {
         ServiceLogsFieldsService,
         ServiceLogsHistogramDataService,
         ServiceLogsTruncatedService,
+        TabsService,
         ComponentActionsService,
         FilteringService,
         UtilsService,
-        HttpClientService,
+        {
+          provide: HttpClientService,
+          useValue: httpClient
+        },
         LogsContainerService
       ],
       schemas: [NO_ERRORS_SCHEMA]

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-dropdown/filter-dropdown.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-dropdown/filter-dropdown.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-dropdown/filter-dropdown.component.spec.ts
index f5b9330..c294e8e 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-dropdown/filter-dropdown.component.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-dropdown/filter-dropdown.component.spec.ts
@@ -27,6 +27,7 @@ import {ServiceLogsService, serviceLogs} from '@app/services/storage/service-log
 import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.service';
 import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/services/storage/service-logs-histogram-data.service';
 import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service';
+import {TabsService, tabs} from '@app/services/storage/tabs.service';
 import {FilteringService} from '@app/services/filtering.service';
 import {UtilsService} from '@app/services/utils.service';
 import {ComponentActionsService} from '@app/services/component-actions.service';
@@ -56,6 +57,14 @@ describe('FilterDropdownComponent', () => {
   };
 
   beforeEach(async(() => {
+    const httpClient = {
+      get: () => {
+        return {
+          subscribe: () => {
+          }
+        }
+      }
+    };
     TestBed.configureTestingModule({
       declarations: [FilterDropdownComponent],
       imports: [
@@ -67,7 +76,8 @@ describe('FilterDropdownComponent', () => {
           serviceLogs,
           serviceLogsFields,
           serviceLogsHistogramData,
-          serviceLogsTruncated
+          serviceLogsTruncated,
+          tabs
         }),
         ...TranslationModules
       ],
@@ -80,6 +90,7 @@ describe('FilterDropdownComponent', () => {
         ServiceLogsFieldsService,
         ServiceLogsHistogramDataService,
         ServiceLogsTruncatedService,
+        TabsService,
         {
           provide: FilteringService,
           useValue: filtering
@@ -87,7 +98,10 @@ describe('FilterDropdownComponent', () => {
         UtilsService,
         ComponentActionsService,
         LogsContainerService,
-        HttpClientService
+        {
+          provide: HttpClientService,
+          useValue: httpClient
+        }
       ],
       schemas: [NO_ERRORS_SCHEMA]
     })

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html
index 22ec8fe..fa739a4 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html
@@ -15,7 +15,7 @@
   limitations under the License.
 -->
 
-<form class="col-md-12" [formGroup]="filtersForm">
+<form [formGroup]="filtersForm">
   <div class="form-inline filter-input-container col-md-8">
     <filter-dropdown [label]="filters.clusters.label" formControlName="clusters" [options]="filters.clusters.options"
                      [defaultLabel]="filters.clusters.defaultLabel" [isMultipleChoice]="true"
@@ -33,15 +33,16 @@
   <div class="filter-buttons col-md-4">
     <dropdown-button [options]="searchBoxItems" iconClass="fa fa-search-minus" label="filter.excluded"
                      [hideCaret]="true" [showSelectedValue]="false" action="proceedWithExclude"></dropdown-button>
-    <filter-button formControlName="hosts" label="{{filters.hosts.label | translate}}"
-                   [iconClass]="filters.hosts.iconClass" [subItems]="filters.hosts.options"
-                   [isMultipleChoice]="true" [isRightAlign]="true"
+    <filter-button *ngIf="isFilterConditionDisplayed('hosts')" formControlName="hosts"
+                   label="{{filters.hosts.label | translate}}" [iconClass]="filters.hosts.iconClass"
+                   [subItems]="filters.hosts.options" [isMultipleChoice]="true" [isRightAlign]="true"
                    additionalLabelComponentSetter="getDataForHostsNodeBar"></filter-button>
-    <filter-button formControlName="components" label="{{filters.components.label | translate}}"
-                   [iconClass]="filters.components.iconClass" [subItems]="filters.components.options"
-                   [isMultipleChoice]="true" [isRightAlign]="true"
+    <filter-button *ngIf="isFilterConditionDisplayed('components')" formControlName="components"
+                   label="{{filters.components.label | translate}}" [iconClass]="filters.components.iconClass"
+                   [subItems]="filters.components.options" [isMultipleChoice]="true" [isRightAlign]="true"
                    additionalLabelComponentSetter="getDataForComponentsNodeBar"></filter-button>
-    <filter-button formControlName="levels" label="{{filters.levels.label | translate}}" [iconClass]="filters.levels.iconClass"
+    <filter-button *ngIf="isFilterConditionDisplayed('levels')" formControlName="levels"
+                   label="{{filters.levels.label | translate}}" [iconClass]="filters.levels.iconClass"
                    [subItems]="filters.levels.options" [isMultipleChoice]="true" [isRightAlign]="true"></filter-button>
     <menu-button *ngIf="!captureSeconds" label="{{'filter.capture' | translate}}" iconClass="fa fa-caret-right"
                  action="startCapture"></menu-button>

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.spec.ts
index 0643ea6..0bb0204 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.spec.ts
@@ -31,6 +31,7 @@ import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage
 import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/services/storage/service-logs-histogram-data.service';
 import {AppStateService, appState} from '@app/services/storage/app-state.service';
 import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service';
+import {TabsService, tabs} from '@app/services/storage/tabs.service';
 import {FilteringService} from '@app/services/filtering.service';
 import {HttpClientService} from '@app/services/http-client.service';
 import {UtilsService} from '@app/services/utils.service';
@@ -69,7 +70,8 @@ describe('FiltersPanelComponent', () => {
           serviceLogsFields,
           serviceLogsHistogramData,
           appState,
-          serviceLogsTruncated
+          serviceLogsTruncated,
+          tabs
         }),
         ...TranslationModules
       ],
@@ -85,6 +87,7 @@ describe('FiltersPanelComponent', () => {
         ServiceLogsHistogramDataService,
         AppStateService,
         ServiceLogsTruncatedService,
+        TabsService,
         FilteringService,
         LogsContainerService,
         {

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts
index 5eef03e..9601a0e 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts
@@ -16,12 +16,14 @@
  * limitations under the License.
  */
 
-import {Component} from '@angular/core';
+import {Component, Input} from '@angular/core';
 import {FormGroup} from '@angular/forms';
 import {Subject} from 'rxjs/Subject';
 import {TranslateService} from '@ngx-translate/core';
 import {ListItem} from '@app/classes/list-item';
+import {filtersFormItemsMap} from '@app/classes/filtering';
 import {CommonEntry} from '@app/classes/models/common-entry';
+import {LogField} from '@app/classes/models/log-field';
 import {FilteringService} from '@app/services/filtering.service';
 import {LogsContainerService} from '@app/services/logs-container.service';
 import {AppStateService} from '@app/services/storage/app-state.service';
@@ -36,38 +38,42 @@ export class FiltersPanelComponent {
   constructor(private translate: TranslateService, private filtering: FilteringService, private logsContainer: LogsContainerService, private appState: AppStateService) {
     appState.getParameter('activeLogsType').subscribe(value => {
       this.logsType = value;
-      logsContainer.logsTypeMap[value].fieldsModel.getAll().subscribe(fields => {
+      logsContainer.logsTypeMap[value].fieldsModel.getAll().subscribe((fields: LogField[]): void => {
         if (fields.length) {
-          const items = fields.filter(field => this.excludedParameters.indexOf(field.name) === -1).map(field => {
+          const items = fields.filter((field: LogField): boolean => {
+              return this.excludedParameters.indexOf(field.name) === -1;
+            }).map((field: LogField): CommonEntry => {
               return {
                 name: field.displayName || field.name,
                 value: field.name
               };
             }),
-            labelKeys = items.map(item => item.name);
-          this.searchBoxItems = items.map(item => {
+            labelKeys = items.map((item: CommonEntry): string => item.name);
+          this.searchBoxItems = items.map((item: CommonEntry): ListItem => {
             return {
               label: item.name,
               value: item.value
             };
           });
-          translate.get(labelKeys).first().subscribe(translation => this.searchBoxItemsTranslated = items.map(item => {
-            return {
-              name: translation[item.name],
-              value: item.value
-            };
-          }));
+          translate.get(labelKeys).first().subscribe((translation: {[key: string]: string}): void => {
+            this.searchBoxItemsTranslated = items.map((item: CommonEntry): CommonEntry => {
+              return {
+                name: translation[item.name],
+                value: item.value
+              };
+            })
+          });
         }
       })
     });
-    filtering.loadClusters();
-    filtering.loadComponents();
-    filtering.loadHosts();
   }
 
+  @Input()
+  filtersForm: FormGroup;
+
   private readonly excludedParameters = ['cluster', 'host', 'level', 'type', 'logtime'];
 
-  private logsType: string; // TODO implement setting the parameter depending on user's navigation
+  private logsType: string;
 
   searchBoxItems: ListItem[] = [];
 
@@ -77,10 +83,6 @@ export class FiltersPanelComponent {
     return this.filtering.filters;
   }
 
-  get filtersForm(): FormGroup {
-    return this.filtering.filtersForm;
-  }
-
   get queryParameterNameChange(): Subject<any> {
     return this.filtering.queryParameterNameChange;
   }
@@ -93,4 +95,8 @@ export class FiltersPanelComponent {
     return this.filtering.captureSeconds;
   }
 
+  isFilterConditionDisplayed(key: string): boolean {
+    return filtersFormItemsMap[this.logsType].indexOf(key) > -1;
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts
index c21750a..4e9bdc9 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts
@@ -29,6 +29,7 @@ import {ClustersService, clusters} from '@app/services/storage/clusters.service'
 import {ComponentsService, components} from '@app/services/storage/components.service';
 import {HostsService, hosts} from '@app/services/storage/hosts.service';
 import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service';
+import {TabsService, tabs} from '@app/services/storage/tabs.service';
 import {TranslationModules} from '@app/test-config.spec';
 import {ModalComponent} from '@app/components/modal/modal.component';
 import {LogsContainerService} from '@app/services/logs-container.service';
@@ -67,7 +68,8 @@ describe('LogContextComponent', () => {
           clusters,
           components,
           hosts,
-          serviceLogsTruncated
+          serviceLogsTruncated,
+          tabs
         }),
         ...TranslationModules
       ],
@@ -83,6 +85,7 @@ describe('LogContextComponent', () => {
         ComponentsService,
         HostsService,
         ServiceLogsTruncatedService,
+        TabsService,
         LogsContainerService,
         {
           provide: HttpClientService,

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.html
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.html
index 8b63278..70150a5 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.html
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.html
@@ -15,25 +15,30 @@
   limitations under the License.
 -->
 
+<div class="tabs-container row">
+  <tabs class="col-md-12" [items]="tabs | async" (tabSwitched)="onSwitchTab($event)"
+        (tabClosed)="onCloseTab($event[0], $event[1])"></tabs>
+</div>
+<filters-panel class="row" [filtersForm]="filtersForm"></filters-panel>
 <div *ngIf="autoRefreshRemainingSeconds" class="col-md-12">
   <div class="auto-refresh-message pull-right">
     {{'filter.capture.triggeringRefresh' | translate: autoRefreshMessageParams}}
   </div>
 </div>
 <!-- TODO use plugin for singular/plural -->
-<div class="logs-header col-md-12">{{
+<div class="logs-header">{{
   (!totalEventsFoundMessageParams.totalCount ? 'logs.noEventFound' :
     (totalEventsFoundMessageParams.totalCount === 1 ? 'logs.oneEventFound' : 'logs.totalEventFound'))
         | translate: totalEventsFoundMessageParams
 }}</div>
-<collapsible-panel openTitle="logs.hideGraph" collapsedTitle="logs.showGraph" class="col-md-12">
-  <time-histogram class="col-md-12" [data]="histogramData" [customOptions]="histogramOptions"
-      svgId="service-logs-histogram"
-      (selectArea)="setCustomTimeRange($event[0], $event[1])"></time-histogram>
+<collapsible-panel openTitle="logs.hideGraph" collapsedTitle="logs.showGraph">
+  <time-histogram [data]="histogramData" [customOptions]="histogramOptions" svgId="service-logs-histogram"
+                  (selectArea)="setCustomTimeRange($event[0], $event[1])"></time-histogram>
 </collapsible-panel>
 <dropdown-button *ngIf="!isServiceLogsFileView" class="pull-right" label="logs.columns"
                  [options]="availableColumns | async" [isRightAlign]="true" [isMultipleChoice]="true"
                  action="updateSelectedColumns" [additionalArgs]="logsTypeMapObject.fieldsModel"></dropdown-button>
-<logs-list [logs]="logs | async" [totalCount]="totalCount" [displayedColumns]="displayedColumns"></logs-list>
+<logs-list [logs]="logs | async" [totalCount]="totalCount" [displayedColumns]="displayedColumns"
+           [isServiceLogsFileView]="isServiceLogsFileView" [filtersForm]="filtersForm"></logs-list>
 <log-context *ngIf="isServiceLogContextView" [hostName]="activeLog.host_name" [componentName]="activeLog.component_name"
              [id]="activeLog.id"></log-context>

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.less
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.less
index cd28efc..23d5f92 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.less
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.less
@@ -21,9 +21,12 @@
 :host {
   display: block;
   overflow: hidden;
-  padding-top: @block-margin-top;
 
-  .auto-refresh-message {
+  .tabs-container, .auto-refresh-message {
     background-color: @filters-panel-background-color;
   }
+
+  filters-panel {
+    margin-bottom: @block-margin-top;
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.spec.ts
index 9b3a043..0a9418f 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.spec.ts
@@ -31,10 +31,12 @@ import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage
 import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/services/storage/service-logs-histogram-data.service';
 import {HostsService, hosts} from '@app/services/storage/hosts.service';
 import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service';
+import {TabsService, tabs} from '@app/services/storage/tabs.service';
 import {HttpClientService} from '@app/services/http-client.service';
 import {FilteringService} from '@app/services/filtering.service';
 import {UtilsService} from '@app/services/utils.service';
 import {LogsContainerService} from '@app/services/logs-container.service';
+import {TabsComponent} from '@app/components/tabs/tabs.component';
 
 import {LogsContainerComponent} from './logs-container.component';
 
@@ -52,7 +54,10 @@ describe('LogsContainerComponent', () => {
 
   beforeEach(async(() => {
     TestBed.configureTestingModule({
-      declarations: [LogsContainerComponent],
+      declarations: [
+        LogsContainerComponent,
+        TabsComponent
+      ],
       imports: [
         StoreModule.provideStore({
           appSettings,
@@ -64,6 +69,7 @@ describe('LogsContainerComponent', () => {
           serviceLogs,
           serviceLogsFields,
           serviceLogsHistogramData,
+          tabs,
           hosts,
           serviceLogsTruncated
         }),
@@ -85,6 +91,7 @@ describe('LogsContainerComponent', () => {
         ServiceLogsHistogramDataService,
         HostsService,
         ServiceLogsTruncatedService,
+        TabsService,
         FilteringService,
         UtilsService,
         LogsContainerService
@@ -97,7 +104,7 @@ describe('LogsContainerComponent', () => {
   beforeEach(() => {
     fixture = TestBed.createComponent(LogsContainerComponent);
     component = fixture.componentInstance;
-    component.logsType = 'serviceLogs';
+    component['logsType'] = 'serviceLogs';
     fixture.detectChanges();
   });
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts
index cdc023d..21949f1 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts
@@ -16,68 +16,85 @@
  * limitations under the License.
  */
 
-import {Component, OnInit, Input} from '@angular/core';
+import {Component} from '@angular/core';
 import {FormGroup} from '@angular/forms';
 import {Observable} from 'rxjs/Observable';
+import {Subject} from 'rxjs/Subject';
 import 'rxjs/add/operator/map';
+import 'rxjs/add/operator/takeUntil';
 import {FilteringService} from '@app/services/filtering.service';
 import {LogsContainerService} from '@app/services/logs-container.service';
 import {ServiceLogsHistogramDataService} from '@app/services/storage/service-logs-histogram-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 {LogField} from '@app/classes/models/log-field';
+import {Tab} from '@app/classes/models/tab';
+import {BarGraph} from '@app/classes/models/bar-graph';
 import {ActiveServiceLogEntry} from '@app/classes/active-service-log-entry';
 import {HistogramOptions} from '@app/classes/histogram-options';
+import {ListItem} from '@app/classes/list-item';
 
 @Component({
   selector: 'logs-container',
   templateUrl: './logs-container.component.html',
   styleUrls: ['./logs-container.component.less']
 })
-export class LogsContainerComponent implements OnInit {
-
-  constructor(private serviceLogsHistogramStorage: ServiceLogsHistogramDataService, private appState: AppStateService, private filtering: FilteringService, private logsContainer: LogsContainerService) {
-    serviceLogsHistogramStorage.getAll().subscribe(data => this.histogramData = this.logsContainer.getHistogramData(data));
-    appState.getParameter('isServiceLogContextView').subscribe((value: boolean) => this.isServiceLogContextView = value);
-  }
-
-  ngOnInit() {
-    const fieldsModel = this.logsTypeMapObject.fieldsModel,
-      logsModel = this.logsTypeMapObject.logsModel;
-    this.appState.getParameter(this.logsTypeMapObject.isSetFlag).subscribe((value: boolean) => this.isLogsSet = value);
-    this.availableColumns = fieldsModel.getAll().map(fields => {
-      return fields.filter(field => field.isAvailable).map(field => {
-        return {
-          value: field.name,
-          label: field.displayName || field.name,
-          isChecked: field.isDisplayed
-        };
+export class LogsContainerComponent {
+
+  constructor(private serviceLogsHistogramStorage: ServiceLogsHistogramDataService, private appState: AppStateService, private tabsStorage: TabsService, private filtering: FilteringService, private logsContainer: LogsContainerService) {
+    this.logsContainer.loadColumnsNames();
+    this.logsTypeChange.first().subscribe(() => this.logsContainer.loadLogs());
+    appState.getParameter('activeLogsType').subscribe((value: string): void => {
+      this.logsType = value;
+      this.logsTypeChange.next();
+      const fieldsModel = this.logsTypeMapObject.fieldsModel,
+        logsModel = this.logsTypeMapObject.logsModel;
+      this.availableColumns = fieldsModel.getAll().takeUntil(this.logsTypeChange).map((fields: LogField[]): ListItem[] => {
+        return fields.filter((field: LogField): boolean => field.isAvailable).map((field: LogField): ListItem => {
+          return {
+            value: field.name,
+            label: field.displayName || field.name,
+            isChecked: field.isDisplayed
+          };
+        });
       });
-    });
-    fieldsModel.getAll().subscribe(columns => {
-      const availableFields = columns.filter(field => field.isAvailable),
-        availableNames = availableFields.map(field => field.name);
-      if (availableNames.length && !this.isLogsSet) {
-        this.logs = logsModel.getAll().map((logs: (AuditLog | ServiceLog)[]): (AuditLog | ServiceLog)[] => {
-          return logs.map((log: AuditLog | ServiceLog): AuditLog | ServiceLog => {
-            return availableNames.reduce((obj, key) => Object.assign(obj, {
-              [key]: log[key]
-            }), {});
+      fieldsModel.getAll().takeUntil(this.logsTypeChange).subscribe(columns => {
+        const availableFields = columns.filter((field: LogField): boolean => field.isAvailable),
+          availableNames = availableFields.map((field: LogField): string => field.name);
+        if (availableNames.length) {
+          this.logs = logsModel.getAll().map((logs: (AuditLog | ServiceLog)[]): (AuditLog | ServiceLog)[] => {
+            return logs.map((log: AuditLog | ServiceLog): AuditLog | ServiceLog => {
+              return availableNames.reduce((obj, key) => Object.assign(obj, {
+                [key]: log[key]
+              }), {});
+            });
           });
-        });
-        this.appState.setParameter(this.logsTypeMapObject.isSetFlag, true);
-      }
-      this.displayedColumns = columns.filter(column => column.isAvailable && column.isDisplayed);
+        }
+        this.displayedColumns = columns.filter((column: LogField): boolean => column.isAvailable && column.isDisplayed);
+      });
+    });
+    appState.getParameter('activeFiltersForm').subscribe((form: FormGroup): void => {
+      this.filtersFormChange.next();
+      form.valueChanges.takeUntil(this.filtersFormChange).subscribe(() => this.logsContainer.loadLogs());
+      this.filtersForm = form;
     });
-    this.logsContainer.loadLogs(this.logsType);
-    this.filtersForm.valueChanges.subscribe(() => this.logsContainer.loadLogs(this.logsType));
+    serviceLogsHistogramStorage.getAll().subscribe((data: BarGraph[]): void => {
+      this.histogramData = this.logsContainer.getHistogramData(data);
+    });
+    appState.getParameter('isServiceLogContextView').subscribe((value: boolean) => this.isServiceLogContextView = value);
   }
 
-  @Input()
-  logsType: string;
+  tabs: Observable<Tab[]> = this.tabsStorage.getAll();
 
-  private isLogsSet: boolean = false;
+  filtersForm: FormGroup;
+
+  private logsType: string;
+
+  private filtersFormChange: Subject<any> = new Subject();
+
+  private logsTypeChange: Subject<any> = new Subject();
 
   get logsTypeMapObject(): any {
     return this.logsContainer.logsTypeMap[this.logsType];
@@ -99,10 +116,6 @@ export class LogsContainerComponent implements OnInit {
     keysWithColors: this.logsContainer.colors
   };
 
-  private get filtersForm(): FormGroup {
-    return this.filtering.filtersForm;
-  }
-
   get autoRefreshRemainingSeconds(): number {
     return this.filtering.autoRefreshRemainingSeconds;
   }
@@ -136,4 +149,15 @@ export class LogsContainerComponent implements OnInit {
   setCustomTimeRange(startTime: number, endTime: number): void {
     this.filtering.setCustomTimeRange(startTime, endTime);
   }
+
+  onSwitchTab(activeTab: Tab): void {
+    this.logsContainer.switchTab(activeTab);
+  }
+
+  onCloseTab(activeTab: Tab, newActiveTab: Tab): void {
+    this.tabsStorage.deleteObjectInstance(activeTab);
+    if (newActiveTab) {
+      this.onSwitchTab(newActiveTab);
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.html
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.html
index b27eb69..1e0f49c 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.html
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.html
@@ -15,22 +15,24 @@
   limitations under the License.
 -->
 
-<form *ngIf="logs && logs.length" [formGroup]="filtersForm" class="pull-right">
+<form *ngIf="logs && logs.length" [formGroup]="filtersForm" class="row pull-right">
   <filter-dropdown [label]="filters.sorting.label" formControlName="sorting" [options]="filters.sorting.options"
-                   [defaultLabel]="filters.sorting.defaultLabel" [isRightAlign]="true"></filter-dropdown>
+                   [defaultLabel]="filters.sorting.defaultLabel" [isRightAlign]="true"
+                   class="col-md-12"></filter-dropdown>
 </form>
-<div *ngFor="let log of logs; let i = index">
-  <div *ngIf="!isServiceLogsFileView && (i === 0 || isDifferentDates(log.logtime, logs[i - 1].logtime))" class="col-md-12">
-    <div class="logs-header">{{log.logtime | amTz: timeZone | amDateFormat: dateFormat}}</div>
+<div *ngFor="let log of logs; let i = index" class="row">
+  <div class="logs-header col-md-12"
+       *ngIf="!isServiceLogsFileView && (i === 0 || isDifferentDates(log.logtime, logs[i - 1].logtime))">
+    <div class="col-md-12">{{log.logtime | amTz: timeZone | amDateFormat: dateFormat}}</div>
   </div>
   <accordion-panel *ngIf="!isServiceLogsFileView" [toggleId]="'details-' + i" class="col-md-12">
     <ng-template>
-      <div *ngIf="isColumnDisplayed('level')" [ngClass]="'hexagon ' + log.level.toLowerCase()"></div>
+      <div *ngIf="isColumnDisplayed('level')" [ngClass]="'hexagon ' + (log.level ? log.level.toLowerCase() : '')"></div>
       <div class="col-md-1">
         <dropdown-button iconClass="fa fa-ellipsis-h" [hideCaret]="true" [options]="logActions"
                          [additionalArgs]="[log]"></dropdown-button>
       </div>
-      <div *ngIf="isColumnDisplayed('level')" [ngClass]="'col-md-1 log-status ' + log.level.toLowerCase()">
+      <div *ngIf="isColumnDisplayed('level')" [ngClass]="'col-md-1 log-status ' + (log.level ? log.level.toLowerCase() : '')">
         {{log.level}}
       </div>
       <div *ngIf="isColumnDisplayed('type') || isColumnDisplayed('logtime')" class="col-md-3">
@@ -45,9 +47,7 @@
         </div>
         <div class="log-content-inner-wrapper">
           <div class="log-content" *ngIf="isColumnDisplayed('log_message')"
-               (contextmenu)="openMessageContextMenu($event)">
-            {{log.log_message}}
-          </div>
+               (contextmenu)="openMessageContextMenu($event)">{{log.log_message}}</div>
         </div>
       </div>
       <div *ngFor="let column of displayedColumns">
@@ -56,10 +56,10 @@
       </div>
     </ng-template>
   </accordion-panel>
-  <log-file-entry *ngIf="isServiceLogsFileView" class="col-md-12" [time]="log.logtime" [level]="log.level"
+  <log-file-entry *ngIf="isServiceLogsFileView" [time]="log.logtime" [level]="log.level"
                   [fileName]="log.file" [lineNumber]="log.line_number" [message]="log.log_message"></log-file-entry>
 </div>
 <ul #contextmenu *ngIf="!isServiceLogsFileView" data-component="dropdown-list" class="dropdown-menu context-menu"
     [items]="contextMenuItems" (selectedItemChange)="updateQuery($event)"></ul>
-<pagination class="col-md-12" *ngIf="logs && logs.length" [totalCount]="totalCount" [filtersForm]="filtersForm"
+<pagination class="pull-right" *ngIf="logs && logs.length" [totalCount]="totalCount" [filtersForm]="filtersForm"
             [filterInstance]="filters.pageSize" [currentCount]="logs.length"></pagination>

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.ts
index 2462a61..017bc82 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.ts
@@ -18,11 +18,11 @@
 import {Component, AfterViewInit, Input, ViewChild, ElementRef} from '@angular/core';
 import {FormGroup} from '@angular/forms';
 import 'rxjs/add/operator/map';
-import {AppStateService} from '@app/services/storage/app-state.service';
 import {FilteringService} from '@app/services/filtering.service';
 import {UtilsService} from '@app/services/utils.service';
 import {AuditLog} from '@app/classes/models/audit-log';
 import {ServiceLog} from '@app/classes/models/service-log';
+import {LogField} from '@app/classes/models/log-field';
 
 @Component({
   selector: 'logs-list',
@@ -31,12 +31,13 @@ import {ServiceLog} from '@app/classes/models/service-log';
 })
 export class LogsListComponent implements AfterViewInit {
 
-  constructor(private filtering: FilteringService, private utils: UtilsService, private appState: AppStateService) {
-    appState.getParameter('isServiceLogsFileView').subscribe((value: boolean) => this.isServiceLogsFileView = value);
+  constructor(private filtering: FilteringService, private utils: UtilsService) {
   }
 
   ngAfterViewInit() {
-    this.contextMenuElement = this.contextMenu.nativeElement;
+    if (this.contextMenu) {
+      this.contextMenuElement = this.contextMenu.nativeElement;
+    }
   }
 
   @Input()
@@ -46,7 +47,13 @@ export class LogsListComponent implements AfterViewInit {
   totalCount: number = 0;
 
   @Input()
-  displayedColumns: any[] = [];
+  displayedColumns: LogField[] = [];
+
+  @Input()
+  isServiceLogsFileView: boolean = false;
+
+  @Input()
+  filtersForm: FormGroup;
 
   @ViewChild('contextmenu', {
     read: ElementRef
@@ -103,19 +110,13 @@ export class LogsListComponent implements AfterViewInit {
   get filters(): any {
     return this.filtering.filters;
   }
-  
-  get filtersForm(): FormGroup {
-    return this.filtering.filtersForm;
-  }
-
-  isServiceLogsFileView: boolean = false;
 
   isDifferentDates(dateA, dateB): boolean {
     return this.utils.isDifferentDates(dateA, dateB, this.timeZone);
   }
 
   isColumnDisplayed(key: string): boolean {
-    return this.displayedColumns.some(column => column.name === key);
+    return this.displayedColumns.some((column: LogField): boolean  => column.name === key);
   }
 
   openMessageContextMenu(event: MouseEvent): void {

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.html
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.html
index 7e3621a..2061582 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.html
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.html
@@ -20,12 +20,4 @@
   <span class="fa fa-spinner fa-spin"></span>
 </div>
 <login-form *ngIf="!isInitialLoading && !isAuthorized"></login-form>
-
-<!-- TODO implement tabs: Service Logs/Audit Logs/active file -->
-<div *ngIf="isServiceLogsFileView" class="col-md-12 logs-header">
-  {{activeLogHostName}} &gt;&gt; {{activeLogComponentName}}
-  <span class="fa fa-times close-icon" (click)="closeLog()"></span>
-</div>
-
-<filters-panel *ngIf="isAuthorized" class="row"></filters-panel>
-<logs-container *ngIf="isAuthorized" logsType="serviceLogs"></logs-container>
+<logs-container *ngIf="isAuthorized" class="col-md-12"></logs-container>

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.less
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.less
index b596a3d..bca668d 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.less
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.less
@@ -21,8 +21,4 @@
 :host {
   .full-size;
   overflow-x: hidden;
-
-  .close-icon {
-    .clickable-item;
-  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.spec.ts
index bbbebdf..18adec7 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.spec.ts
@@ -23,7 +23,6 @@ import {StoreModule} from '@ngrx/store';
 import {AppStateService, appState} from '@app/services/storage/app-state.service';
 import {AuditLogsFieldsService, auditLogsFields} from '@app/services/storage/audit-logs-fields.service';
 import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.service';
-import {HttpClientService} from '@app/services/http-client.service';
 
 import {MainContainerComponent} from './main-container.component';
 
@@ -32,14 +31,6 @@ describe('MainContainerComponent', () => {
   let fixture: ComponentFixture<MainContainerComponent>;
 
   beforeEach(async(() => {
-    const httpClient = {
-      get: () => {
-        return {
-          subscribe: () => {
-          }
-        }
-      }
-    };
     TestBed.configureTestingModule({
       declarations: [MainContainerComponent],
       imports: [
@@ -54,11 +45,7 @@ describe('MainContainerComponent', () => {
       providers: [
         AppStateService,
         AuditLogsFieldsService,
-        ServiceLogsFieldsService,
-        {
-          provide: HttpClientService,
-          useValue: httpClient
-        }
+        ServiceLogsFieldsService
       ]
     })
     .compileComponents();

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.ts
index ad86a74..6747a0c 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.ts
@@ -17,13 +17,7 @@
  */
 
 import {Component, ContentChild, TemplateRef} from '@angular/core';
-import {HttpClientService} from '@app/services/http-client.service';
 import {AppStateService} from '@app/services/storage/app-state.service';
-import {AuditLogsFieldsService} from '@app/services/storage/audit-logs-fields.service';
-import {ServiceLogsFieldsService} from '@app/services/storage/service-logs-fields.service';
-import {AuditLogField} from '@app/classes/models/audit-log-field';
-import {ServiceLogField} from '@app/classes/models/service-log-field';
-import {ActiveServiceLogEntry} from '@app/classes/active-service-log-entry';
 
 @Component({
   selector: 'main-container',
@@ -32,20 +26,9 @@ import {ActiveServiceLogEntry} from '@app/classes/active-service-log-entry';
 })
 export class MainContainerComponent {
 
-  constructor(private httpClient: HttpClientService, private appState: AppStateService, private auditLogsFieldsStorage: AuditLogsFieldsService, private serviceLogsFieldsStorage: ServiceLogsFieldsService) {
-    this.loadColumnsNames();
+  constructor(private appState: AppStateService) {
     appState.getParameter('isAuthorized').subscribe((value: boolean) => this.isAuthorized = value);
     appState.getParameter('isInitialLoading').subscribe((value: boolean) => this.isInitialLoading = value);
-    appState.getParameter('isServiceLogsFileView').subscribe((value: boolean) => this.isServiceLogsFileView = value);
-    appState.getParameter('activeLog').subscribe((value: ActiveServiceLogEntry | null) => {
-      if (value) {
-        this.activeLogHostName = value.host_name;
-        this.activeLogComponentName = value.component_name;
-      } else {
-        this.activeLogHostName = '';
-        this.activeLogComponentName = '';
-      }
-    });
   }
 
   @ContentChild(TemplateRef)
@@ -55,36 +38,4 @@ export class MainContainerComponent {
 
   isInitialLoading: boolean = false;
 
-  isServiceLogsFileView: boolean = false;
-
-  activeLogHostName: string = '';
-
-  activeLogComponentName: string = '';
-
-  private loadColumnsNames(): void {
-    this.httpClient.get('serviceLogsFields').subscribe(response => {
-      const jsonResponse = response.json();
-      if (jsonResponse) {
-        this.serviceLogsFieldsStorage.addInstances(this.getColumnsArray(jsonResponse, ServiceLogField));
-      }
-    });
-    this.httpClient.get('auditLogsFields').subscribe(response => {
-      const jsonResponse = response.json();
-      if (jsonResponse) {
-        this.auditLogsFieldsStorage.addInstances(this.getColumnsArray(jsonResponse, AuditLogField));
-      }
-    });
-  }
-
-  private getColumnsArray(keysObject: any, fieldClass: any): any[] {
-    return Object.keys(keysObject).map(key => new fieldClass(key));
-  }
-
-  closeLog(): void {
-    this.appState.setParameters({
-      isServiceLogsFileView: false,
-      activeLog: null
-    });
-  }
-
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts
index 5414f4f..261e213 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts
@@ -31,6 +31,7 @@ import {ServiceLogsService, serviceLogs} from '@app/services/storage/service-log
 import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.service';
 import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/services/storage/service-logs-histogram-data.service';
 import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service';
+import {TabsService, tabs} from '@app/services/storage/tabs.service';
 import {ComponentActionsService} from '@app/services/component-actions.service';
 import {FilteringService} from '@app/services/filtering.service';
 import {HttpClientService} from '@app/services/http-client.service';
@@ -43,6 +44,14 @@ describe('MenuButtonComponent', () => {
   let fixture: ComponentFixture<MenuButtonComponent>;
 
   beforeEach(async(() => {
+    const httpClient = {
+      get: () => {
+        return {
+          subscribe: () => {
+          }
+        }
+      }
+    };
     TestBed.configureTestingModule({
       declarations: [MenuButtonComponent],
       imports: [
@@ -57,7 +66,8 @@ describe('MenuButtonComponent', () => {
           serviceLogs,
           serviceLogsFields,
           serviceLogsHistogramData,
-          serviceLogsTruncated
+          serviceLogsTruncated,
+          tabs
         }),
         ...TranslationModules
       ],
@@ -73,9 +83,13 @@ describe('MenuButtonComponent', () => {
         ServiceLogsFieldsService,
         ServiceLogsHistogramDataService,
         ServiceLogsTruncatedService,
+        TabsService,
         ComponentActionsService,
         FilteringService,
-        HttpClientService,
+        {
+          provide: HttpClientService,
+          useValue: httpClient
+        },
         LogsContainerService
       ],
       schemas: [NO_ERRORS_SCHEMA]

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.html
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.html
index be6591b..679a7e5 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.html
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.html
@@ -15,7 +15,7 @@
   limitations under the License.
 -->
 
-<form class="pagination-form col-md-12" [formGroup]="filtersForm">
+<form class="pagination-form" [formGroup]="filtersForm">
   <filter-dropdown [label]="filterInstance.label" formControlName="pageSize" [options]="filterInstance.options"
                    [defaultLabel]="filterInstance.defaultLabel" [isRightAlign]="true" isDropup="true"></filter-dropdown>
   <span>{{'pagination.numbers' | translate: numbersTranslateParams}}</span>

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.ts
index d38d0d8..cc5589f 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.ts
@@ -28,7 +28,9 @@ export class PaginationComponent implements OnInit {
 
   ngOnInit() {
     this.setPageSizeFromString(this.filterInstance.defaultValue);
-    this.filtersForm.controls.pageSize.valueChanges.subscribe(value => this.setPageSizeFromString(value));
+    this.filtersForm.controls.pageSize.valueChanges.subscribe((value: string): void => {
+      this.setPageSizeFromString(value);
+    });
   }
 
   @Input()
@@ -45,7 +47,7 @@ export class PaginationComponent implements OnInit {
 
   private pageSize: number = 0;
 
-  setPageSizeFromString(value: string) {
+  private setPageSizeFromString(value: string) {
     this.pageSize = parseInt(value);
   }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.html
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.html
new file mode 100644
index 0000000..9bbcacf
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.html
@@ -0,0 +1,25 @@
+<!--
+  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.
+-->
+
+<ul class="nav nav-tabs">
+  <li *ngFor="let tab of items" [ngClass]="{'active': tab.isActive}">
+    <a href="#" (click)="switchTab(tab)">
+      {{tab.label | translate}}
+      <span *ngIf="tab.isCloseable" class="fa fa-times close-icon" (click)="closeTab(tab)"></span>
+    </a>
+  </li>
+</ul>

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.less
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.less
new file mode 100644
index 0000000..67e4e8c
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.less
@@ -0,0 +1,22 @@
+/**
+ * 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 '../mixins';
+
+.close-icon {
+  .clickable-item;
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.spec.ts
new file mode 100644
index 0000000..2df5090
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.spec.ts
@@ -0,0 +1,125 @@
+/**
+ * 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 {async, ComponentFixture, TestBed} from '@angular/core/testing';
+import {Tab} from '@app/classes/models/tab';
+import {TranslationModules} from '@app/test-config.spec';
+
+import {TabsComponent} from './tabs.component';
+
+describe('TabsComponent', () => {
+  let component: TabsComponent;
+  let fixture: ComponentFixture<TabsComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [TabsComponent],
+      imports: TranslationModules
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(TabsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create component', () => {
+    expect(component).toBeTruthy();
+  });
+
+  describe('#switchTab()', () => {
+    let activeTab;
+    const tab = {
+      id: 'tab0',
+      type: '',
+      isActive: true,
+      label: '',
+      appState: null
+    };
+
+    it('new active tab', () => {
+      component.tabSwitched.subscribe((tab: Tab) => activeTab = tab);
+      component.switchTab(tab);
+      expect(activeTab).toEqual(tab);
+    });
+  });
+
+  describe('#closeTab()', () => {
+    const items = [
+        {
+          id: 'serviceLogs',
+          type: '',
+          isActive: false,
+          label: '',
+          appState: null
+        },
+        {
+          id: 'auditLogs',
+          type: '',
+          isActive: false,
+          label: '',
+          appState: null
+        },
+        {
+          id: 'newTab',
+          type: '',
+          isActive: false,
+          label: '',
+          appState: null
+        }
+      ],
+      cases = [
+        {
+          closedTabIndex: 2,
+          newActiveTabIndex: 1,
+          title: 'last tab closed'
+        },
+        {
+          closedTabIndex: 1,
+          newActiveTabIndex: 2,
+          title: 'not last tab closed'
+        }
+      ];
+
+    cases.forEach(test => {
+      let oldTab,
+        newTab;
+      describe(test.title, () => {
+        beforeEach(() => {
+          oldTab = null;
+          newTab = null;
+          component.items = items;
+          component.tabClosed.subscribe((tabs: Tab[]): void => {
+            oldTab = tabs[0];
+            newTab = tabs[1];
+          });
+          component.closeTab(items[test.closedTabIndex]);
+        });
+
+        it('closed tab', () => {
+          expect(oldTab).toEqual(items[test.closedTabIndex]);
+        });
+
+        it('new active tab', () => {
+          expect(newTab).toEqual(items[test.newActiveTabIndex]);
+        });
+      });
+    });
+  });
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.ts
new file mode 100644
index 0000000..ef941e6
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.ts
@@ -0,0 +1,48 @@
+/**
+ * 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 {Component, Input, Output, EventEmitter} from '@angular/core';
+import {Tab} from '@app/classes/models/tab';
+
+@Component({
+  selector: 'tabs',
+  templateUrl: './tabs.component.html',
+  styleUrls: ['./tabs.component.less']
+})
+export class TabsComponent {
+
+  @Input()
+  items: Tab[] = [];
+
+  @Output()
+  tabSwitched: EventEmitter<Tab> = new EventEmitter();
+
+  @Output()
+  tabClosed: EventEmitter<Tab[]> = new EventEmitter();
+
+  switchTab(tab: Tab): void {
+    this.tabSwitched.emit(tab);
+  }
+
+  closeTab(tab: Tab): void {
+    const tabs = this.items,
+      tabsCount = tabs.length,
+      newActiveTab = tabs[tabsCount - 1] === tab ? tabs[tabsCount - 2] : tabs[tabsCount - 1];
+    this.tabClosed.emit([tab, newActiveTab]);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.less
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.less
index 6fe6292..e8d3240 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.less
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.less
@@ -17,6 +17,7 @@
  */
 
 :host {
+  display: block;
   cursor: crosshair;
   background: #ECECEC; // TODO add style according to actual design
   /deep/ .axis {

http://git-wip-us.apache.org/repos/asf/ambari/blob/15cec1cb/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts
index 08817f4..7612cc3 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts
@@ -35,6 +35,14 @@ describe('TimeRangePickerComponent', () => {
   let fixture: ComponentFixture<TimeRangePickerComponent>;
 
   beforeEach(async(() => {
+    const httpClient = {
+      get: () => {
+        return {
+          subscribe: () => {
+          }
+        }
+      }
+    };
     TestBed.configureTestingModule({
       declarations: [TimeRangePickerComponent],
       imports: [
@@ -48,7 +56,10 @@ describe('TimeRangePickerComponent', () => {
         ...TranslationModules
       ],
       providers: [
-        HttpClientService,
+        {
+          provide: HttpClientService,
+          useValue: httpClient
+        },
         FilteringService,
         AppSettingsService,
         AppStateService,