http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts index 4e9bdc9..7bd87ad 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-context/log-context.component.spec.ts @@ -34,7 +34,6 @@ import {TranslationModules} from '@app/test-config.spec'; import {ModalComponent} from '@app/components/modal/modal.component'; import {LogsContainerService} from '@app/services/logs-container.service'; import {HttpClientService} from '@app/services/http-client.service'; -import {FilteringService} from '@app/services/filtering.service'; import {LogContextComponent} from './log-context.component'; @@ -90,8 +89,7 @@ describe('LogContextComponent', () => { { provide: HttpClientService, useValue: httpClient - }, - FilteringService + } ], schemas: [CUSTOM_ELEMENTS_SCHEMA] })
http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/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/e83bf1bd/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/e83bf1bd/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/e83bf1bd/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/e83bf1bd/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/e83bf1bd/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/e83bf1bd/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/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/login-form/login-form.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/login-form/login-form.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/login-form/login-form.component.spec.ts index fb5c2a0..ac9f3a8 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/login-form/login-form.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/login-form/login-form.component.spec.ts @@ -22,6 +22,7 @@ import {TranslationModules} from '@app/test-config.spec'; import {StoreModule} from '@ngrx/store'; import {AppStateService, appState} from '@app/services/storage/app-state.service'; import {HttpClientService} from '@app/services/http-client.service'; +import {AuthService} from '@app/services/auth.service'; import {LoginFormComponent} from './login-form.component'; @@ -58,7 +59,8 @@ describe('LoginFormComponent', () => { { provide: HttpClientService, useValue: httpClient - } + }, + AuthService ] }) .compileComponents(); @@ -101,9 +103,6 @@ describe('LoginFormComponent', () => { expect(component.isLoginAlertDisplayed).toEqual(test.isLoginAlertDisplayed); }); - it('isLoginInProgress', () => { - expect(component.isLoginInProgress).toEqual(false); - }); }); }); http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/login-form/login-form.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/login-form/login-form.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/login-form/login-form.component.ts index 2bc45404..39a4975 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/login-form/login-form.component.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/login-form/login-form.component.ts @@ -17,9 +17,10 @@ */ import {Component} from '@angular/core'; +import {Response} from '@angular/http'; import 'rxjs/add/operator/finally'; -import {HttpClientService} from '@app/services/http-client.service'; import {AppStateService} from '@app/services/storage/app-state.service'; +import {AuthService} from '@app/services/auth.service'; @Component({ selector: 'login-form', @@ -28,7 +29,7 @@ import {AppStateService} from '@app/services/storage/app-state.service'; }) export class LoginFormComponent { - constructor(private httpClient: HttpClientService, private appState: AppStateService) { + constructor(private authService: AuthService, private appState: AppStateService) { appState.getParameter('isLoginInProgress').subscribe(value => this.isLoginInProgress = value); } @@ -40,20 +41,25 @@ export class LoginFormComponent { isLoginInProgress: boolean; - private setIsAuthorized(value: boolean): void { - this.appState.setParameters({ - isAuthorized: value, - isLoginInProgress: false - }); - this.isLoginAlertDisplayed = !value; - } + /** + * Handling the response from the login action. Actually the goal only to show or hide the login error alert. + * When it gets error response it shows. + * @param {Response} resp + */ + private onLoginError = (resp: Response): void => { + this.isLoginAlertDisplayed = true; + }; + /** + * Handling the response from the login action. Actually the goal only to show or hide the login error alert. + * When it gets success response it hides. + * @param {Response} resp + */ + private onLoginSuccess = (resp: Response): void => { + this.isLoginAlertDisplayed = false; + }; login() { - this.appState.setParameter('isLoginInProgress', true); - this.httpClient.postFormData('login', { - username: this.username, - password: this.password - }).subscribe(() => this.setIsAuthorized(true), () => this.setIsAuthorized(false)); + this.authService.login(this.username,this.password).subscribe(this.onLoginSuccess, this.onLoginError); } } http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/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 70150a5..f34dd15 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.html +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.html @@ -15,30 +15,41 @@ limitations under the License. --> -<div class="tabs-container row"> - <tabs class="col-md-12" [items]="tabs | async" (tabSwitched)="onSwitchTab($event)" - (tabClosed)="onCloseTab($event[0], $event[1])"></tabs> +<div class="tabs-container container-fluid"> + <div class="row"> + <div class="col-md-12"> + <tabs class="pull-left" [items]="tabs | async" (tabSwitched)="onSwitchTab($event)" + (tabClosed)="onCloseTab($event[0], $event[1])"></tabs> + <action-menu class="pull-right"></action-menu> + </div> + </div> </div> -<filters-panel class="row" [filtersForm]="filtersForm"></filters-panel> -<div *ngIf="autoRefreshRemainingSeconds" class="col-md-12"> - <div class="auto-refresh-message pull-right"> - {{'filter.capture.triggeringRefresh' | translate: autoRefreshMessageParams}} +<div class="container-fluid"> + <filters-panel class="row" [filtersForm]="filtersForm"></filters-panel> + <div class="row"> + <div *ngIf="autoRefreshRemainingSeconds" class="col-md-12"> + <div class="auto-refresh-message pull-right"> + {{'filter.capture.triggeringRefresh' | translate: autoRefreshMessageParams}} + </div> + </div> + + <!-- TODO use plugin for singular/plural --> + <div class="logs-header col-md-12">{{ + (!totalEventsFoundMessageParams.totalCount ? 'logs.noEventFound' : + (totalEventsFoundMessageParams.totalCount === 1 ? 'logs.oneEventFound' : 'logs.totalEventFound')) + | translate: totalEventsFoundMessageParams + }}</div> </div> + <collapsible-panel openTitle="logs.hideGraph" collapsedTitle="logs.showGraph"> + <time-histogram [data]="histogramData" [customOptions]="histogramOptions" svgId="service-logs-histogram" + (selectArea)="setCustomTimeRange($event[0], $event[1])"></time-histogram> + </collapsible-panel> + <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> + </ng-container> + <log-context *ngIf="isServiceLogContextView" [id]="activeLog.id" [hostName]="activeLog.host_name" + [componentName]="activeLog.component_name"></log-context> </div> -<!-- TODO use plugin for singular/plural --> -<div class="logs-header">{{ - (!totalEventsFoundMessageParams.totalCount ? 'logs.noEventFound' : - (totalEventsFoundMessageParams.totalCount === 1 ? 'logs.oneEventFound' : 'logs.totalEventFound')) - | translate: totalEventsFoundMessageParams -}}</div> -<collapsible-panel openTitle="logs.hideGraph" collapsedTitle="logs.showGraph"> - <time-histogram [data]="histogramData" [customOptions]="histogramOptions" svgId="service-logs-histogram" - (selectArea)="setCustomTimeRange($event[0], $event[1])"></time-histogram> -</collapsible-panel> -<dropdown-button *ngIf="!isServiceLogsFileView" class="pull-right" label="logs.columns" - [options]="availableColumns | async" [isRightAlign]="true" [isMultipleChoice]="true" - action="updateSelectedColumns" [additionalArgs]="logsTypeMapObject.fieldsModel"></dropdown-button> -<logs-list [logs]="logs | async" [totalCount]="totalCount" [displayedColumns]="displayedColumns" - [isServiceLogsFileView]="isServiceLogsFileView" [filtersForm]="filtersForm"></logs-list> -<log-context *ngIf="isServiceLogContextView" [hostName]="activeLog.host_name" [componentName]="activeLog.component_name" - [id]="activeLog.id"></log-context> http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.less ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.less index 23d5f92..9902b79 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.less +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.less @@ -25,6 +25,9 @@ .tabs-container, .auto-refresh-message { background-color: @filters-panel-background-color; } + .tabs-container { + border-bottom: 1px solid @table-border-color; + } filters-panel { margin-bottom: @block-margin-top; http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.spec.ts index 0a9418f..2bb8731 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.spec.ts @@ -33,7 +33,6 @@ import {HostsService, hosts} from '@app/services/storage/hosts.service'; import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service'; import {TabsService, tabs} from '@app/services/storage/tabs.service'; import {HttpClientService} from '@app/services/http-client.service'; -import {FilteringService} from '@app/services/filtering.service'; import {UtilsService} from '@app/services/utils.service'; import {LogsContainerService} from '@app/services/logs-container.service'; import {TabsComponent} from '@app/components/tabs/tabs.component'; @@ -92,7 +91,6 @@ describe('LogsContainerComponent', () => { HostsService, ServiceLogsTruncatedService, TabsService, - FilteringService, UtilsService, LogsContainerService ], http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/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 21949f1..b06cfa4 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 @@ -19,17 +19,12 @@ import {Component} from '@angular/core'; import {FormGroup} from '@angular/forms'; import {Observable} from 'rxjs/Observable'; -import {Subject} from 'rxjs/Subject'; -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/takeUntil'; -import {FilteringService} from '@app/services/filtering.service'; import {LogsContainerService} from '@app/services/logs-container.service'; import {ServiceLogsHistogramDataService} from '@app/services/storage/service-logs-histogram-data.service'; import {AppStateService} from '@app/services/storage/app-state.service'; import {TabsService} from '@app/services/storage/tabs.service'; import {AuditLog} from '@app/classes/models/audit-log'; import {ServiceLog} from '@app/classes/models/service-log'; -import {LogField} from '@app/classes/models/log-field'; import {Tab} from '@app/classes/models/tab'; import {BarGraph} from '@app/classes/models/bar-graph'; import {ActiveServiceLogEntry} from '@app/classes/active-service-log-entry'; @@ -43,43 +38,12 @@ import {ListItem} from '@app/classes/list-item'; }) export class LogsContainerComponent { - constructor(private serviceLogsHistogramStorage: ServiceLogsHistogramDataService, private appState: AppStateService, private tabsStorage: TabsService, private filtering: FilteringService, private logsContainer: LogsContainerService) { + constructor( + private serviceLogsHistogramStorage: ServiceLogsHistogramDataService, private appState: AppStateService, + private tabsStorage: TabsService, private logsContainer: LogsContainerService + ) { this.logsContainer.loadColumnsNames(); - this.logsTypeChange.first().subscribe(() => this.logsContainer.loadLogs()); - appState.getParameter('activeLogsType').subscribe((value: string): void => { - this.logsType = value; - this.logsTypeChange.next(); - const fieldsModel = this.logsTypeMapObject.fieldsModel, - logsModel = this.logsTypeMapObject.logsModel; - this.availableColumns = fieldsModel.getAll().takeUntil(this.logsTypeChange).map((fields: LogField[]): ListItem[] => { - return fields.filter((field: LogField): boolean => field.isAvailable).map((field: LogField): ListItem => { - return { - value: field.name, - label: field.displayName || field.name, - isChecked: field.isDisplayed - }; - }); - }); - fieldsModel.getAll().takeUntil(this.logsTypeChange).subscribe(columns => { - const availableFields = columns.filter((field: LogField): boolean => field.isAvailable), - availableNames = availableFields.map((field: LogField): string => field.name); - if (availableNames.length) { - this.logs = logsModel.getAll().map((logs: (AuditLog | ServiceLog)[]): (AuditLog | ServiceLog)[] => { - return logs.map((log: AuditLog | ServiceLog): AuditLog | ServiceLog => { - return availableNames.reduce((obj, key) => Object.assign(obj, { - [key]: log[key] - }), {}); - }); - }); - } - this.displayedColumns = columns.filter((column: LogField): boolean => column.isAvailable && column.isDisplayed); - }); - }); - appState.getParameter('activeFiltersForm').subscribe((form: FormGroup): void => { - this.filtersFormChange.next(); - form.valueChanges.takeUntil(this.filtersFormChange).subscribe(() => this.logsContainer.loadLogs()); - this.filtersForm = form; - }); + appState.getParameter('activeLogsType').subscribe((value: string) => this.logsType = value); serviceLogsHistogramStorage.getAll().subscribe((data: BarGraph[]): void => { this.histogramData = this.logsContainer.getHistogramData(data); }); @@ -88,28 +52,16 @@ export class LogsContainerComponent { tabs: Observable<Tab[]> = this.tabsStorage.getAll(); - filtersForm: FormGroup; + get filtersForm(): FormGroup { + return this.logsContainer.filtersForm; + }; private logsType: string; - private filtersFormChange: Subject<any> = new Subject(); - - private logsTypeChange: Subject<any> = new Subject(); - - get logsTypeMapObject(): any { - return this.logsContainer.logsTypeMap[this.logsType]; - } - get totalCount(): number { return this.logsContainer.totalCount; } - logs: Observable<AuditLog[] | ServiceLog[]>; - - availableColumns: Observable<LogField[]>; - - displayedColumns: any[] = []; - histogramData: {[key: string]: number}; readonly histogramOptions: HistogramOptions = { @@ -117,10 +69,10 @@ export class LogsContainerComponent { }; get autoRefreshRemainingSeconds(): number { - return this.filtering.autoRefreshRemainingSeconds; + return this.logsContainer.autoRefreshRemainingSeconds; } - get autoRefreshMessageParams(): any { + get autoRefreshMessageParams(): object { return { remainingSeconds: this.autoRefreshRemainingSeconds }; @@ -133,7 +85,7 @@ export class LogsContainerComponent { get totalEventsFoundMessageParams(): object { return { totalCount: this.totalCount - } + }; } isServiceLogContextView: boolean = false; @@ -146,8 +98,24 @@ export class LogsContainerComponent { return this.logsContainer.activeLog; } + get auditLogs(): Observable<AuditLog[]> { + return this.logsContainer.auditLogs; + } + + get auditLogsColumns(): Observable<ListItem[]> { + return this.logsContainer.auditLogsColumns; + } + + get serviceLogs(): Observable<ServiceLog[]> { + return this.logsContainer.serviceLogs; + } + + get serviceLogsColumns(): Observable<ListItem[]> { + return this.logsContainer.serviceLogsColumns; + } + setCustomTimeRange(startTime: number, endTime: number): void { - this.filtering.setCustomTimeRange(startTime, endTime); + this.logsContainer.setCustomTimeRange(startTime, endTime); } onSwitchTab(activeTab: Tab): void { http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/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 deleted file mode 100644 index 1e0f49c..0000000 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.html +++ /dev/null @@ -1,65 +0,0 @@ -<!-- - 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. ---> - -<form *ngIf="logs && logs.length" [formGroup]="filtersForm" class="row pull-right"> - <filter-dropdown [label]="filters.sorting.label" formControlName="sorting" [options]="filters.sorting.options" - [defaultLabel]="filters.sorting.defaultLabel" [isRightAlign]="true" - 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> -<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/e83bf1bd/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 deleted file mode 100644 index 67d0615..0000000 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.less +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@import '../mixins'; - -.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); - } - - &.warn { - .common-hexagon(15px, @warning-color); - } - - &.info { - .common-hexagon(15px, @info-color); - } - - &.debug { - .common-hexagon(15px, @debug-color); - } - - &.trace { - .common-hexagon(15px, @trace-color); - } - - &.unknown { - .common-hexagon(15px, @unknown-color); - } -} - -.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; - } - } - - .log-actions { - &.collapsing + .log-content-inner-wrapper, &.collapse.in + .log-content-inner-wrapper { - min-height: 6em; - max-height: none; - overflow-x: auto; - } - - .action-icon { - .clickable-item; - display: block; - padding: 5px; - } - } -} - -.context-menu { - position: fixed; -} http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.spec.ts deleted file mode 100644 index 8ee4ca3..0000000 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.spec.ts +++ /dev/null @@ -1,95 +0,0 @@ -/** - * 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 {NO_ERRORS_SCHEMA} from '@angular/core'; -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; -import {TranslationModules} from '@app/test-config.spec'; -import {StoreModule} from '@ngrx/store'; -import {MomentModule} from 'angular2-moment'; -import {MomentTimezoneModule} from 'angular-moment-timezone'; -import {AuditLogsService, auditLogs} from '@app/services/storage/audit-logs.service'; -import {ServiceLogsService, serviceLogs} from '@app/services/storage/service-logs.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'; -import {ComponentsService, components} from '@app/services/storage/components.service'; -import {HostsService, hosts} from '@app/services/storage/hosts.service'; -import {HttpClientService} from '@app/services/http-client.service'; -import {FilteringService} from '@app/services/filtering.service'; -import {UtilsService} from '@app/services/utils.service'; - -import {LogsListComponent} from './logs-list.component'; - -describe('LogsListComponent', () => { - let component: LogsListComponent; - let fixture: ComponentFixture<LogsListComponent>; - const httpClient = { - get: () => { - return { - subscribe: () => { - } - }; - } - }; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [LogsListComponent], - imports: [ - StoreModule.provideStore({ - auditLogs, - serviceLogs, - appSettings, - appState, - clusters, - components, - hosts - }), - MomentModule, - MomentTimezoneModule, - ...TranslationModules - ], - providers: [ - { - provide: HttpClientService, - useValue: httpClient - }, - AuditLogsService, - ServiceLogsService, - AppSettingsService, - AppStateService, - ClustersService, - ComponentsService, - HostsService, - FilteringService, - UtilsService - ], - schemas: [NO_ERRORS_SCHEMA] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(LogsListComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create component', () => { - expect(component).toBeTruthy(); - }); -}); http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.ts deleted file mode 100644 index 017bc82..0000000 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * 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, AfterViewInit, Input, ViewChild, ElementRef} from '@angular/core'; -import {FormGroup} from '@angular/forms'; -import 'rxjs/add/operator/map'; -import {FilteringService} from '@app/services/filtering.service'; -import {UtilsService} from '@app/services/utils.service'; -import {AuditLog} from '@app/classes/models/audit-log'; -import {ServiceLog} from '@app/classes/models/service-log'; -import {LogField} from '@app/classes/models/log-field'; - -@Component({ - selector: 'logs-list', - templateUrl: './logs-list.component.html', - styleUrls: ['./logs-list.component.less'] -}) -export class LogsListComponent implements AfterViewInit { - - constructor(private filtering: FilteringService, private utils: UtilsService) { - } - - ngAfterViewInit() { - if (this.contextMenu) { - this.contextMenuElement = this.contextMenu.nativeElement; - } - } - - @Input() - logs: (AuditLog| ServiceLog)[] = []; - - @Input() - totalCount: number = 0; - - @Input() - displayedColumns: LogField[] = []; - - @Input() - isServiceLogsFileView: boolean = false; - - @Input() - filtersForm: FormGroup; - - @ViewChild('contextmenu', { - read: ElementRef - }) - contextMenu: ElementRef; - - private contextMenuElement: HTMLElement; - - private selectedText: string = ''; - - private readonly messageFilterParameterName = 'log_message'; - - readonly customStyledColumns = ['level', 'type', 'logtime', 'log_message']; - - readonly contextMenuItems = [ - { - label: 'logs.addToQuery', - iconClass: 'fa fa-search-plus', - value: false // 'isExclude' is false - }, - { - label: 'logs.excludeFromQuery', - iconClass: 'fa fa-search-minus', - value: true // 'isExclude' is true - } - ]; - - readonly logActions = [ - { - label: 'logs.copy', - iconClass: 'fa fa-files-o', - action: 'copyLog' - }, - { - label: 'logs.open', - iconClass: 'fa fa-external-link', - action: 'openLog' - }, - { - label: 'logs.context', - iconClass: 'fa fa-crosshairs', - action: 'openContext' - } - ]; - - readonly dateFormat: string = 'dddd, MMMM Do'; - - readonly timeFormat: string = 'h:mm:ss A'; - - get timeZone(): string { - return this.filtering.timeZone; - } - - get filters(): any { - return this.filtering.filters; - } - - isDifferentDates(dateA, dateB): boolean { - return this.utils.isDifferentDates(dateA, dateB, this.timeZone); - } - - isColumnDisplayed(key: string): boolean { - return this.displayedColumns.some((column: LogField): boolean => column.name === key); - } - - openMessageContextMenu(event: MouseEvent): void { - const selectedText = getSelection().toString(); - if (selectedText) { - let contextMenuStyle = this.contextMenuElement.style; - Object.assign(contextMenuStyle, { - left: `${event.clientX}px`, - top: `${event.clientY}px`, - display: 'block' - }); - this.selectedText = selectedText; - document.body.addEventListener('click', this.dismissContextMenu); - event.preventDefault(); - } - } - - updateQuery(event: any) { - this.filtering.queryParameterAdd.next({ - name: this.messageFilterParameterName, - value: this.selectedText, - isExclude: event.value - }); - } - - private dismissContextMenu = (): void => { - this.selectedText = ''; - this.contextMenuElement.style.display = 'none'; - document.body.removeEventListener('click', this.dismissContextMenu); - } - -} http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.html ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.html index 2061582..95dd238 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.html +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/main-container/main-container.component.html @@ -20,4 +20,4 @@ <span class="fa fa-spinner fa-spin"></span> </div> <login-form *ngIf="!isInitialLoading && !isAuthorized"></login-form> -<logs-container *ngIf="isAuthorized" class="col-md-12"></logs-container> +<logs-container *ngIf="isAuthorized"></logs-container> http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.html ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.html index ca70927..5e2b15f 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.html +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.html @@ -15,14 +15,15 @@ limitations under the License. --> -<div #dropdown [ngClass]="{'dropdown': hasSubItems, 'text-center': true}"> - <a [ngClass]="iconClass + ' icon'" (mousedown)="onMouseDown($event)" (mouseup)="onMouseUp($event)" - (click)="$event.stopPropagation()"></a> - <a #dropdownToggle class="dropdown-toggle caret" data-toggle="dropdown" *ngIf="hasCaret"></a> - <br> - <a *ngIf="label" (mousedown)="onMouseDown($event)" [ngClass]="labelClass" (mouseup)="onMouseUp($event)" - (click)="$event.stopPropagation()">{{label}}</a> - <ul data-component="dropdown-list" *ngIf="hasSubItems" [items]="subItems" (selectedItemChange)="updateValue($event)" +<div #dropdown [ngClass]="{'dropdown': hasSubItems, 'text-center': true, 'open': dropdownIsOpen}"> + <a class="dropdown-toggle" [ngClass]="(labelClass || '') + (hasCaret ? ' has-caret' : '')" + (click)="onMouseClick($event)" + (mousedown)="onMouseDown($event)"> + <i *ngIf="iconClass" [ngClass]="['icon', iconClass]"></i> + <i *ngIf="hasCaret" [ngClass]="['fa ', caretClass ]"></i> + <span *ngIf="label" class="menu-button-label">{{label}}</span> + </a> + <ul data-component="dropdown-list" *ngIf="hasSubItems" [items]="subItems" (selectedItemChange)="onDropdownItemChange($event)" [isMultipleChoice]="isMultipleChoice" [additionalLabelComponentSetter]="additionalLabelComponentSetter" [ngClass]="{'dropdown-menu': true, 'dropdown-menu-right': isRightAlign}"></ul> </div> http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.less ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.less index 615db24..0207561 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.less +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.less @@ -21,14 +21,26 @@ cursor: pointer; display: inline-block; position: relative; - a:hover, a:focus { + a { + text-align: center; text-decoration: none; + i { + color: @link-color; + display: inline-block; + position: relative; + &.fa-caret-down { + padding: 0 .25em; + } + } + .menu-button-label { + display: block; + } } - - .icon { - padding: @icon-padding; + a:hover, a:focus { + i { + color: @link-hover-color; + } } - .unstyled-link { color: inherit; } http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/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 261e213..3836e7a 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 @@ -33,9 +33,9 @@ import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/se import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service'; import {TabsService, tabs} from '@app/services/storage/tabs.service'; import {ComponentActionsService} from '@app/services/component-actions.service'; -import {FilteringService} from '@app/services/filtering.service'; import {HttpClientService} from '@app/services/http-client.service'; import {LogsContainerService} from '@app/services/logs-container.service'; +import {AuthService} from '@app/services/auth.service'; import {MenuButtonComponent} from './menu-button.component'; @@ -85,12 +85,12 @@ describe('MenuButtonComponent', () => { ServiceLogsTruncatedService, TabsService, ComponentActionsService, - FilteringService, { provide: HttpClientService, useValue: httpClient }, - LogsContainerService + LogsContainerService, + AuthService ], schemas: [NO_ERRORS_SCHEMA] }) http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/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 0aa7c7e..ca89935 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 @@ -19,7 +19,6 @@ import {Component, Input, ViewChild, ElementRef} from '@angular/core'; import {ListItem} from '@app/classes/list-item'; import {ComponentActionsService} from '@app/services/component-actions.service'; -import * as $ from 'jquery'; @Component({ selector: 'menu-button', @@ -64,6 +63,37 @@ export class MenuButtonComponent { @Input() badge: string; + @Input() + caretClass: string = 'fa-caret-down'; + + /** + * The minimum time to handle a mousedown as a longclick. Default is 500 ms (0.5sec) + * @default 500 + * @type {number} + */ + @Input() + minLongClickDelay: number = 500; + + /** + * The maximum milliseconds to wait for longclick ends. The default is 0 which means no upper limit. + * @default 0 + * @type {number} + */ + @Input() + maxLongClickDelay: number = 0; + + /** + * This is a private property to indicate the mousedown timestamp, so that we can check it when teh click event + * has been triggered. + */ + private mouseDownTimestamp: number; + + /** + * Indicates if the dropdown list is open or not. So that we use internal state to display or hide the dropdown. + * @type {boolean} + */ + private dropdownIsOpen: boolean = false; + get hasSubItems(): boolean { return Boolean(this.subItems && this.subItems.length); } @@ -72,29 +102,111 @@ export class MenuButtonComponent { return this.hasSubItems && !this.hideCaret; } - private clickStartTime: number; - - private readonly longClickInterval = 1000; - - onMouseDown(event: MouseEvent): void { - if (this.action && event.button === 0) { - this.clickStartTime = (new Date()).getTime(); + /** + * Handling the click event on the component element. + * Two goal: + * - check if we have a 'longclick' event and open the dropdown (if any) when longclick event happened + * - trigger the action or the dropdown open depending on the target element (caret will open the dropdown otherwise + * trigger the action. + * @param {MouseEvent} event + */ + onMouseClick(event: MouseEvent): void { + let el = <HTMLElement>event.target; + let now = Date.now(); + let mdt = this.mouseDownTimestamp; // mousedown time + let isLongClick = mdt && mdt + this.minLongClickDelay <= now && ( + !this.maxLongClickDelay || mdt + this.maxLongClickDelay >= now + ); + let openDropdown = this.hasSubItems && ( + el.classList.contains(this.caretClass) || isLongClick || !this.actions[this.action] + ); + if (openDropdown && this.dropdown) { + if (this.toggleDropdown()) { + this.listenToClickOut(); + } + } else if (this.action) { + this.actions[this.action](); } + this.mouseDownTimestamp = 0; + event.preventDefault(); } - onMouseUp(event: MouseEvent): void { - if (event.button === 0) { - const clickEndTime = (new Date()).getTime(); - if (this.hasSubItems && (!this.action || clickEndTime - this.clickStartTime >= this.longClickInterval)) { - $(this.dropdown.nativeElement).toggleClass('open'); - } else if (this.action) { - this.actions[this.action](); + /** + * Listening the click event on the document so that we can hide our dropdown list if the event source is not the + * component. + */ + private listenToClickOut = (): void => { + this.dropdownIsOpen && document.addEventListener('click', this.onDocumentMouseClick); + }; + + /** + * Handling the click event on the document to hide the dropdown list if it needs. + * @param {MouseEvent} event + */ + private onDocumentMouseClick = (event: MouseEvent): void => { + let el = <HTMLElement>event.target; + if (!this.dropdown.nativeElement.contains(el)) { + this.closeDropdown(); + this.removeDocumentClickListener() + } + }; + + /** + * Handling the mousedown event, so that we can check the long clicks and open the dropdown if any. + * @param {MouseEvent} event + */ + onMouseDown = (event: MouseEvent): void => { + if (this.hasSubItems) { + let el = <HTMLElement>event.target; + if (!el.classList.contains(this.caretClass)) { + this.mouseDownTimestamp = Date.now(); } - event.stopPropagation(); } + }; + + /** + * The goal is to have one and only one place where we open the dropdown. So that later if we need to change the way + * how we do, it will be easier. + */ + private openDropdown():void { + this.dropdownIsOpen = true; + } + + /** + * The goal is to have one and only one place where we close the dropdown. So that later if we need to change the way + * how we do, it will be easier. + */ + private closeDropdown():void { + this.dropdownIsOpen = false; + } + + /** + * Just a simple helper method to make the dropdown toggle more easy. + * @returns {boolean} It will return the open state of the dropdown; + */ + private toggleDropdown(): boolean { + this[this.dropdownIsOpen ? 'closeDropdown' : 'openDropdown'](); + return this.dropdownIsOpen; + } + + /** + * The goal is to simply remove the click event listeners from the document. + */ + private removeDocumentClickListener(): void { + document.removeEventListener('click', this.onDocumentMouseClick); + } + + /** + * The main goal if this function is tho handle the item change event on the child dropdown list. + * Should update the value and close the dropdown if it is not multiple choice type. + * @param {ListItem} options The selected item(s) from the dropdown list. + */ + onDropdownItemChange(options: ListItem) { + this.updateSelection(options); + !this.isMultipleChoice && this.closeDropdown(); } - updateValue(options: ListItem) { + updateSelection(options: ListItem) { // TODO implement value change behaviour } http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/mixins.less ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/mixins.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/mixins.less index 2e46213..5fa265b 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/mixins.less +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/mixins.less @@ -28,6 +28,11 @@ justify-content: space-between; } +.stretch-flex { + align-items: stretch; + display: flex; +} + .common-hexagon(@side, @color) { display: block; position: absolute; http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.html ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.html index c227a2b..370d1c7 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.html +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.html @@ -14,10 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. --> - -<button class="btn btn-link" [disabled]="currentPage === 0" (click)="updateValue(true)"> - <span class="pagination-control fa fa-chevron-left"></span> +<button class="btn btn-link" [disabled]="!hasPreviousPage()" (click)="setFirstPage()"> + <span class="pagination-control fa fa-angle-double-left"></span> +</button> +<button class="btn btn-link" [disabled]="!hasPreviousPage()" (click)="setPreviousPage()"> + <span class="pagination-control fa fa-angle-left"></span> +</button> +<button class="btn btn-link" [disabled]="!hasNextPage()" (click)="setNextPage()"> + <span class="pagination-control fa fa-angle-right"></span> </button> -<button class="btn btn-link" [disabled]="currentPage === pagesCount - 1" (click)="updateValue()"> - <span class="pagination-control fa fa-chevron-right"></span> +<button class="btn btn-link" [disabled]="!hasNextPage()" (click)="setLastPage()"> + <span class="pagination-control fa fa-angle-double-right"></span> </button> http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.spec.ts index 489f79c..999609c 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.spec.ts @@ -34,10 +34,111 @@ describe('PaginationControlsComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(PaginationControlsComponent); component = fixture.componentInstance; + component.registerOnChange(() => {}); + component.pagesCount = 3; + component.totalCount = 30; fixture.detectChanges(); }); it('should create component', () => { expect(component).toBeTruthy(); }); + + it('should the hasNextPage function return true when the currentPage is less than the pagesCount', () => { + component.pagesCount = 3; + component.totalCount = 30; + fixture.detectChanges(); + expect(component.hasNextPage()).toBe(true); + }); + it('should the hasNextPage function return false when the currentPage is equal than the pagesCount', () => { + component.currentPage = 3; + fixture.detectChanges(); + expect(component.hasNextPage()).toBe(false); + }); + it('should the hasNextPage function return false when the pagesCount is 0', () => { + component.pagesCount = 0; + component.totalCount = 0; + component.currentPage = 0; + fixture.detectChanges(); + expect(component.hasNextPage()).toBe(false); + }); + + it('should the hasPreviousPage function return true when the currentPage is greater than 0 and the pagesCount is greater than 0', () => { + component.currentPage = 1; + fixture.detectChanges(); + expect(component.hasPreviousPage()).toBe(true); + }); + it('should the hasPreviousPage function return false when the currentPage is equal to 0', () => { + component.currentPage = 0; + fixture.detectChanges(); + expect(component.hasPreviousPage()).toBe(false); + }); + it('should the hasPreviousPage function return false when the pagesCount is 0', () => { + component.pagesCount = 0; + component.totalCount = 0; + fixture.detectChanges(); + expect(component.hasPreviousPage()).toBe(false); + }); + + it('should the setNextPage function increment the value/currentPage when it is less then the pagesCount', () => { + let initialPage = 0; + let pagesCount = 3; + component.pagesCount = pagesCount; + component.totalCount = 30; + component.currentPage = initialPage; + fixture.detectChanges(); + component.setNextPage(); + fixture.detectChanges(); + expect(component.currentPage).toEqual(initialPage + 1); + }); + + it('should not the setNextPage function increment the value/currentPage when it is on the last page', () => { + let pagesCount = 3; + component.pagesCount = pagesCount; + component.totalCount = 30; + component.currentPage = pagesCount - 1; + fixture.detectChanges(); + component.setNextPage(); + fixture.detectChanges(); + expect(component.currentPage).toEqual(pagesCount - 1); + }); + + it('should the setPreviousPage function decrement the value/currentPage', () => { + let initialPage = 1; + component.pagesCount = 3; + component.totalCount = 30; + component.currentPage = initialPage; + fixture.detectChanges(); + component.setPreviousPage(); + fixture.detectChanges(); + expect(component.currentPage).toEqual(initialPage - 1); + }); + + it('should not the setPreviousPage function decrement the value/currentPage when it is equal to 0', () => { + component.pagesCount = 3; + component.totalCount = 30; + component.currentPage = 0; + fixture.detectChanges(); + component.setPreviousPage(); + fixture.detectChanges(); + expect(component.currentPage).toEqual(0); + }); + + it('should the setFirstPage set the value/currentPage to 0', () => { + component.pagesCount = 3; + component.totalCount = 30; + component.currentPage = 1; + fixture.detectChanges(); + component.setFirstPage(); + fixture.detectChanges(); + expect(component.currentPage).toEqual(0); + }); + + + it('should the setLastPage set the value/currentPage to the value of pagesCount', () => { + component.setLastPage(); + fixture.detectChanges(); + expect(component.currentPage).toEqual(component.pagesCount - 1); + }); + }); http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.ts index c71844c..5f85da7 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.ts @@ -51,16 +51,80 @@ export class PaginationControlsComponent implements ControlValueAccessor { } set value(newValue: number) { - this.currentPage = newValue; - this.currentPageChange.emit(newValue); - this.onChange(newValue); + if (this.isValidValue(newValue)) { // this is the last validation check + this.currentPage = newValue; + this.currentPageChange.emit(newValue); + if (this.onChange) { + this.onChange(newValue); + } + } else { + throw new Error(`Invalid value ${newValue}. The currentPage should be between 0 and ${this.pagesCount}.`); + } + } + + /** + * A simple check if the given value is valid for the current pagination instance + * @param {number} value The new value to test + * @returns {boolean} + */ + private isValidValue(value: number): boolean { + return value <= this.pagesCount || value >= 0; + } + + /** + * The goal is to set the value to the first page... obviously to zero. It is just to have a centralized api for that. + */ + setFirstPage(): void { + this.value = 0; + } + + /** + * The goal is to set the value to the last page which is the pagesCount property anyway. + */ + setLastPage(): void { + this.value = this.pagesCount - 1; + } + + /** + * The goal is to decrease the value (currentPage) property if it is possible (checking with 'hasPreviousPage'). + * @returns {number} The new value of the currentPage + */ + setPreviousPage(): number { + if (this.hasPreviousPage()) { + this.value -= 1; + } + return this.value; + } + + /** + * The goal is to increase the value (currentPage) property if it is possible (checking with 'hasNextPage'). + * @returns {number} The new value of the currentPage + */ + setNextPage(): number { + if (this.hasNextPage()){ + this.value += 1; + } + return this.value; + } + + /** + * The goal is to have a single source of true to check if we can set a next page or not. + * @returns {boolean} + */ + hasNextPage(): boolean { + return this.pagesCount > 0 && this.value < this.pagesCount - 1; } - updateValue(isDecrement?: boolean) { - isDecrement? this.value-- : this.value++; + /** + * The goal is to have a single source of true to check if we can set a previous page or not. + * @returns {boolean} + */ + hasPreviousPage(): boolean { + return this.pagesCount > 0 && this.value > 0; } - writeValue() { + writeValue(value: number) { + this.value = value; } registerOnChange(callback: any): void { http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.html ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.html index 679a7e5..4be0a47 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.html +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.html @@ -17,7 +17,7 @@ <form class="pagination-form" [formGroup]="filtersForm"> <filter-dropdown [label]="filterInstance.label" formControlName="pageSize" [options]="filterInstance.options" - [defaultLabel]="filterInstance.defaultLabel" [isRightAlign]="true" isDropup="true"></filter-dropdown> + [isRightAlign]="true" [isDropup]="true"></filter-dropdown> <span>{{'pagination.numbers' | translate: numbersTranslateParams}}</span> <pagination-controls formControlName="page" [totalCount]="totalCount" [pagesCount]="pagesCount" (currentPageChange)="setCurrentPage($event)"></pagination-controls> http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.spec.ts index ff8675d..c820027 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.spec.ts @@ -39,7 +39,14 @@ describe('PaginationComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(PaginationComponent); component = fixture.componentInstance; - component.filterInstance = {}; + component.filterInstance = { + defaultSelection: [ + { + label: '10', + value: '10' + } + ] + }; component.filtersForm = new FormGroup({ pageSize: new FormControl() }); http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.ts index cc5589f..890c2ee 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination/pagination.component.ts @@ -18,6 +18,8 @@ import {Component, OnInit, Input} from '@angular/core'; import {FormGroup} from '@angular/forms'; +import {ListItem} from '@app/classes/list-item'; +import {FilterCondition} from '@app/classes/filtering'; @Component({ selector: 'pagination', @@ -27,9 +29,9 @@ import {FormGroup} from '@angular/forms'; export class PaginationComponent implements OnInit { ngOnInit() { - this.setPageSizeFromString(this.filterInstance.defaultValue); - this.filtersForm.controls.pageSize.valueChanges.subscribe((value: string): void => { - this.setPageSizeFromString(value); + this.setPageSizeFromString(this.filterInstance.defaultSelection[0].value); + this.filtersForm.controls.pageSize.valueChanges.subscribe((selection: ListItem): void => { + this.setPageSizeFromString(selection[0].value); }); } @@ -37,7 +39,7 @@ export class PaginationComponent implements OnInit { filtersForm: FormGroup; @Input() - filterInstance: any; + filterInstance: FilterCondition; @Input() currentCount?: number; http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/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 5520310..18ff715 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 @@ -159,7 +159,7 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess this.isValueInput = true; this.currentValue = ''; setTimeout(() => this.valueInput.focus(), 0); - } + }; onParameterValueChange(event: KeyboardEvent): void { if (this.utils.isEnterPressed(event) && this.currentValue) { @@ -187,7 +187,7 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess isExclude: options.isExclude }); this.updateValue(); - } + }; removeParameter(event: MouseEvent, id: number): void { this.parameters = this.parameters.filter(parameter => parameter.id !== id); @@ -196,10 +196,14 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess } updateValue() { - this.onChange(this.parameters); + if (this.onChange) { + this.onChange(this.parameters); + } } - writeValue() { + writeValue(parameters: any [] = []) { + this.parameters = parameters; + this.updateValue(); } registerOnChange(callback: any): void { http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.html ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.html new file mode 100644 index 0000000..a2e666e --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.html @@ -0,0 +1,76 @@ +<!-- + 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. +--> + +<dropdown-button class="pull-right" label="logs.columns" [options]="columns" [isRightAlign]="true" + [isMultipleChoice]="true" action="updateSelectedColumns" + [additionalArgs]="logsTypeMapObject.fieldsModel"></dropdown-button> +<form *ngIf="logs && logs.length" [formGroup]="filtersForm" class="row pull-right"> + <filter-dropdown class="col-md-12" [label]="filters.serviceLogsSorting.label" formControlName="serviceLogsSorting" + [options]="filters.serviceLogsSorting.options" [isRightAlign]="true"></filter-dropdown> +</form> +<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="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.value) === -1" [ngClass]="'log-' + column.value"> + {{log[column.value]}} + </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>
