You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by am...@apache.org on 2017/11/16 15:36:34 UTC

[11/50] [abbrv] ambari git commit: AMBARI-22388 Log Search UI: restyle logs list. (Istvan Tobias via ababiichuk)

AMBARI-22388 Log Search UI: restyle logs list. (Istvan Tobias via ababiichuk)


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

Branch: refs/heads/branch-feature-AMBARI-22008
Commit: 216453500fda2dc050af6afdc117ec027ba44e65
Parents: 8d20fe1
Author: Istvan Tobias <to...@gmail.com>
Authored: Thu Nov 9 13:24:16 2017 +0200
Committer: Attila Magyar <am...@hortonworks.com>
Committed: Thu Nov 16 16:35:26 2017 +0100

----------------------------------------------------------------------
 .../ambari-logsearch-web/src/app/app.module.ts  |   4 +
 .../log-level/log-level.component.html          |  18 +++
 .../log-level/log-level.component.spec.ts       |  73 +++++++++++
 .../components/log-level/log-level.component.ts |  52 ++++++++
 .../log-message/log-message.component.html      |  24 ++++
 .../log-message/log-message.component.less      |  69 ++++++++++
 .../log-message/log-message.component.spec.ts   |  64 +++++++++
 .../log-message/log-message.component.ts        | 129 ++++++++++++++++++
 .../logs-list/logs-list.component.html          |  92 +++++++------
 .../logs-list/logs-list.component.less          | 130 +++++++++----------
 .../ambari-logsearch-web/webpack.config.js      |  15 ++-
 11 files changed, 550 insertions(+), 120 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/21645350/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 488437e..56562df 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts
@@ -68,6 +68,8 @@ import {FilterButtonComponent} from '@app/components/filter-button/filter-button
 import {AccordionPanelComponent} from '@app/components/accordion-panel/accordion-panel.component';
 import {CollapsiblePanelComponent} from '@app/components/collapsible-panel/collapsible-panel.component';
 import {LogsListComponent} from '@app/components/logs-list/logs-list.component';
+import {LogMessageComponent} from '@app/components/log-message/log-message.component';
+import {LogLevelComponent} from '@app/components/log-level/log-level.component';
 import {DropdownButtonComponent} from '@app/components/dropdown-button/dropdown-button.component';
 import {PaginationComponent} from '@app/components/pagination/pagination.component';
 import {PaginationControlsComponent} from '@app/components/pagination-controls/pagination-controls.component';
