You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by nc...@apache.org on 2018/01/02 16:56:24 UTC

[33/37] ambari git commit: AMBARI-22714 Log Search UI: implement Summary tab for Access Logs page. (ababiichuk)

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/graph-tooltip/graph-tooltip.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/graph-tooltip/graph-tooltip.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/graph-tooltip/graph-tooltip.component.spec.ts
new file mode 100644
index 0000000..14fa60e
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/graph-tooltip/graph-tooltip.component.spec.ts
@@ -0,0 +1,50 @@
+/**
+ * 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} from '@angular/core/testing';
+import {TranslationModules} from '@app/test-config.spec';
+import {GraphLegendItemComponent} from '@app/components/graph-legend-item/graph-legend-item.component';
+
+import {GraphTooltipComponent} from './graph-tooltip.component';
+
+describe('GraphTooltipComponent', () => {
+  let component: GraphTooltipComponent;
+  let fixture: ComponentFixture<GraphTooltipComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [
+        GraphTooltipComponent,
+        GraphLegendItemComponent
+      ],
+      imports: TranslationModules,
+      schemas: [CUSTOM_ELEMENTS_SCHEMA]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(GraphTooltipComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create component', () => {
+    expect(component).toBeTruthy();
+  });
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/graph-tooltip/graph-tooltip.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/graph-tooltip/graph-tooltip.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/graph-tooltip/graph-tooltip.component.ts
new file mode 100644
index 0000000..9d26a2e
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/graph-tooltip/graph-tooltip.component.ts
@@ -0,0 +1,36 @@
+/**
+ * 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} from '@angular/core';
+
+@Component({
+  selector: 'graph-tooltip',
+  templateUrl: './graph-tooltip.component.html',
+  styleUrls: ['./graph-tooltip.component.less']
+})
+export class GraphTooltipComponent {
+
+  @Input()
+  title: string | number = '';
+
+  @Input()
+  data = [];
+
+  @Input()
+  labelClass: string = 'initial-color';
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.html
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.html
new file mode 100644
index 0000000..015013f
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.html
@@ -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.
+-->
+
+<div #graphContainer></div>
+<graph-legend class="col-md-12" [items]="legendItems"></graph-legend>
+<graph-tooltip #tooltip [data]="tooltipInfo.data" [title]="tooltipInfo.title" [ngClass]="{'hide': !tooltipInfo.data}"
+               [style.top]="tooltipInfo.data ? tooltipPosition.top + 'px' : ''"
+               [style.left]="tooltipInfo.data ? tooltipPosition.left + 'px' : ''"></graph-tooltip>

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.less
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.less
new file mode 100644
index 0000000..395737a
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.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 '../variables';
+
+:host {
+  padding-top: @graph-padding;
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.spec.ts
new file mode 100644
index 0000000..2c59916
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.spec.ts
@@ -0,0 +1,61 @@
+/**
+ * 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 {Injector} from '@angular/core';
+import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing';
+import {TranslationModules} from '@app/test-config.spec';
+import {ServiceInjector} from '@app/classes/service-injector';
+import {GraphLegendComponent} from '@app/components/graph-legend/graph-legend.component';
+import {GraphLegendItemComponent} from '@app/components/graph-legend-item/graph-legend-item.component';
+import {GraphTooltipComponent} from '@app/components/graph-tooltip/graph-tooltip.component';
+import {UtilsService} from '@app/services/utils.service';
+
+import {HorizontalHistogramComponent} from './horizontal-histogram.component';
+
+describe('HorizontalHistogramComponent', () => {
+  let component: HorizontalHistogramComponent;
+  let fixture: ComponentFixture<HorizontalHistogramComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [
+        HorizontalHistogramComponent,
+        GraphLegendComponent,
+        GraphLegendItemComponent,
+        GraphTooltipComponent
+      ],
+      imports: [
+        ...TranslationModules
+      ],
+      providers: [
+        UtilsService
+      ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(inject([Injector], (injector: Injector) => {
+    ServiceInjector.injector = injector;
+    fixture = TestBed.createComponent(HorizontalHistogramComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  }));
+
+  it('should create component', () => {
+    expect(component).toBeTruthy();
+  });
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.ts
new file mode 100644
index 0000000..9553e2e
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.ts
@@ -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 {Component, Input} from '@angular/core';
+import * as d3 from 'd3';
+import {GraphComponent} from '@app/classes/components/graph/graph.component';
+import {HomogeneousObject} from '@app/classes/object';
+
+@Component({
+  selector: 'horizontal-histogram',
+  templateUrl: './horizontal-histogram.component.html',
+  styleUrls: ['../../classes/components/graph/graph.component.less', './horizontal-histogram.component.less']
+})
+export class HorizontalHistogramComponent extends GraphComponent {
+
+  /**
+   * Thickness of horizontal bar o the graph
+   * @type {number}
+   */
+  @Input()
+  barSize: number = 5;
+
+  rowsCount: number;
+
+  readonly reverseYRange: boolean = true;
+
+  protected populate(): void {
+    const barSize = this.barSize,
+      data = this.data,
+      yValues = Object.keys(data),
+      keys = Object.keys(this.labels),
+      rowsCount = yValues.reduce((currentCount: number, currentKey: string): number => {
+        return currentCount + Object.keys(this.data[currentKey]).length;
+      }, 0),
+      formattedData = yValues.reduce((currentData, currentKey: string) => {
+        const currentValues = data[currentKey],
+          currentObjects = keys.map((key: string): HomogeneousObject<number> => {
+            return {
+              [key]: currentValues[key] || 0
+            };
+          });
+        return [...currentData, Object.assign({
+          tick: currentKey
+        }, ...currentObjects)];
+      }, []),
+      layers = d3.stack().keys(keys)(formattedData),
+      formattedLayers = d3.transpose<any>(layers);
+
+    this.rowsCount = rowsCount;
+
+    this.setXScaleDomain();
+    this.setYScaleDomain();
+
+    // drawing the axis
+    this.drawXAxis();
+    this.drawYAxis(rowsCount);
+
+    let i = 0;
+
+    // populate the data and drawing the bars
+    this.svg.selectAll().data(formattedLayers).enter().append('g').attr('class', 'value')
+      .selectAll().data(item => item).enter().append('rect')
+      .attr('x', item => this.xScale(0) + 1).attr('y', item => {
+        if (item [0] !== item[1]) {
+          return this.yScale(i++) - this.barSize / 2;
+        }
+      }).attr('height', item => item[0] === item[1] ? '0' : barSize.toString())
+      .attr('width', item => this.xScale(item[1]) - this.xScale(item[0]))
+      .style('fill', (item, index) => this.orderedColors[index])
+      .on('mouseover', this.handleRectMouseOver)
+      .on('mousemove', this.handleRectMouseMove)
+      .on('mouseout', this.handleRectMouseOut);
+  }
+
+  protected setXScaleDomain(): void {
+    const keys = Object.keys(this.data),
+      maxValues = keys.map((currentKey: string): number => this.utils.getMaxNumberInObject(this.data[currentKey]), 0),
+      maximum = Math.max(...maxValues);
+    this.xScale.domain([0, maximum]);
+  }
+
+  protected setYScaleDomain(): void {
+    this.yScale.domain([0, this.rowsCount]);
+  }
+
+  protected yAxisTickFormatter = (tick: any, index: number): string | undefined => {
+    const data = this.data,
+      keys = Object.keys(data);
+    let currentIndex = 0;
+    for (let i = 0; i < keys.length && i <= index; i++) {
+      const currentKey = keys[i];
+      if (currentIndex === index) {
+        return currentKey;
+      } else {
+        currentIndex += Object.keys(data[currentKey]).length;
+      }
+    }
+  };
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/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 d1b11e6..8e507ae 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
@@ -41,14 +41,14 @@
     }}</div>
   </div>
   <collapsible-panel openTitle="logs.hideGraph" collapsedTitle="logs.showGraph">
-    <time-histogram [data]="histogramData" [customOptions]="histogramOptions" svgId="service-logs-histogram"
+    <time-histogram [data]="histogramData" [colors]="serviceLogsHistogramColors" svgId="service-logs-histogram"
                     (selectArea)="setCustomTimeRange($event[0], $event[1])"></time-histogram>
   </collapsible-panel>
   <ng-container [ngSwitch]="logsType">
     <service-logs-table *ngSwitchCase="'serviceLogs'" [totalCount]="totalCount" [logs]="serviceLogs | async"
                         [columns]="serviceLogsColumns | async" [filtersForm]="filtersForm"></service-logs-table>
