You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by mr...@apache.org on 2017/11/27 23:29:31 UTC
[19/30] ambari git commit: Merge trunk with feature branch and fix
some UT compilation issues (mradhakrishnan)
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>