@@ -121,6 +123,8 @@ export function getXHRBackend(injector: Injector, browser: BrowserXhr, xsrf: XSR
     AccordionPanelComponent,
     CollapsiblePanelComponent,
     LogsListComponent,
+    LogLevelComponent,
+    LogMessageComponent,
     DropdownButtonComponent,
     PaginationComponent,
     PaginationControlsComponent,

http://git-wip-us.apache.org/repos/asf/ambari/blob/21645350/ambari-logsearch/ambari-logsearch-web/src/app/components/log-level/log-level.component.html
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-level/log-level.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-level/log-level.component.html
new file mode 100644
index 0000000..d72c9d33
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-level/log-level.component.html
@@ -0,0 +1,18 @@
+<!--
+  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.
+-->
+<i class="fa {{cssClass}}"></i>
+{{logEntry.level}}

http://git-wip-us.apache.org/repos/asf/ambari/blob/21645350/ambari-logsearch/ambari-logsearch-web/src/app/components/log-level/log-level.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-level/log-level.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-level/log-level.component.spec.ts
new file mode 100644
index 0000000..c13d373
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-level/log-level.component.spec.ts
@@ -0,0 +1,73 @@
+/**
+ * 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 {DebugElement} from '@angular/core';
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
+
+import {LogLevelComponent} from './log-level.component';
+import {By} from '@angular/platform-browser';
+
+describe('LogLevelComponent', () => {
+  let component: LogLevelComponent;
+  let fixture: ComponentFixture<LogLevelComponent>;
+  let de: DebugElement;
+  let el: HTMLElement;
+  let logLevelMap = {
+    warn: 'fa-exclamation-triangle',
+    fatal: 'fa-exclamation-circle',
+    error: 'fa-exclamation-circle',
+    info: 'fa-info-circle',
+    debug: 'fa-bug',
+    trace: 'fa-random',
+    unknown: 'fa-question-circle'
+  };
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ LogLevelComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(LogLevelComponent);
+    component = fixture.componentInstance;
+    component.logEntry = {level: 'unknown'};
+    fixture.detectChanges();
+    de = fixture.debugElement.query(By.css('i.fa'));
+    el = de.nativeElement;
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  Object.keys(logLevelMap).forEach((level) => {
+    describe(level, () => {
+      beforeEach(() => {
+        component.logEntry = {level: level};
+        fixture.detectChanges();
+      });
+      it(`should return with the ${logLevelMap[level]} css class for ${level} log level`, () => {
+        expect(component.cssClass).toEqual(logLevelMap[level]);
+      });
+      it(`should set the ${logLevelMap[level]} css class on the icon element`, () => {
+        expect(el.classList).toContain(logLevelMap[level]);
+      });
+    });
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/21645350/ambari-logsearch/ambari-logsearch-web/src/app/components/log-level/log-level.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-level/log-level.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-level/log-level.component.ts
new file mode 100644
index 0000000..8542770
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-level/log-level.component.ts
@@ -0,0 +1,52 @@
+/**
+ * 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';
+
+/**
+ * This is a simple UI component to display the log message. The goal is to be able to show one line and be collapsile
+ * to show the full log message with new lines.
+ * @class LogMessageComponent
+ */
+@Component({
+  selector: 'log-level',
+  templateUrl: './log-level.component.html',
+  styleUrls: []
+})
+export class LogLevelComponent {
+
+  /**
+   * This is the log entry object
+   * @type {object}
+   */
+  @Input()
+  logEntry: any;
+
+  private classMap: object = {
+    warn: 'fa-exclamation-triangle',
+    fatal: 'fa-exclamation-circle',
+    error: 'fa-exclamation-circle',
+    info: 'fa-info-circle',
+    debug: 'fa-bug',
+    trace: 'fa-random',
+    unknown: 'fa-question-circle'
+  };
+
+  get cssClass() {
+    return this.classMap[((this.logEntry && this.logEntry.level) || 'unknown').toLowerCase()];
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/21645350/ambari-logsearch/ambari-logsearch-web/src/app/components/log-message/log-message.component.html
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-message/log-message.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-message/log-message.component.html
new file mode 100644
index 0000000..d4c2902
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-message/log-message.component.html
@@ -0,0 +1,24 @@
+<!--
+  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 [ngClass]="{
+  'log-message-container': true,
+  'log-message-container-collapsible': addCaret,
+  'log-message-container-open': isOpen
+  }">
+  <button *ngIf="addCaret" (click)="onCaretClick($event)"><i class="caret"></i></button>
+  <div #content class="log-message-content"><ng-content></ng-content></div>
+</div>

http://git-wip-us.apache.org/repos/asf/ambari/blob/21645350/ambari-logsearch/ambari-logsearch-web/src/app/components/log-message/log-message.component.less
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-message/log-message.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-message/log-message.component.less
new file mode 100644
index 0000000..602d7bd
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-message/log-message.component.less
@@ -0,0 +1,69 @@
+/**
+ * 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 {
+  .log-message-container {
+    display: block;
+    margin: 0;
+    padding: 0;
+
+    .caret {
+      margin-top: -3px;
+      transition: transform 250ms;
+      transform: rotate(-90deg);
+    }
+    &.log-message-container-open .caret {
+      transform: rotate(0deg);
+    }
+
+    .log-message-content {
+      max-height: calc(20em/14); // from Bootstrap
+      overflow: hidden;
+      padding-left: 1em;
+      position: relative;
+    }
+    &.log-message-container-open .log-message-content {
+      max-height: none;
+      white-space: pre-wrap;
+      &:before {
+        display: none;
+      }
+    }
+    &.log-message-container-collapsible {
+      .log-message-content {
+        padding-left: 0;
+        &:before {
+          content: "...";
+          float: right;
+          margin-left: 1em;
+        }
+      }
+
+    }
+
+    button, button:active {
+      background: none transparent;
+      border: none transparent;
+      color: @base-font-color;
+      cursor: pointer;
+      float: left;
+      height: 1em;
+      outline: none;
+      padding: 0 .15em;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/21645350/ambari-logsearch/ambari-logsearch-web/src/app/components/log-message/log-message.component.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-message/log-message.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-message/log-message.component.spec.ts
new file mode 100644
index 0000000..edc2515
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-message/log-message.component.spec.ts
@@ -0,0 +1,64 @@
+/**
+ * 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 {LogMessageComponent} from './log-message.component';
+
+describe('LogMessageComponent', () => {
+  let component: LogMessageComponent;
+  let fixture: ComponentFixture<LogMessageComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ LogMessageComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(LogMessageComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  it('event handler should call the toggleOpen method', () => {
+    let mockEvent: MouseEvent = document.createEvent('MouseEvent');
+    mockEvent.initEvent('click', true, true);
+    spyOn(component,'toggleOpen');
+    component.onCaretClick(mockEvent);
+    expect(component.toggleOpen).toHaveBeenCalled();
+  });
+
+  it('event handler should prevent the default behaviour of the action', () => {
+    let mockEvent: MouseEvent = document.createEvent('MouseEvent');
+    mockEvent.initEvent('click', true, true);
+    spyOn(mockEvent,'preventDefault');
+    component.onCaretClick(mockEvent);
+    expect(mockEvent.preventDefault).toHaveBeenCalled();
+  });
+
+  it('calling the toggleOpen method should negate the isOpen property', () => {
+    let currentState = component.isOpen;
+    component.toggleOpen();
+    expect(component.isOpen).toEqual(!currentState);
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/21645350/ambari-logsearch/ambari-logsearch-web/src/app/components/log-message/log-message.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-message/log-message.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-message/log-message.component.ts
new file mode 100644
index 0000000..b8be61b
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-message/log-message.component.ts
@@ -0,0 +1,129 @@
+/**
+ * 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, AfterViewInit, ElementRef, ViewChild, OnChanges, SimpleChanges, HostListener, ChangeDetectorRef} from '@angular/core';
+
+/**
+ * This is a simple UI component to display the log message. The goal is to be able to show one line and be collapsile
+ * to show the full log message with new lines.
+ * @class LogMessageComponent
+ */
+@Component({
+  selector: 'log-message',
+  templateUrl: './log-message.component.html',
+  styleUrls: ['./log-message.component.less']
+})
+export class LogMessageComponent implements AfterViewInit, OnChanges {
+
+  /**
+   * This is the element reference to the message log container element. So that we can calculate if the caret should be
+   * displayed or not.
+   * @type ElementRef
+   */
+  @ViewChild('content') content: ElementRef;
+
+  /**
+   * This is the flag property to indicate if the content container is open or not.
+   * @type {boolean}
+   */
+  @Input()
+  isOpen: boolean = false;
+
+  /**
+   * This is a helper property to handle the changes on the parent component. The goal of this input is to be able to
+   * react when the parent component (currently the log-list component) has changed (its size) in a way that the
+   * LogMessageComponent should check if the caret should be visible or not.
+   */
+  @Input()
+  listenChangesOn: any;
+
+  /**
+   * This is a private flag to check if it should display the caret or not, it depends on the size of the size of
+   * the content container element. Handled by the @checkAddCaret method
+   * @type {boolean}
+   */
+  private addCaret: boolean = false;
+
+  /**
+   * This is a primary check if the message content does contain new line (/n) characters. If so than we display the
+   * caret to give a possibility to the user to see the message as it is (pre-wrapped).
+   * @type {boolean}
+   */
+  private isMultiLineMessage: boolean = false;
+
+  constructor(private cdRef:ChangeDetectorRef) {}
+
+  /**
+   * This change handler's goal is to check if we should add the caret or not. Mainly it is because currently we have
+   * the LogListComponent where columns can be added or removed and we have to recheck the visibility of the caret every
+   * changes of the displayed columns.
+   * @param {SimpleChanges} changes
+   */
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes.listenChangesOn !== undefined) {
+      this.checkAddCaret();
+    }
+  }
+
+  /**
+   * The goal is to perform a initial caret display check when the component has been initialized.
+   */
+  ngAfterViewInit(): void {
+    let text = this.content.nativeElement.textContent;
+    let newLinePos = text.indexOf('\n');
+    this.isMultiLineMessage = ((text.length - 1) > newLinePos) && (newLinePos > 0);
+    this.checkAddCaret();
+  }
+
+  /**
+   * Since the size of the column is depends on the window size we have to listen the resize event and show/hide the
+   * caret corresponding the new size of the content container element.
+   * Using the arrow function will keep the instance scope.
+   */
+  @HostListener('window:resize', ['$event'])
+  onWindowResize = (): void => {
+    this.isMultiLineMessage || this.checkAddCaret();
+  };
+
+  /**
+   * The goal is to perform a height check on the content container element. It is based on the comparison of the
+   * scrollHeight and the clientHeight.
+   */
+  checkAddCaret = (): void =>  {
+    let el = this.content.nativeElement;
+    this.addCaret = this.isMultiLineMessage || (el.scrollHeight > el.clientHeight);
+    this.cdRef.detectChanges();
+  };
+
+  /**
+   * This is the click event handler of the caret button element. It will only toggle the isOpen property so that the
+   * component element css classes will follow its state.
+   * @param ev {MouseEvent}
+   */
+  onCaretClick(ev:MouseEvent) {
+    ev.preventDefault();
+    this.toggleOpen();
+  }
+
+  /**
+   * This is a simple property toggle method of the @isOpen property.
+   * The goal is to separate this logic from the event handling and give a way to call it from anywhere.
+   */
+  toggleOpen():void {
+    this.isOpen = !this.isOpen;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/21645350/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 1e0f49c..10f4af1 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
@@ -20,46 +20,54 @@
                    [defaultLabel]="filters.sorting.defaultLabel" [isRightAlign]="true"
                    class="col-md-12"></filter-dropdown>
 </form>
-<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 ? 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 ? log.level.toLowerCase() : '')">
-        {{log.level}}
-      </div>
-      <div *ngIf="isColumnDisplayed('type') || isColumnDisplayed('logtime')" class="col-md-3">
-        <div *ngIf="isColumnDisplayed('type')" class="log-type">{{log.type}}</div>
-        <time *ngIf="isColumnDisplayed('logtime')" class="log-time">
-          {{log.logtime | amTz: timeZone | amDateFormat: timeFormat}}
-        </time>
-      </div>
-      <div class="col-md-6 log-content-wrapper">
-        <div class="collapse log-actions" attr.id="details-{{i}}">
-          <!-- TODO remove after restyling the table -->
-        </div>
-        <div class="log-content-inner-wrapper">
-          <div class="log-content" *ngIf="isColumnDisplayed('log_message')"
-               (contextmenu)="openMessageContextMenu($event)">{{log.log_message}}</div>
-        </div>
-      </div>
-      <div *ngFor="let column of displayedColumns">
-        <div *ngIf="customStyledColumns.indexOf(column.name) === -1" [innerHTML]="log[column.name]"
-             class="col-md-1"></div>
-      </div>
-    </ng-template>
-  </accordion-panel>
-  <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 class="panel panel-default">
+ <div class="panel-body">
+   <table class="table table-hover">
+     <tbody>
+     <ng-container *ngFor="let log of logs; let i = index">
+         <tr *ngIf="!isServiceLogsFileView && (i === 0 || isDifferentDates(log.logtime, logs[i - 1].logtime))"
+                              class="log-date-row" >
+           <th attr.colspan="{{displayedColumns.length + 1}}">
+             {{log.logtime | amTz: timeZone | amDateFormat: dateFormat}}
+           </th>
+         </tr>
+         <tr class="log-item-row">
+           <td class="log-action">
+             <dropdown-button iconClass="fa fa-ellipsis-h action" [hideCaret]="true" [options]="logActions"
+                [additionalArgs]="[log]"></dropdown-button>
+           </td>
+           <td *ngIf="isColumnDisplayed('logtime')" class="log-time">
+             <time>
+               {{log.logtime | amTz: timeZone | amDateFormat: timeFormat}}
+             </time>
+           </td>
+           <td *ngIf="isColumnDisplayed('level')" [ngClass]="'log-level ' + log.level.toLowerCase()">
+             <log-level [logEntry]="log"></log-level>
+           </td>
+           <td *ngIf="isColumnDisplayed('type')" [ngClass]="'log-type'">
+             {{log.type}}
+           </td>
+           <td *ngIf="isColumnDisplayed('log_message')" [ngClass]="'log-message'" width="*"
+               (contextmenu)="openMessageContextMenu($event)">
+             <log-message [listenChangesOn]="displayedColumns">{{log.log_message}}</log-message>
+           </td>
+           <ng-container *ngFor="let column of displayedColumns">
+             <td *ngIf="customStyledColumns.indexOf(column.name) === -1"
+               [ngClass]="'log-' + column.name">{{log[column.name]}}</td>
+           </ng-container>
+         </tr>
+       </ng-container>
+   </tbody>
+     <tfoot>
+     <tr>
+         <td attr.colspan="{{displayedColumns.length + 1}}">
+           <pagination class="col-md-12" *ngIf="logs && logs.length" [totalCount]="totalCount"
+             [filtersForm]="filtersForm" [filterInstance]="filters.pageSize" [currentCount]="logs.length"></pagination>
+         </td>
+       </tr>
+   </tfoot>
+   </table>
+   <ul #contextmenu data-component="dropdown-list" class="dropdown-menu context-menu" [items]="contextMenuItems"
+                  (selectedItemChange)="updateQuery($event)"></ul>
+ </div>
 </div>
-<ul #contextmenu *ngIf="!isServiceLogsFileView" data-component="dropdown-list" class="dropdown-menu context-menu"
-    [items]="contextMenuItems" (selectedItemChange)="updateQuery($event)"></ul>
-<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/21645350/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.less
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.less
index 67d0615..c5c4c5a 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.less
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.less
@@ -17,93 +17,81 @@
 
 @import '../mixins';
 
-.logs-header {
-  // TODO get rid of magic numbers, base on actual design
-  margin: 10px 0;
-  padding: 5px 0;
-  background-color: @list-header-background-color; // TODO implement actual color
-  overflow: hidden;
-}
-
-/deep/ filter-dropdown {
-  justify-content: flex-end;
-}
-
-.hexagon {
-  // TODO remove, since it's not a part of updated design
-  left: -7.5px;
-
-  &.fatal {
-    .common-hexagon(15px, @fatal-color);
-  }
-
-  &.error {
-    .common-hexagon(15px, @error-color);
+:host {
+  /deep/ filter-dropdown {
+    justify-content: flex-end;
   }
 
-  &.warn {
-    .common-hexagon(15px, @warning-color);
+  .panel-body {
+    overflow: hidden;
+    width: 100%;
   }
 
-  &.info {
-    .common-hexagon(15px, @info-color);
+  table {
+    width: 100%;
   }
 
-  &.debug {
-    .common-hexagon(15px, @debug-color);
+  tr.log-date-row, tr.log-date-row:hover {
+    background: @list-header-background-color;
+    border: none transparent;
+    th {
+      border: none transparent;
+    }
   }
-
-  &.trace {
-    .common-hexagon(15px, @trace-color);
+  tr.log-item-row td {
+    background: none transparent;
   }
 
-  &.unknown {
-    .common-hexagon(15px, @unknown-color);
+  td {
+    &.log-action {
+      min-width: 3em;
+      /deep/ .btn, /deep/ .filter-label {
+        font-size: 1em;
+        height: auto;
+        line-height: 1em;
+        padding: 0;
+      }
+    }
+    &.log-time {
+      color: @grey-color;
+      min-width: 7em;
+      text-align: right;
+    }
+    &.log-level {
+      text-transform: uppercase;
+      min-width: 8em;
+      .log-colors;
+    }
+    &.log-type {
+      color: @link-color;
+    }
+    &.log-message, &.log-path {
+      width: 100%;
+    }
   }
-}
-
-.log-status {
-  text-transform: uppercase;
-  .log-colors;
-}
-
-.log-type {
-  color: @link-color;
-}
-
-.log-time {
-  color: @grey-color;
-}
-
-.log-content-wrapper {
-  position: relative;
 
-  // TODO get rid of magic numbers, base on actual design
-  .log-content-inner-wrapper {
-    overflow: hidden;
-    max-height: @default-line-height * 2em;
-    padding-right: 65px;
-
-    .log-content {
-      white-space: pre-wrap;
+  tr:hover td.log-action {
+    /deep/ .btn {
+      display: inline-block;
     }
   }
 
-  .log-actions {
-    &.collapsing + .log-content-inner-wrapper, &.collapse.in + .log-content-inner-wrapper {
-      min-height: 6em;
-      max-height: none;
-      overflow-x: auto;
+  .table.table-hover>tbody>tr{
+    box-sizing: border-box;
+    border-width: 1px;
+    >td {
+      border-top: 0 none;
     }
-
-    .action-icon {
-      .clickable-item;
-      display: block;
-      padding: 5px;
+    &:first-of-type {
+      border-top-color: transparent;
+    }
+    &:last-of-type {
+      border-bottom-color: transparent;
     }
   }
-}
 
-.context-menu {
-  position: fixed;
+  .context-menu {
+    position: fixed;
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/21645350/ambari-logsearch/ambari-logsearch-web/webpack.config.js
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/webpack.config.js b/ambari-logsearch/ambari-logsearch-web/webpack.config.js
index 7a60df2..75d6aee 100644
--- a/ambari-logsearch/ambari-logsearch-web/webpack.config.js
+++ b/ambari-logsearch/ambari-logsearch-web/webpack.config.js
@@ -81,18 +81,17 @@ module.exports = {
   "resolve": {
     "extensions": [
       ".ts",
-      ".js"
+      ".js",
+      ".less"
     ],
     "modules": [
-      "./node_modules",
-      "./node_modules"
+      "node_modules"
     ],
     "symlinks": true
   },
   "resolveLoader": {
     "modules": [
-      "./node_modules",
-      "./node_modules"
+      "node_modules"
     ]
   },
   "entry": {
@@ -229,7 +228,9 @@ module.exports = {
             "loader": "less-loader",
             "options": {
               "sourceMap": false,
-              "paths": []
+              "paths": [
+                "./node_modules"
+              ]
             }
           }
         ]
@@ -359,7 +360,7 @@ module.exports = {
             "loader": "less-loader",
             "options": {
               "sourceMap": false,
-              "paths": []
+              "paths": ["./node_modules"]
             }
           }
         ]