-    <audit-logs-table *ngSwitchCase="'auditLogs'" [totalCount]="totalCount" [logs]="auditLogs | async"
-                      [columns]="auditLogsColumns | async" [filtersForm]="filtersForm"></audit-logs-table>
+    <audit-logs-entries *ngSwitchCase="'auditLogs'" [totalCount]="totalCount" [logs]="auditLogs | async"
+                        [columns]="auditLogsColumns | async" [filtersForm]="filtersForm"></audit-logs-entries>
   </ng-container>
   <log-context *ngIf="isServiceLogContextView" [id]="activeLog.id" [hostName]="activeLog.host_name"
                [componentName]="activeLog.component_name"></log-context>

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/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 cf28a8b..6d50a17 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
@@ -28,8 +28,8 @@ import {ServiceLog} from '@app/classes/models/service-log';
 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';
+import {HomogeneousObject} from '@app/classes/object';
 import {LogsType} from '@app/classes/string';
 import {FiltersPanelComponent} from "@app/components/filters-panel/filters-panel.component";
 
@@ -74,11 +74,11 @@ export class LogsContainerComponent {
     return this.logsContainer.totalCount;
   }
 
-  histogramData: {[key: string]: number};
+  histogramData: HomogeneousObject<HomogeneousObject<number>>;
 
-  readonly histogramOptions: HistogramOptions = {
-    keysWithColors: this.logsContainer.colors
-  };
+  get serviceLogsHistogramColors(): HomogeneousObject<string> {
+    return this.logsContainer.colors;
+  }
 
   get autoRefreshRemainingSeconds(): number {
     return this.logsContainer.autoRefreshRemainingSeconds;

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/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 3836e7a..67d9423 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
@@ -16,9 +16,10 @@
  * limitations under the License.
  */
 
-import {NO_ERRORS_SCHEMA} from '@angular/core';
-import {async, ComponentFixture, TestBed} from '@angular/core/testing';
+import {NO_ERRORS_SCHEMA, Injector} from '@angular/core';
+import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing';
 import {TranslationModules} from '@app/test-config.spec';
+import {ServiceInjector} from '@app/classes/service-injector';
 import {StoreModule} from '@ngrx/store';
 import {AppSettingsService, appSettings} from '@app/services/storage/app-settings.service';
 import {AppStateService, appState} from '@app/services/storage/app-state.service';
@@ -97,11 +98,12 @@ describe('MenuButtonComponent', () => {
     .compileComponents();
   }));
 
-  beforeEach(() => {
+  beforeEach(inject([Injector], (injector: Injector) => {
+    ServiceInjector.injector = injector;
     fixture = TestBed.createComponent(MenuButtonComponent);
     component = fixture.componentInstance;
     fixture.detectChanges();
-  });
+  }));
 
   it('should create component', () => {
     expect(component).toBeTruthy();

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts
index ca89935..12da4ac 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts
@@ -18,6 +18,7 @@
 
 import {Component, Input, ViewChild, ElementRef} from '@angular/core';
 import {ListItem} from '@app/classes/list-item';
+import {ServiceInjector} from '@app/classes/service-injector';
 import {ComponentActionsService} from '@app/services/component-actions.service';
 
 @Component({
@@ -27,7 +28,8 @@ import {ComponentActionsService} from '@app/services/component-actions.service';
 })
 export class MenuButtonComponent {
 
-  constructor(protected actions: ComponentActionsService) {
+  constructor() {
+    this.actions = ServiceInjector.injector.get(ComponentActionsService);
   }
 
   @ViewChild('dropdown')
@@ -82,6 +84,8 @@ export class MenuButtonComponent {
   @Input()
   maxLongClickDelay: number = 0;
 
+  private actions: ComponentActionsService;
+
   /**
    * This is a private property to indicate the mousedown timestamp, so that we can check it when teh click event
    * has been triggered.

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.ts
index b2136f4..9dd8e26 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.ts
@@ -21,6 +21,7 @@ import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
 import {Subject} from 'rxjs/Subject';
 import {SearchBoxParameter, SearchBoxParameterProcessed, SearchBoxParameterTriggered} from '@app/classes/filtering';
 import {ListItem} from '@app/classes/list-item';
+import {HomogeneousObject} from '@app/classes/object';
 import {UtilsService} from '@app/services/utils.service';
 
 @Component({
@@ -88,7 +89,7 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess
   items: ListItem[] = [];
 
   @Input()
-  itemsOptions: {[key: string]: ListItem[]} = {};
+  itemsOptions: HomogeneousObject<ListItem[]> = {};
 
   /**
    * Name of parameter to be used if there are no matching values

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts
index 9f38371..84a59b9 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts
@@ -18,7 +18,7 @@
 
 import {Component, AfterViewInit, ViewChild, ElementRef} from '@angular/core';
 import {ListItem} from '@app/classes/list-item';
-import {LogsTableComponent} from '@app/classes/components/logs-table-component';
+import {LogsTableComponent} from '@app/classes/components/logs-table/logs-table-component';
 import {LogsContainerService} from '@app/services/logs-container.service';
 import {UtilsService} from '@app/services/utils.service';
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/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
index 2df5090..7eef0e1 100644
--- 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
@@ -47,7 +47,6 @@ describe('TabsComponent', () => {
     let activeTab;
     const tab = {
       id: 'tab0',
-      type: '',
       isActive: true,
       label: '',
       appState: null
@@ -64,21 +63,18 @@ describe('TabsComponent', () => {
     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

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/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
index ef941e6..e202c2d 100644
--- 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
@@ -35,6 +35,7 @@ export class TabsComponent {
   tabClosed: EventEmitter<Tab[]> = new EventEmitter();
 
   switchTab(tab: Tab): void {
+    this.items.forEach((item: Tab) => item.isActive = item.id === tab.id);
     this.tabSwitched.emit(tab);
   }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.html
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.html
index 1193b2e..720f55e 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.html
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.html
@@ -18,30 +18,20 @@
 <header>
   <div class="container-fluid">
     <div class="row">
-      <div *ngIf="chartTimeGap" class="time-gap col-lg-2 col-md-offset-5">
+      <div *ngIf="isTimeGraph && chartTimeGap" class="time-gap col-md-2 col-md-offset-5">
         {{chartTimeGap.value}} {{chartTimeGap.label | translate}} {{'histogram.gap' | translate}}
       </div>
-      <div class="legends col-md-5" [class.md-offset-7]="!chartTimeGap">
-        <div *ngFor="let legend of legends" class="legend {{legend.level | lowercase}}">
-          {{ legend.label | translate }}
-        </div>
-      </div>
+      <graph-legend [ngClass]="{'col-md-5 text-right': true, 'md-offset-7': !chartTimeGap}"
+                    [items]="legendItems"></graph-legend>
     </div>
   </div>
 </header>
-<div #container></div>
-<footer *ngIf="firstDateTick || lastDateTick">
-  <div *ngIf="firstDateTick" class="first-date-tick-label">{{firstDateTick | amTz:timeZone | amDateFormat:historyStartEndTimeFormat}}</div>
-  <div *ngIf="lastDateTick" class="last-date-tick-label">{{lastDateTick | amTz:timeZone | amDateFormat:historyStartEndTimeFormat}}</div>
+<div #graphContainer></div>
+<footer *ngIf="isTimeGraph && (firstDateTick || lastDateTick)">
+  <div *ngIf="firstDateTick">{{firstDateTick | amTz: timeZone | amDateFormat: historyStartEndTimeFormat}}</div>
+  <div *ngIf="lastDateTick">{{lastDateTick | amTz: timeZone | amDateFormat: historyStartEndTimeFormat}}</div>
 </footer>
-<div [ngClass]="{hide: !tooltipInfo, 'tooltip-left': tooltipOnTheLeft, 'tooltip-chart': true}" #tooltipEl
-     [style.top]="tooltipInfo ? (tooltipPosition.top + 'px') : ''" [style.left]="tooltipInfo ? (tooltipPosition.left + 'px') : ''">
-  <ng-container *ngIf="tooltipInfo">
-    <div class="tooltip-chart-date">{{tooltipInfo.timeStamp | amTz:timeZone | amDateFormat:tickTimeFormat}}</div>
-    <div *ngFor="let data of tooltipInfo.data" class="level {{data.level | lowercase}}">
-      <span class="level-label">{{data.levelLabel | translate }}</span>
-      <span class="level-value">{{data.value}}</span>
-    </div>
-  </ng-container>
-</div>
-
+<graph-tooltip #tooltip [data]="tooltipInfo.data" [ngClass]="{'hide': !tooltipInfo.data}"
+               [style.top]="tooltipInfo.data ? tooltipPosition.top + 'px' : ''"
+               [style.left]="tooltipInfo.data ? tooltipPosition.left + 'px' : ''"
+               [title]="tooltipInfo.title | amTz: timeZone | amDateFormat: tickTimeFormat"></graph-tooltip>

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/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 1d3766d..364517c 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
@@ -20,28 +20,11 @@
 
 :host {
   position: relative;
-  .level-mixin(@level, @size: .8em) {
-    @name: "@{level}-color";
-    border-radius: 100%;
-    content: "";
-    display: inline-block;
-    height: .8em;
-    width: .8em;
-    background-color: @@name;
-  }
-
   background: #ECECEC; // TODO add style according to actual design
-  display: block;
 
   /deep/ .axis {
-    .domain {
-      display: none;
-    }
     .tick {
       cursor: default;
-      line {
-        display: none;
-      }
     }
   }
 
@@ -49,137 +32,21 @@
     cursor: crosshair;
   }
 
-  /deep/ .value {
-    cursor: pointer;
-    rect {
-      transition: opacity 250ms;
-      opacity: .8;
-      &:hover {
-        opacity: 1;
-      }
-    }
-  }
-
-  /deep/ .tooltip-chart {
-    background: #fff;
-    border-radius: 4px;
-    border: @input-border;
-    display: block;
-    font-size: .8em;
-    margin: 0 1.5em;
-    min-height: 2em;
-    min-width: 5em;
-    padding: .5em;
-    position: absolute;
-    &:empty {
-      display: none;
-    }
-    &::before {
-      .caret-mixin(6px, left, #fff);
-      left: -6px;
-      position: absolute;
-      top: calc(50% - 2px);
-    }
-    &.tooltip-left::before {
-      display: none;
-    }
-    &.tooltip-left::after {
-      .caret-mixin(6px, right, #fff);
-      right: -6px;
-      position: absolute;
-      top: calc(50% - 2px);
-    }
-    .tooltip-chart-date {
-      padding: 0 0 .1em 0;
-      text-align: center;
-    }
-    .level {
-      display: flex;
-      &::before {
-        margin: auto .2em auto 0;
-      }
-      .level-label {
-        flex-grow: 3;
-        padding: 0 2em 0 0;
-      }
-      .level-value {
-        text-align: right;
-      }
-    }
-
-    .fatal::before {
-      .level-mixin('fatal');
-    }
-    .error::before {
-      .level-mixin('error');
-    }
-    .warn::before {
-      .level-mixin('warning');
-    }
-    .info::before {
-      .level-mixin('info');
-    }
-    .trace::before {
-      .level-mixin('trace');
-    }
-    .debug::before {
-      .level-mixin('debug');
-    }
-    .unknown::before {
-      .level-mixin('unknown');
-    }
-  }
   header {
-    padding: .5rem;
-  }
-  .legends {
-    text-align: right;
-    .legend {
-      display: inline-block;
-      font-size: 1rem;
-      text-transform: uppercase;
-      padding-right: 1em;
-    }
-    .fatal::before {
-      .level-mixin('fatal');
-    }
-    .error::before {
-      .level-mixin('error');
-    }
-    .warn::before {
-      .level-mixin('warning');
-    }
-    .info::before {
-      .level-mixin('info');
-    }
-    .trace::before {
-      .level-mixin('trace');
-    }
-    .debug::before {
-      .level-mixin('debug');
-    }
-    .unknown::before {
-      .level-mixin('unknown');
-    }
+    padding: @graph-padding;
   }
 
   .time-gap {
-    color: #666;
+    color: @base-font-color;
     font-size: 1.2rem;
     text-align: center;
   }
 
   footer {
-    display: flex;
-    div {
-      color: #666;
-      flex-grow: 1;
-      font-size: 1.2rem;
-      padding: 0 1em .5em 1em;
-    }
-    .last-date-tick-label {
-      text-align: right;
-    }
+    .default-flex;
+    font-size: 1.2rem;
+    color: @base-font-color;
+    padding: 0 1em .5em;
   }
 
   /deep/ rect.drag-area {

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.spec.ts
index ee14780..09cd5d8 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.spec.ts
@@ -16,34 +16,41 @@
  * limitations under the License.
  */
 
-import {async, ComponentFixture, TestBed} from '@angular/core/testing';
+import {Injector} from '@angular/core';
+import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing';
 import {StoreModule} from '@ngrx/store';
 import {AppSettingsService, appSettings} from '@app/services/storage/app-settings.service';
 import {TranslationModules} from '@app/test-config.spec';
 import {MomentModule} from 'angular2-moment';
 import {MomentTimezoneModule} from 'angular-moment-timezone';
+import {ServiceInjector} from '@app/classes/service-injector';
 import {TimeZoneAbbrPipe} from '@app/pipes/timezone-abbr.pipe';
+import {GraphLegendComponent} from '@app/components/graph-legend/graph-legend.component';
+import {GraphLegendItemComponent} from '@app/components/graph-legend-item/graph-legend-item.component';
+import {GraphTooltipComponent} from '@app/components/graph-tooltip/graph-tooltip.component';
 
 import {ServiceLogsHistogramDataService} from '@app/services/storage/service-logs-histogram-data.service';
 import {TimeHistogramComponent} from './time-histogram.component';
 import {LogsContainerService} from '@app/services/logs-container.service';
-import {HttpClientService} from "@app/services/http-client.service";
-import {AppStateService} from "@app/services/storage/app-state.service";
-import {AuditLogsService} from "@app/services/storage/audit-logs.service";
-import {AuditLogsFieldsService} from "@app/services/storage/audit-logs-fields.service";
-import {ServiceLogsService} from "@app/services/storage/service-logs.service";
-import {ServiceLogsFieldsService} from "@app/services/storage/service-logs-fields.service";
-import {ServiceLogsTruncatedService} from "@app/services/storage/service-logs-truncated.service";
-import {TabsService} from "@app/services/storage/tabs.service";
-import {ClustersService} from "@app/services/storage/clusters.service";
-import {ComponentsService} from "@app/services/storage/components.service";
-import {HostsService} from "@app/services/storage/hosts.service";
+import {HttpClientService} from '@app/services/http-client.service';
+import {UtilsService} from '@app/services/utils.service';
+import {AppStateService} from '@app/services/storage/app-state.service';
+import {AuditLogsService} from '@app/services/storage/audit-logs.service';
+import {AuditLogsFieldsService} from '@app/services/storage/audit-logs-fields.service';
+import {ServiceLogsService} from '@app/services/storage/service-logs.service';
+import {ServiceLogsFieldsService} from '@app/services/storage/service-logs-fields.service';
+import {ServiceLogsTruncatedService} from '@app/services/storage/service-logs-truncated.service';
+import {TabsService} from '@app/services/storage/tabs.service';
+import {ClustersService} from '@app/services/storage/clusters.service';
+import {ComponentsService} from '@app/services/storage/components.service';
+import {HostsService} from '@app/services/storage/hosts.service';
+import {HomogeneousObject} from '@app/classes/object';
 
 describe('TimeHistogramComponent', () => {
   let component: TimeHistogramComponent;
   let fixture: ComponentFixture<TimeHistogramComponent>;
   let histogramData: any;
-  let customOptions: any;
+  let colors: HomogeneousObject<string>;
 
   beforeEach(async(() => {
     const httpClient = {
@@ -54,29 +61,42 @@ describe('TimeHistogramComponent', () => {
       }
     };
     histogramData = {
-      "1512476481940": {
-        "FATAL": 0,
-        "ERROR": 1000,
-        "WARN": 700,
-        "INFO": 0,
-        "DEBUG": 0,
-        "TRACE": 0,
-        "UNKNOWN": 0
-      }, "1512472881940": {"FATAL": 0, "ERROR": 2000, "WARN": 900, "INFO": 0, "DEBUG": 0, "TRACE": 0, "UNKNOWN": 0}
-    };
-    customOptions = {
-      keysWithColors: {
-        FATAL: '#830A0A',
-        ERROR: '#E81D1D',
-        WARN: '#FF8916',
-        INFO: '#2577B5',
-        DEBUG: '#65E8FF',
-        TRACE: '#888',
-        UNKNOWN: '#BDBDBD'
+      1512476481940: {
+        FATAL: 0,
+        ERROR: 1000,
+        WARN: 700,
+        INFO: 0,
+        DEBUG: 0,
+        TRACE: 0,
+        UNKNOWN: 0
+      },
+      1512472881940: {
+        FATAL: 0,
+        ERROR: 2000,
+        WARN: 900,
+        INFO: 0,
+        DEBUG: 0,
+        TRACE: 0,
+        UNKNOWN: 0
       }
     };
+    colors = {
+      FATAL: '#830A0A',
+      ERROR: '#E81D1D',
+      WARN: '#FF8916',
+      INFO: '#2577B5',
+      DEBUG: '#65E8FF',
+      TRACE: '#888',
+      UNKNOWN: '#BDBDBD'
+    };
     TestBed.configureTestingModule({
-      declarations: [TimeHistogramComponent, TimeZoneAbbrPipe],
+      declarations: [
+        TimeHistogramComponent,
+        GraphLegendComponent,
+        GraphLegendItemComponent,
+        GraphTooltipComponent,
+        TimeZoneAbbrPipe
+      ],
       imports: [
         StoreModule.provideStore({
           appSettings
@@ -93,6 +113,7 @@ describe('TimeHistogramComponent', () => {
           provide: HttpClientService,
           useValue: httpClient
         },
+        UtilsService,
         AppStateService,
         AuditLogsService,
         AuditLogsFieldsService,
@@ -109,14 +130,15 @@ describe('TimeHistogramComponent', () => {
       .compileComponents();
   }));
 
-  beforeEach(() => {
-      fixture = TestBed.createComponent(TimeHistogramComponent);
-      component = fixture.componentInstance;
-      component.customOptions = customOptions;
-      component.svgId = "HistogramSvg";
-      component.data = histogramData;
-      fixture.detectChanges();
-    });
+  beforeEach(inject([Injector], (injector: Injector) => {
+    ServiceInjector.injector = injector;
+    fixture = TestBed.createComponent(TimeHistogramComponent);
+    component = fixture.componentInstance;
+    component.colors = colors;
+    component.svgId = 'HistogramSvg';
+    component.data = histogramData;
+    fixture.detectChanges();
+  }));
 
   it('should create component', () => {
     expect(component).toBeTruthy();

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.ts
index fb3092f..8544677 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.ts
@@ -16,100 +16,48 @@
  * limitations under the License.
  */
 
-import {Component, OnInit, AfterViewInit, OnChanges, Input, Output, ViewChild, ElementRef, EventEmitter} from '@angular/core';
-import {ContainerElement, Selection} from 'd3';
+import {Component, OnInit, Input, Output, EventEmitter} from '@angular/core';
 import * as d3 from 'd3';
 import * as moment from 'moment-timezone';
 import {AppSettingsService} from '@app/services/storage/app-settings.service';
-import {HistogramStyleOptions, HistogramOptions} from '@app/classes/histogram-options';
+import {GraphComponent} from '@app/classes/components/graph/graph.component';
+import {GraphScaleItem} from '@app/classes/graph';
 
 @Component({
   selector: 'time-histogram',
   templateUrl: './time-histogram.component.html',
-  styleUrls: ['./time-histogram.component.less']
+  styleUrls: ['../../classes/components/graph/graph.component.less', './time-histogram.component.less']
 })
-export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges {
+export class TimeHistogramComponent extends GraphComponent implements OnInit {
 
-  constructor(private appSettings: AppSettingsService) {}
+  constructor(private appSettings: AppSettingsService) {
+    super();
+  }
 
   ngOnInit() {
     this.appSettings.getParameter('timeZone').subscribe((value: string): void => {
       this.timeZone = value;
-      this.createHistogram();
+      this.createGraph();
     });
-    this.options = Object.assign({}, this.defaultOptions, this.customOptions);
-  }
-
-  ngAfterViewInit() {
-    this.htmlElement = this.element.nativeElement;
-    this.tooltipElement = this.tooltipEl.nativeElement;
-    this.host = d3.select(this.htmlElement);
-  }
-
-  ngOnChanges() {
-    this.createHistogram();
   }
 
-  @ViewChild('container')
-  element: ElementRef;
-
-  @ViewChild('tooltipEl')
-  tooltipEl: ElementRef;
-
-  @Input()
-  svgId: string;
-
   @Input()
-  customOptions: HistogramOptions;
-
-  @Input()
-  data: {[key: string]: number};
+  columnWidth = {
+    second: 40,
+    minute: 30,
+    hour: 25,
+    day: 20,
+    base: 20
+  };
 
   @Output()
   selectArea: EventEmitter<number[]> = new EventEmitter();
 
-  private readonly defaultOptions: HistogramStyleOptions = {
-    margin: {
-      top: 5,
-      right: 50,
-      bottom: 30,
-      left: 50
-    },
-    height: 150,
-    tickPadding: 10,
-    columnWidth: {
-      second: 40,
-      minute: 30,
-      hour: 25,
-      day: 20,
-      base: 20
-    }
-  };
-
-  private options: HistogramOptions;
+  readonly isTimeGraph: boolean = true;
 
   private timeZone: string;
 
-  private host;
-
-  private svg;
-
-  private width: number;
-
-  private xScale;
-
-  private yScale;
-
-  private colorScale;
-
-  private xAxis;
-
-  private yAxis;
-
-  private htmlElement: HTMLElement;
-  private tooltipElement: HTMLElement;
-
-  private dragArea: Selection<SVGGraphicsElement, undefined, SVGGraphicsElement, undefined>;
+  private dragArea: d3.Selection<SVGGraphicsElement, undefined, SVGGraphicsElement, undefined>;
 
   private dragStartX: number;
 
@@ -118,26 +66,12 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges
   private maxDragX: number;
 
   private readonly tickTimeFormat: string = 'MM/DD HH:mm';
-  private readonly historyStartEndTimeFormat = 'dddd, MMMM DD, YYYY';
+
+  private readonly historyStartEndTimeFormat: string = 'dddd, MMMM DD, YYYY';
 
   histogram: any;
 
   /**
-   * This property is to hold the data of the bar where the mouse is over.
-   */
-  private tooltipInfo: {data: object, timeStamp: number};
-  /**
-   * This is the computed position of the tooltip relative to the @htmlElement which is the container of the histogram.
-   * It is set when the mousemoving over the bars in the @handleRectMouseMove method.
-   */
-  private tooltipPosition: {top: number, left: number};
-  /**
-   * This property indicates if the tooltip should be positioned on the left side of the cursor or not.
-   * It should be true when the tooltip is out from the window.
-   * @type {boolean}
-   */
-  private tooltipOnTheLeft: boolean = false;
-  /**
    * This property holds the data structure describing the gaps between the xAxis ticks.
    * The unit property can be: second, minute, hour, day
    * The value is the number of the given unit.
@@ -146,11 +80,11 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges
   /**
    * This is the rectangle element to represent the unselected time range on the left side of the selected time range
    */
-  private leftDragArea: Selection<SVGGraphicsElement, undefined, SVGGraphicsElement, undefined>;
+  private leftDragArea: d3.Selection<SVGGraphicsElement, undefined, SVGGraphicsElement, undefined>;
   /**
    * This is the rectangle element to represent the unselected time range on the right side of the selected time range
    */
-  private rightDragArea: Selection<SVGGraphicsElement, undefined, SVGGraphicsElement, undefined>;
+  private rightDragArea: d3.Selection<SVGGraphicsElement, undefined, SVGGraphicsElement, undefined>;
   /**
    * This is a Date object holding the value of the first tick of the xAxis. It is a helper getter for the template.
    */
@@ -166,134 +100,12 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges
     return (ticks && ticks.length && ticks[ticks.length-1]) || undefined;
   }
 
-  /**
-   * This will return the information about the used levels and the connected colors and labels.
-   * The goal is to provide an easy property to the template to display the legend of the levels.
-   * @returns {Array<{level: string; label: string; color: string}>}
-   */
-  private get legends(): Array<{level: string, label: string, color: string}> {
-    return Object.keys(this.options.keysWithColors).map(level => Object.assign({},{
-      level,
-      label: `levels.${level.toLowerCase()}`,
-      color: this.options.keysWithColors[level]
-    }));
-  }
-
-  private createHistogram(): void {
-    if (this.host) {
-      this.setup();
-      this.buildSVG();
-      this.populate();
-    }
-  }
-
-  private setup(): void {
-    const margin = this.options.margin,
-      keysWithColors = this.options.keysWithColors,
-      keys = Object.keys(keysWithColors),
-      colors = keys.reduce((array: string[], key: string): string[] => [...array, keysWithColors[key]], []);
-    this.width = this.htmlElement.clientWidth - margin.left - margin.right;
-    this.xScale = d3.scaleTime().range([0, this.width]);
-    this.yScale = d3.scaleLinear().range([this.options.height, 0]);
-    this.colorScale = d3.scaleOrdinal(colors);
-  }
-
-  private buildSVG(): void {
-    const margin = this.options.margin;
-    this.host.html('');
-    this.svg = this.host.append('svg').attr('id', this.svgId).attr('width', this.htmlElement.clientWidth)
-      .attr('height', this.options.height + margin.top + margin.bottom).append('g')
-      .attr('transform', `translate(${margin.left},${margin.top})`);
-  }
-
-  /**
-   * It draws the svg representation of the x axis. The goal is to set the ticks here, add the axis to the svg element
-   * and set the position of the axis.
-   */
-  private drawXAxis(): void {
-    this.xAxis = d3.axisBottom(this.xScale)
-      .tickFormat(tick => moment(tick).tz(this.timeZone).format(this.tickTimeFormat))
-      .tickPadding(this.options.tickPadding);
-    this.svg.append('g').attr('class', 'axis axis-x').attr('transform', `translate(0,${this.options.height})`).call(this.xAxis);
-  }
-
-  /**
-   * It draws the svg representation of the y axis. The goal is to set the ticks here, add the axis to the svg element
-   * and set the position of the axis.
-   */
-  private drawYAxis(): void {
-    this.yAxis = d3.axisLeft(this.yScale).tickFormat((tick: number): string | undefined => {
-      if (Number.isInteger(tick)) {
-        return tick.toFixed(0);
-      } else {
-        return;
-      }
-    }).tickPadding(this.options.tickPadding);
-    this.svg.append('g').attr('class', 'axis axis-y').call(this.yAxis).append('text');
-  };
-
-  /**
-   * The goal is to handle the mouse over event on the rect svg elements so that we can populate the tooltip info object
-   * and set the initial position of the tooltip. So we call the corresponding methods.
-   * @param d The data for the currently "selected" bar
-   * @param {number} index The index of the current element in the selection
-   * @param elements The selection of the elements
-   */
-  private handleRectMouseOver = (d: any, index: number, elements: any):void => {
-    this.setTooltipDataFromChartData(d);
-    this.setTooltipPosition();
-  };
-
-  /**
-   * The goal is to handle the movement of the mouse over the rect svg elements, so that we can set the position of
-   * the tooltip by calling the @setTooltipPosition method.
-   */
-  private handleRectMouseMove = ():void => {
-    this.setTooltipPosition();
+  protected xAxisTickFormatter = (tick: Date): string => {
+    return moment(tick).tz(this.timeZone).format(this.tickTimeFormat);
   };
 
-  /**
-   * The goal is to reset the tooltipInfo object so that the tooltip will be hidden.
-   */
-  private handleRectMouseOut = ():void => {
-    this.tooltipInfo = null;
-  };
-
-  /**
-   * The goal is set the tooltip
-   * @param d
-   */
-  private setTooltipDataFromChartData(d: {data: any, [key: string]: any}): void {
-    let {timeStamp, ...data} = d.data;
-    let levelColors = this.options.keysWithColors;
-    this.tooltipInfo = {
-      data: Object.keys(levelColors).map(key => Object.assign({}, {
-        level: key,
-        levelLabel: `levels.${key.toLowerCase()}`,
-        value: data[key]
-      })),
-      timeStamp
-    };
-  }
-
-  /**
-   * The goal of this function is to set the tooltip position regarding the d3.mouse event relative to the @htmlElement.
-   * Onlty if we have @tooltipInfo
-   */
-  private setTooltipPosition():void {
-    if (this.tooltipInfo) {
-      let tEl = this.tooltipElement;
-      let pos = d3.mouse(this.htmlElement);
-      let left = pos[0];
-      let top = pos[1] - (tEl.offsetHeight / 2);
-      let tooltipWidth = tEl.offsetWidth;
-      let windowSize = window.innerWidth;
-      if (left + tooltipWidth > windowSize) {
-        left = pos[0] - (tooltipWidth + 25);
-      }
-      this.tooltipOnTheLeft = left < pos[0];
-      this.tooltipPosition = {left, top};
-    }
+  protected yAxisTickFormatter = (tick: number): string | undefined => {
+    return Number.isInteger(tick) ? tick.toFixed(0) : undefined;
   };
 
   /**
@@ -361,33 +173,33 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges
   /**
    * Set the domain for the y scale regarding the given data. The maximum value of the data is the sum of the log level
    * values.
-   * An example data: [{timeStamp: 1233455677, WARN: 12, ERROR: 123}]
-   * @param {Array<{timeStamp: number; [p: string]: number}>} data
+   * An example data: [{tick: 1233455677, WARN: 12, ERROR: 123}]
+   * @param {GraphScaleItem[]} data
    */
-  private setYScaleDomain(data: Array<{timeStamp: number, [key: string]: number}>): void {
-    const keys = Object.keys(this.options.keysWithColors);
+  protected setYScaleDomain(data: GraphScaleItem[]): void {
+    const keys = Object.keys(this.labels);
     const maxYValue = d3.max(data, item => keys.reduce((sum: number, key: string): number => sum + item[key], 0));
     this.yScale.domain([0, maxYValue]);
   }
 
   /**
    * Set the domain values for the x scale regarding the given data.
-   * An example data: [{timeStamp: 1233455677, WARN: 12, ERROR: 123}]
-   * @param {Array<{timeStamp: number; [p: string]: any}>} data
+   * An example data: [{tick: 1233455677, WARN: 12, ERROR: 123}]
+   * @param {GraphScaleItem[]} data
    */
-  private setXScaleDomain(data: Array<{timeStamp: number, [key: string]: any}>): void {
-    this.xScale.domain(d3.extent(data, item => item.timeStamp)).nice();
+  protected setXScaleDomain(data: GraphScaleItem[]): void {
+    this.xScale.domain(d3.extent(data, item => item.tick)).nice();
   }
 
-  private populate(): void {
-    const keys = Object.keys(this.options.keysWithColors);
+  protected populate(): void {
+    const keys = Object.keys(this.colors);
     const data = this.data;
     const timeStamps = Object.keys(data);
     // we create a more consumable data structure for d3
-    const formattedData = timeStamps.map((timeStamp: string): {timeStamp: number, [key: string]: number} => Object.assign({
-        timeStamp: Number(timeStamp)
+    const formattedData = timeStamps.map((timeStamp: string): {tick: number, [key: string]: number} => Object.assign({
+        tick: Number(timeStamp)
       }, data[timeStamp]));
-    const layers = (d3.stack().keys(keys)(formattedData));
+    const layers = d3.stack().keys(keys)(formattedData);
 
     // after we have the data we set the domain values both scales
     this.setXScaleDomain(formattedData);
@@ -396,39 +208,32 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges
     // Setting the timegap label above the chart
     this.setChartTimeGapByXScale();
 
-    let unitD3TimeProp = this.chartTimeGap.unit.charAt(0).toUpperCase() + this.chartTimeGap.unit.slice(1);
+    const unitD3TimeProp = this.chartTimeGap.unit.charAt(0).toUpperCase() + this.chartTimeGap.unit.slice(1);
     this.xScale.nice(d3[`time${unitD3TimeProp}`], 2);
 
-    let columnWidth = this.options.columnWidth[this.chartTimeGap.unit] || this.options.columnWidth.base;
+    const columnWidth = this.columnWidth[this.chartTimeGap.unit] || this.columnWidth.base;
 
     // drawing the axis
     this.drawXAxis();
     this.drawYAxis();
 
     // populate the data and drawing the bars
-    const layer = this.svg.selectAll('.value').data(d3.transpose<any>(layers))
-                    .attr('class', 'value')
-                  .enter().append('g')
-                    .attr('class', 'value');
-    layer.selectAll('.value rect').data(item => item)
-        .attr('x', item => this.xScale(item.data.timeStamp) - columnWidth / 2)
-        .attr('y', item => this.yScale(item[1]))
-        .attr('height', item => this.yScale(item[0]) - this.yScale(item[1]))
-        .attr('width', columnWidth.toString())
-        .style('fill', (item, index) => this.colorScale(index))
-      .enter().append('rect')
-        .attr('x', item => this.xScale(item.data.timeStamp) - columnWidth / 2)
+    const layer = this.svg.selectAll().data(d3.transpose<any>(layers))
+      .enter().append('g')
+      .attr('class', 'value');
+    layer.selectAll().data(item => item).enter().append('rect')
+        .attr('x', item => this.xScale(item.data.tick) - columnWidth / 2)
         .attr('y', item => this.yScale(item[1]))
         .attr('height', item => this.yScale(item[0]) - this.yScale(item[1]))
         .attr('width', columnWidth.toString())
-        .style('fill', (item, index) => this.colorScale(index))
+        .style('fill', (item, index) => this.orderedColors[index])
         .on('mouseover', this.handleRectMouseOver)
         .on('mousemove', this.handleRectMouseMove)
         .on('mouseout', this.handleRectMouseOut);
     this.setDragBehavior();
   }
 
-  private getTimeRangeByXRanges(startX: number, endX:number): [number, number] {
+  private getTimeRangeByXRanges(startX: number, endX: number): [number, number] {
     const xScaleInterval = this.xScale.domain().map((point: Date): number => point.valueOf());
     const xScaleLength = xScaleInterval[1] - xScaleInterval[0];
     const ratio = xScaleLength / this.width;
@@ -442,7 +247,7 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges
    * @param {number} currentX This is the ending point of the drag within the container
    */
   private createInvertDragArea(startX: number, currentX: number): void {
-    const height: number = this.options.height + this.options.margin.top + this.options.margin.bottom;
+    const height: number = this.height + this.margin.top + this.margin.bottom;
     this.leftDragArea = this.svg.insert('rect').attr('height', height).attr('class', 'unselected-drag-area');
     this.rightDragArea = this.svg.insert('rect').attr('height', height).attr('class', 'unselected-drag-area');
     this.setInvertDragArea(startX, currentX);
@@ -456,9 +261,8 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges
   private setInvertDragArea(startX: number, currentX: number): void {
     const left: number = Math.min(startX, currentX);
     const right: number = Math.max(startX, currentX);
-    let rightAreaWidth: number = this.width - right;
-    rightAreaWidth = rightAreaWidth > 0 ? rightAreaWidth : 0;
-    let leftAreaWidth: number = left > 0 ? left : 0;
+    const rightAreaWidth: number = Math.max(0, this.width - right);
+    const leftAreaWidth: number = Math.max(0, left);
     this.leftDragArea.attr('x', 0).attr('width', leftAreaWidth);
     this.rightDragArea.attr('x', right).attr('width', rightAreaWidth);
   }
@@ -472,20 +276,20 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges
   }
 
   private setDragBehavior(): void {
-    this.minDragX = this.options.margin.left;
-    this.maxDragX = this.htmlElement.clientWidth;
+    this.minDragX = this.margin.left;
+    this.maxDragX = this.graphContainer.clientWidth;
     d3.selectAll(`svg#${this.svgId}`).call(d3.drag()
-      .on('start', (datum: undefined, index: number, containers: ContainerElement[]): void => {
+      .on('start', (datum: undefined, index: number, containers: d3.ContainerElement[]): void => {
         if (this.dragArea) {
           this.dragArea.remove();
         }
-        this.dragStartX = Math.max(0, this.getDragX(containers[0]) - this.options.margin.left);
+        this.dragStartX = Math.max(0, this.getDragX(containers[0]) - this.margin.left);
         this.dragArea = this.svg.insert('rect', ':first-child').attr('x', this.dragStartX).attr('y', 0).attr('width', 0)
-          .attr('height', this.options.height).attr('class', 'drag-area');
+          .attr('height', this.height).attr('class', 'drag-area');
       })
-      .on('drag', (datum: undefined, index: number, containers: ContainerElement[]): void => {
+      .on('drag', (datum: undefined, index: number, containers: d3.ContainerElement[]): void => {
         const mousePos = this.getDragX(containers[0]);
-        const currentX = Math.max(mousePos, this.minDragX) - this.options.margin.left;
+        const currentX = Math.max(mousePos, this.minDragX) - this.margin.left;
         const startX = Math.min(currentX, this.dragStartX);
         const currentWidth = Math.abs(currentX - this.dragStartX);
         this.dragArea.attr('x', startX).attr('width', currentWidth);
@@ -507,7 +311,7 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges
     }));
   }
 
-  private getDragX(element: ContainerElement): number {
+  private getDragX(element: d3.ContainerElement): number {
     return d3.mouse(element)[0];
   }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts
index 6df7ab3..8d739ec 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts
@@ -20,6 +20,7 @@ import {Component} from '@angular/core';
 import {FormGroup} from '@angular/forms';
 import {FilterCondition, TimeUnitListItem} from '@app/classes/filtering';
 import {ListItem} from '@app/classes/list-item';
+import {HomogeneousObject} from '@app/classes/object';
 import {LogsContainerService} from '@app/services/logs-container.service';
 
 @Component({
@@ -36,7 +37,7 @@ export class TopMenuComponent {
     return this.logsContainer.filtersForm;
   };
 
-  get filters(): {[key: string]: FilterCondition} {
+  get filters(): HomogeneousObject<FilterCondition> {
     return this.logsContainer.filters;
   };
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/variables.less
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/variables.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/variables.less
index a9ca3b4..9b9bbfd 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/variables.less
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/variables.less
@@ -65,3 +65,6 @@
 
 // Table
 @table-border-color: #EEE;
+
+// Graph
+@graph-padding: .5rem;

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/mock-data.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/mock-data.ts b/ambari-logsearch/ambari-logsearch-web/src/app/mock-data.ts
index 7578867..aa86f4f 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/mock-data.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/mock-data.ts
@@ -18,6 +18,8 @@
 
 import * as moment from 'moment';
 
+const currentTime = moment();
+
 export const mockData = {
   login: {},
   logout: {},
@@ -153,34 +155,82 @@ export const mockData = {
           },
           components: {},
           resources: {
-            graphData: [
-              {
-                dataCount: [
-                  {
-                    name: 'n16',
-                    value: 800
-                  },
-                  {
-                    name: 'n17',
-                    value: 400
-                  }
-                ],
-                name: 'graph8'
-              },
-              {
-                dataCount: [
-                  {
-                    name: 'n18',
-                    value: 600
-                  },
-                  {
-                    name: 'n19',
-                    value: 300
-                  }
-                ],
-                name: 'graph9'
-              }
-            ]
+            6: {
+              graphData: [
+                {
+                  dataCount: [
+                    {
+                      name: 'hdfs',
+                      value: 800
+                    },
+                    {
+                      name: 'zookeeper',
+                      value: 400
+                    },
+                    {
+                      name: 'ambari_metrics',
+                      value: 200
+                    }
+                  ],
+                  name: 'admin'
+                },
+                {
+                  dataCount: [
+                    {
+                      name: 'ambari_agent',
+                      value: 400
+                    },
+                    {
+                      name: 'hdfs',
+                      value: 600
+                    },
+                    {
+                      name: 'ambari_metrics',
+                      value: 300
+                    }
+                  ],
+                  name: 'user'
+                }
+              ]
+            },
+            10: {
+              graphData: [
+                {
+                  dataCount: [
+                    {
+                      name: 'ambari',
+                      value: 800
+                    },
+                    {
+                      name: 'hdfs',
+                      value: 400
+                    },
+                    {
+                      name: 'hbase',
+                      value: 200
+                    },
+                  ],
+                  name: '/user'
+                },
+                {
+                  dataCount: [
+                    {
+                      name: 'hdfs',
+                      value: 400
+                    },
+                    {
+                      name: 'hbase',
+                      value: 600
+                    },
+                    {
+                      name: 'kafka',
+                      value: 300
+                    }
+                  ],
+                  name: '/root'
+                }
+              ]
+            }
           },
           schema: {
             fields: {
@@ -299,7 +349,7 @@ export const mockData = {
               path: '/var/log/ambari-metrics-collector/ambari-metrics-collector.log',
               host: 'h0',
               level: 'WARN',
-              logtime: moment().valueOf(),
+              logtime: currentTime.valueOf(),
               ip: '192.168.0.1',
               logfile_line_number: 8,
               type: 'ams_collector',
@@ -316,14 +366,14 @@ export const mockData = {
               event_md5: '1908755391',
               event_dur_ms: 200,
               _ttl_: '+5DAYS',
-              _expire_at_: moment().add(5, 'd').valueOf(),
+              _expire_at_: currentTime.clone().add(5, 'd').valueOf(),
               _router_field_: 20
             },
             {
               path: '/var/log/ambari-metrics-collector/ambari-metrics-collector.log',
               host: 'h1',
               level: 'ERROR',
-              logtime: moment().subtract(2, 'd').valueOf(),
+              logtime: currentTime.clone().subtract(2, 'd').valueOf(),
               ip: '192.168.0.2',
               type: 'ams_collector',
               _version_: 14,
@@ -340,14 +390,14 @@ export const mockData = {
               event_md5: '1029384756',
               event_dur_ms: 700,
               _ttl_: '+5DAYS',
-              _expire_at_: moment().add(3, 'd').valueOf(),
+              _expire_at_: currentTime.clone().add(3, 'd').valueOf(),
               _router_field_: 5
             },
             {
               path: '/var/log/ambari-metrics-collector/ambari-metrics-collector.log',
               host: 'h1',
               level: 'FATAL',
-              logtime: moment().subtract(10, 'd').valueOf(),
+              logtime: currentTime.clone().subtract(10, 'd').valueOf(),
               ip: '192.168.0.3',
               type: 'ambari_agent',
               _version_: 14,
@@ -364,14 +414,14 @@ export const mockData = {
               event_md5: '67589403',
               event_dur_ms: 100,
               _ttl_: '+5DAYS',
-              _expire_at_: moment().subtract(5, 'd').valueOf(),
+              _expire_at_: currentTime.clone().subtract(5, 'd').valueOf(),
               _router_field_: 45
             },
             {
               path: '/var/log/ambari-metrics-collector/zookeeper-server.log',
               host: 'h1',
               level: 'INFO',
-              logtime: moment().subtract(25, 'h').valueOf(),
+              logtime: currentTime.clone().subtract(25, 'h').valueOf(),
               ip: '192.168.0.4',
               type: 'zookeeper_server',
               _version_: 14,
@@ -388,14 +438,14 @@ export const mockData = {
               event_md5: '67589403',
               event_dur_ms: 1000,
               _ttl_: '+5DAYS',
-              _expire_at_: moment().subtract(25, 'h').add(5, 'd').valueOf(),
+              _expire_at_: currentTime.clone().subtract(25, 'h').add(5, 'd').valueOf(),
               _router_field_: 55
             },
             {
               path: '/var/log/ambari-metrics-collector/zookeeper-server.log',
               host: 'h1',
               level: 'DEBUG',
-              logtime: moment().subtract(25, 'd').valueOf(),
+              logtime: currentTime.clone().subtract(25, 'd').valueOf(),
               ip: '192.168.0.4',
               type: 'zookeeper_server',
               _version_: 14,
@@ -412,14 +462,14 @@ export const mockData = {
               event_md5: '67589403',
               event_dur_ms: 1000,
               _ttl_: '+5DAYS',
-              _expire_at_: moment().subtract(20, 'd').valueOf(),
+              _expire_at_: currentTime.clone().subtract(20, 'd').valueOf(),
               _router_field_: 55
             },
             {
               path: '/var/log/ambari-metrics-collector/zookeeper-client.log',
               host: 'h1',
               level: 'TRACE',
-              logtime: moment().subtract(2, 'h').valueOf(),
+              logtime: currentTime.clone().subtract(2, 'h').valueOf(),
               ip: '192.168.0.4',
               type: 'zookeeper_client',
               _version_: 14,
@@ -436,14 +486,14 @@ export const mockData = {
               event_md5: '67589403',
               event_dur_ms: 1000,
               _ttl_: '+5DAYS',
-              _expire_at_: moment().subtract(2, 'h').add(5, 'd').valueOf(),
+              _expire_at_: currentTime.clone().subtract(2, 'h').add(5, 'd').valueOf(),
               _router_field_: 55
             },
             {
               path: '/var/log/ambari-metrics-collector/zookeeper-client.log',
               host: 'h1',
               level: 'UNKNOWN',
-              logtime: moment().subtract(31, 'd').valueOf(),
+              logtime: currentTime.clone().subtract(31, 'd').valueOf(),
               ip: '192.168.0.4',
               type: 'zookeeper_client',
               _version_: 14,
@@ -460,7 +510,7 @@ export const mockData = {
               event_md5: '67589403',
               event_dur_ms: 1000,
               _ttl_: '+5DAYS',
-              _expire_at_: moment().subtract(26, 'd').valueOf(),
+              _expire_at_: currentTime.clone().subtract(26, 'd').valueOf(),
               _router_field_: 55
             }
           ],
@@ -637,11 +687,11 @@ export const mockData = {
               {
                 dataCount: [
                   {
-                    name: moment().toISOString(),
+                    name: currentTime.toISOString(),
                     value: '1000'
                   },
                   {
-                    name: moment().subtract(1, 'h').toISOString(),
+                    name: currentTime.clone().subtract(1, 'h').toISOString(),
                     value: '2000'
                   }
                 ],
@@ -650,11 +700,11 @@ export const mockData = {
               {
                 dataCount: [
                   {
-                    name: moment().toISOString(),
+                    name: currentTime.toISOString(),
                     value: '700'
                   },
                   {
-                    name: moment().subtract(1, 'h').toISOString(),
+                    name: currentTime.clone().subtract(1, 'h').toISOString(),
                     value: '900'
                   }
                 ],

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.ts
index 51b0c0b..36c4d8d 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.ts
@@ -83,7 +83,6 @@ export class ComponentActionsService {
   openLog(log: ServiceLog): void {
     const tab = {
       id: log.id,
-      type: 'serviceLogs',
       isCloseable: true,
       label: `${log.host} >> ${log.type}`,
       appState: {

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/services/http-client.service.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/http-client.service.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/http-client.service.ts
index 9b61bf6..6f98bf5 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/services/http-client.service.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/http-client.service.ts
@@ -19,8 +19,12 @@
 import {Injectable} from '@angular/core';
 import {Observable} from 'rxjs/Observable';
 import 'rxjs/add/operator/first';
-import {Http, XHRBackend, Request, RequestOptions, RequestOptionsArgs, Response, Headers, URLSearchParams} from '@angular/http';
-import {AuditLogsQueryParams} from '@app/classes/queries/audit-logs-query-params';
+import {
+  Http, XHRBackend, Request, RequestOptions, RequestOptionsArgs, Response, Headers, URLSearchParams
+} from '@angular/http';
+import {HomogeneousObject} from '@app/classes/object';
+import {AuditLogsListQueryParams} from '@app/classes/queries/audit-logs-query-params';
+import {AuditLogsTopResourcesQueryParams} from '@app/classes/queries/audit-logs-top-resources-query-params';
 import {ServiceLogsQueryParams} from '@app/classes/queries/service-logs-query-params';
 import {ServiceLogsHistogramQueryParams} from '@app/classes/queries/service-logs-histogram-query-params';
 import {ServiceLogsTruncatedQueryParams} from '@app/classes/queries/service-logs-truncated-query-params';
@@ -41,7 +45,7 @@ export class HttpClientService extends Http {
     },
     auditLogs: {
       url: 'audit/logs',
-      params: opts => new AuditLogsQueryParams(opts)
+      params: opts => new AuditLogsListQueryParams(opts)
     },
     auditLogsFields: {
       url: 'audit/logs/schema/fields'
@@ -69,14 +73,31 @@ export class HttpClientService extends Http {
     },
     hosts: {
       url: 'service/logs/tree'
+    },
+    topAuditLogsResources: {
+      url: variables => `audit/logs/resources/${variables.number}`,
+      params: opts => new AuditLogsTopResourcesQueryParams(opts)
     }
   };
 
   private readonly unauthorizedStatuses = [401, 403, 419];
 
-  private generateUrlString(url: string): string {
+  private generateUrlString(url: string, urlVariables?: HomogeneousObject<string>): string {
     const preset = this.endPoints[url];
-    return preset ? `${this.apiPrefix}${preset.url}` : url;
+    let generatedUrl: string;
+    if (preset) {
+      const urlExpression = preset.url;
+      let path: string;
+      if (typeof urlExpression === 'function') {
+        path = preset.url(urlVariables);
+      } else if (typeof urlExpression === 'string') {
+        path = preset.url;
+      }
+      generatedUrl = `${this.apiPrefix}${path}`;
+    } else {
+      generatedUrl = url;
+    }
+    return generatedUrl;
   }
 
   private generateUrl(request: string | Request): string | Request {
@@ -89,7 +110,7 @@ export class HttpClientService extends Http {
     }
   }
 
-  private generateOptions(url: string, params: {[key: string]: string}): RequestOptionsArgs {
+  private generateOptions(url: string, params: HomogeneousObject<string>): RequestOptionsArgs {
     const preset = this.endPoints[url],
       rawParams = preset && preset.params ? preset.params(params) : params;
     if (rawParams) {
@@ -122,11 +143,11 @@ export class HttpClientService extends Http {
     return req;
   }
 
-  get(url, params?: {[key: string]: string}): Observable<Response> {
-    return super.get(this.generateUrlString(url), this.generateOptions(url, params));
+  get(url, params?: HomogeneousObject<string>, urlVariables?: HomogeneousObject<string>): Observable<Response> {
+    return super.get(this.generateUrlString(url, urlVariables), this.generateOptions(url, params));
   }
 
-  postFormData(url: string, params: {[key: string]: string}, options?: RequestOptionsArgs): Observable<Response> {
+  postFormData(url: string, params: HomogeneousObject<string>, options?: RequestOptionsArgs): Observable<Response> {
     const encodedParams = this.generateOptions(url, params).params;
     let body;
     if (encodedParams && encodedParams instanceof URLSearchParams) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.spec.ts
index 870058b..acdc0c2 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.spec.ts
@@ -23,7 +23,9 @@ import {AuditLogsService, auditLogs} from '@app/services/storage/audit-logs.serv
 import {ServiceLogsService, serviceLogs} from '@app/services/storage/service-logs.service';
 import {AuditLogsFieldsService, auditLogsFields} from '@app/services/storage/audit-logs-fields.service';
 import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.service';
-import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/services/storage/service-logs-histogram-data.service';
+import {
+  ServiceLogsHistogramDataService, serviceLogsHistogramData
+} from '@app/services/storage/service-logs-histogram-data.service';
 import {AppSettingsService, appSettings} from '@app/services/storage/app-settings.service';
 import {AppStateService, appState} from '@app/services/storage/app-state.service';
 import {ClustersService, clusters} from '@app/services/storage/clusters.service';

http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts
index d719893..78bcdb1 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts
@@ -27,7 +27,6 @@ import 'rxjs/add/operator/first';
 import 'rxjs/add/operator/map';
 import 'rxjs/add/operator/takeUntil';
 import * as moment from 'moment-timezone';
-import {TranslateService} from '@ngx-translate/core';
 import {HttpClientService} from '@app/services/http-client.service';
 import {AuditLogsService} from '@app/services/storage/audit-logs.service';
 import {AuditLogsFieldsService} from '@app/services/storage/audit-logs-fields.service';
@@ -46,6 +45,7 @@ import {
   FilterCondition, TimeUnitListItem, SortingListItem, SearchBoxParameter, SearchBoxParameterTriggered
 } from '@app/classes/filtering';
 import {ListItem} from '@app/classes/list-item';
+import {HomogeneousObject} from '@app/classes/object';
 import {LogsType, ScrollType, SortingType} from '@app/classes/string';
 import {Tab} from '@app/classes/models/tab';
 import {LogField} from '@app/classes/models/log-field';
@@ -61,15 +61,15 @@ import {CommonEntry} from '@app/classes/models/common-entry';
 export class LogsContainerService {
 
   constructor(
-    private translate: TranslateService, private httpClient: HttpClientService,
-    private auditLogsStorage: AuditLogsService, private auditLogsFieldsStorage: AuditLogsFieldsService,
-    private serviceLogsStorage: ServiceLogsService, private serviceLogsFieldsStorage: ServiceLogsFieldsService,
-    private serviceLogsHistogramStorage: ServiceLogsHistogramDataService,
-    private serviceLogsTruncatedStorage: ServiceLogsTruncatedService, private appState: AppStateService,
-    private appSettings: AppSettingsService, private tabsStorage: TabsService, private clustersStorage: ClustersService,
-    private componentsStorage: ComponentsService, private hostsStorage: HostsService
+    private httpClient: HttpClientService, private appState: AppStateService,
+    private appSettings: AppSettingsService, private auditLogsStorage: AuditLogsService,
+    private auditLogsFieldsStorage: AuditLogsFieldsService, private serviceLogsStorage: ServiceLogsService,
+    private serviceLogsFieldsStorage: ServiceLogsFieldsService, private tabsStorage: TabsService,
+    private serviceLogsHistogramStorage: ServiceLogsHistogramDataService, private clustersStorage: ClustersService,
+    private componentsStorage: ComponentsService, private hostsStorage: HostsService,
+    private serviceLogsTruncatedStorage: ServiceLogsTruncatedService
   ) {
-    const formItems = Object.keys(this.filters).reduce((currentObject: any, key: string): {[key: string]: FormControl} => {
+    const formItems = Object.keys(this.filters).reduce((currentObject: any, key: string): HomogeneousObject<FormControl> => {
       let formControl = new FormControl(),
         item = {
           [key]: formControl
@@ -88,7 +88,7 @@ export class LogsContainerService {
     tabsStorage.mapCollection((tab: Tab): Tab => {
       let currentAppState = tab.appState || {};
       const appState = Object.assign({}, currentAppState, {
-        activeFilters: this.getFiltersData(tab.type)
+        activeFilters: this.getFiltersData(tab.appState.activeLogsType)
       });
       return Object.assign({}, tab, {
         appState
@@ -120,7 +120,7 @@ export class LogsContainerService {
 
   private readonly paginationOptions: string[] = ['10', '25', '50', '100'];
 
-  filters: {[key: string]: FilterCondition} = {
+  filters: HomogeneousObject<FilterCondition> = {
     clusters: {
       label: 'filter.clusters',
       options: [],
@@ -507,18 +507,25 @@ export class LogsContainerService {
     query: ['includeQuery', 'excludeQuery']
   };
 
+  readonly topResourcesCount: string = '10';
+
+  readonly topUsersCount: string = '6';
+
   readonly logsTypeMap = {
     auditLogs: {
       logsModel: this.auditLogsStorage,
       fieldsModel: this.auditLogsFieldsStorage,
       // TODO add all the required fields
       listFilters: ['clusters', 'timeRange', 'auditLogsSorting', 'pageSize', 'page', 'query'],
+      topResourcesFilters: ['clusters', 'timeRange', 'query'],
       histogramFilters: ['clusters', 'timeRange', 'query']
     },
     serviceLogs: {
       logsModel: this.serviceLogsStorage,
       fieldsModel: this.serviceLogsFieldsStorage,
-      listFilters: ['clusters', 'timeRange', 'components', 'levels', 'hosts', 'serviceLogsSorting', 'pageSize', 'page', 'query'],
+      listFilters: [
+        'clusters', 'timeRange', 'components', 'levels', 'hosts', 'serviceLogsSorting', 'pageSize', 'page', 'query'
+      ],
       histogramFilters: ['clusters', 'timeRange', 'components', 'levels', 'hosts', 'query']
     }
   };
@@ -620,6 +627,10 @@ export class LogsContainerService {
 
   private stopCaptureTime: number;
 
+  topUsersGraphData: HomogeneousObject<HomogeneousObject<number>> = {};
+
+  topResourcesGraphData: HomogeneousObject<HomogeneousObject<number>> = {};
+
   loadLogs = (logsType: LogsType = this.activeLogsType): void => {
     this.httpClient.get(logsType, this.getParams('listFilters')).subscribe((response: Response): void => {
       const jsonResponse = response.json(),
@@ -647,6 +658,34 @@ export class LogsContainerService {
         }
       });
     }
+    if (logsType === 'auditLogs') {
+      this.httpClient.get('topAuditLogsResources', this.getParams('topResourcesFilters', {
+        field: 'resource'
+      }), {
+        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'
+      }), {
+        number: this.topUsersCount
+      }).subscribe((response: Response): void => {
+        const jsonResponse = response.json();
+        if (jsonResponse) {
+          const data = jsonResponse.graphData;
+          if (data) {
+            this.topUsersGraphData = this.parseAuditLogsTopData(data);
+          }
+        }
+      });
+    }
   };
 
   loadLogContext(id: string, hostName: string, componentName: string, scrollType: ScrollType = ''): void {
@@ -680,7 +719,19 @@ export class LogsContainerService {
     });
   }
 
-  private getParams(filtersMapName: string, logsType: LogsType = this.activeLogsType): {[key: string]: string} {
+  private parseAuditLogsTopData(data: BarGraph[]): HomogeneousObject<HomogeneousObject<number>> {
+    return data.reduce((currentObject: HomogeneousObject<HomogeneousObject<number>>, currentItem: BarGraph): HomogeneousObject<HomogeneousObject<number>> => Object.assign(currentObject, {
+      [currentItem.name]: currentItem.dataCount.reduce((currentDataObject: HomogeneousObject<number>, currentDataItem: CommonEntry): HomogeneousObject<number> => {
+        return Object.assign(currentDataObject, {
+          [currentDataItem.name]: currentDataItem.value
+        });
+      }, {})
+    }), {});
+  }
+
+  private getParams(
+    filtersMapName: string, additionalParams: HomogeneousObject<string> = {}, logsType: LogsType = this.activeLogsType
+  ): HomogeneousObject<string> {
     let params = {};
     this.logsTypeMap[logsType][filtersMapName].forEach((key: string): void => {
       const inputValue = this.filtersForm.getRawValue()[key],
@@ -698,10 +749,10 @@ export class LogsContainerService {
         }
       });
     }, this);
-    return params;
+    return Object.assign({}, params, additionalParams);
   }
 
-  getHistogramData(data: BarGraph[]): {[key: string]: number} {
+  getHistogramData(data: BarGraph[]): HomogeneousObject<HomogeneousObject<number>> {
     let histogramData = {};
     data.forEach(type => {
       const name = type.name;
@@ -800,7 +851,9 @@ export class LogsContainerService {
     return (value: SearchBoxParameter[]): string => {
       let parameters;
       if (value && value.length) {
-        parameters = value.filter((item: SearchBoxParameter): boolean => item.isExclude === isExclude).map((parameter: SearchBoxParameter): {[key: string]: string} => {
+        parameters = value.filter((item: SearchBoxParameter): boolean => {
+          return item.isExclude === isExclude;
+        }).map((parameter: SearchBoxParameter): HomogeneousObject<string> => {
           return {
             [parameter.name]: parameter.value.replace(/\s/g, '+')
           };