Repository: ambari Updated Branches: refs/heads/trunk 786d4b633 -> cc535c841
AMBARI-22477 Log Search UI: implement access logs table. (ababiichuk) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/cc535c84 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/cc535c84 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/cc535c84 Branch: refs/heads/trunk Commit: cc535c841f28ea50cf659ada8e573e84141146ac Parents: 786d4b6 Author: ababiichuk <[email protected]> Authored: Mon Nov 20 13:33:47 2017 +0200 Committer: ababiichuk <[email protected]> Committed: Mon Nov 20 13:33:47 2017 +0200 ---------------------------------------------------------------------- .../ambari-logsearch-web/src/app/app.module.ts | 6 +- .../components/logs-table-component.spec.ts | 61 +++++++ .../classes/components/logs-table-component.ts | 51 ++++++ .../src/app/classes/models/audit-log.ts | 2 +- .../audit-logs-table.component.html | 54 +++++++ .../audit-logs-table.component.less | 21 +++ .../audit-logs-table.component.spec.ts | 157 +++++++++++++++++++ .../audit-logs-table.component.ts | 55 +++++++ .../filters-panel/filters-panel.component.ts | 2 +- .../logs-container.component.html | 15 +- .../logs-container/logs-container.component.ts | 62 ++------ .../logs-list/logs-list.component.html | 72 --------- .../logs-list/logs-list.component.less | 97 ------------ .../logs-list/logs-list.component.spec.ts | 110 ------------- .../components/logs-list/logs-list.component.ts | 151 ------------------ .../service-logs-table.component.html | 76 +++++++++ .../service-logs-table.component.less | 97 ++++++++++++ .../service-logs-table.component.spec.ts | 126 +++++++++++++++ .../service-logs-table.component.ts | 135 ++++++++++++++++ .../ambari-logsearch-web/src/app/mock-data.ts | 4 +- .../src/app/services/auth.service.spec.ts | 2 +- .../src/app/services/logs-container.service.ts | 95 +++++++++-- .../src/app/services/mock-api-data.service.ts | 22 +++ 23 files changed, 970 insertions(+), 503 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/cc535c84/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts b/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts index 805f8e2..5e43582 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts @@ -67,7 +67,6 @@ import {DropdownListComponent} from '@app/components/dropdown-list/dropdown-list import {FilterButtonComponent} from '@app/components/filter-button/filter-button.component'; import {AccordionPanelComponent} from '@app/components/accordion-panel/accordion-panel.component'; import {CollapsiblePanelComponent} from '@app/components/collapsible-panel/collapsible-panel.component'; -import {LogsListComponent} from '@app/components/logs-list/logs-list.component'; import {LogMessageComponent} from '@app/components/log-message/log-message.component'; import {LogLevelComponent} from '@app/components/log-level/log-level.component'; import {DropdownButtonComponent} from '@app/components/dropdown-button/dropdown-button.component'; @@ -85,6 +84,8 @@ import {DatePickerComponent} from '@app/components/date-picker/date-picker.compo import {LogContextComponent} from '@app/components/log-context/log-context.component'; import {LogFileEntryComponent} from '@app/components/log-file-entry/log-file-entry.component'; import {TabsComponent} from '@app/components/tabs/tabs.component'; +import {ServiceLogsTableComponent} from '@app/components/service-logs-table/service-logs-table.component'; +import {AuditLogsTableComponent} from '@app/components/audit-logs-table/audit-logs-table.component'; import {TimeZoneAbbrPipe} from '@app/pipes/timezone-abbr.pipe'; import {TimerSecondsPipe} from '@app/pipes/timer-seconds.pipe'; @@ -122,7 +123,6 @@ export function getXHRBackend(injector: Injector, browser: BrowserXhr, xsrf: XSR FilterButtonComponent, AccordionPanelComponent, CollapsiblePanelComponent, - LogsListComponent, LogLevelComponent, LogMessageComponent, DropdownButtonComponent, @@ -140,6 +140,8 @@ export function getXHRBackend(injector: Injector, browser: BrowserXhr, xsrf: XSR LogContextComponent, LogFileEntryComponent, TabsComponent, + ServiceLogsTableComponent, + AuditLogsTableComponent, TimeZoneAbbrPipe, TimerSecondsPipe ], http://git-wip-us.apache.org/repos/asf/ambari/blob/cc535c84/ambari-logsearch/ambari-logsearch-web/src/app/classes/components/logs-table-component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/components/logs-table-component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/classes/components/logs-table-component.spec.ts new file mode 100644 index 0000000..05f80a7 --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/components/logs-table-component.spec.ts @@ -0,0 +1,61 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {LogsTableComponent} from './logs-table-component'; + +describe('LogsTableComponent', () => { + let component; + + beforeEach(() => { + component = new LogsTableComponent(); + }); + + describe('#isColumnDisplayed()', () => { + const cases = [ + { + name: 'v1', + result: true, + title: 'column is displayed' + }, + { + name: 'l1', + result: false, + title: 'column is not displayed' + } + ]; + + beforeEach(() => { + component.displayedColumns = [ + { + label: 'l0', + value: 'v0' + }, + { + label: 'l1', + value: 'v1' + } + ]; + }); + + cases.forEach(test => { + it(test.title, () => { + expect(component.isColumnDisplayed(test.name)).toEqual(test.result); + }); + }); + }); +}); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/cc535c84/ambari-logsearch/ambari-logsearch-web/src/app/classes/components/logs-table-component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/components/logs-table-component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/classes/components/logs-table-component.ts new file mode 100644 index 0000000..0b8866a --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/components/logs-table-component.ts @@ -0,0 +1,51 @@ +/** + * 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 {OnChanges, SimpleChanges, Input} from '@angular/core'; +import {FormGroup} from '@angular/forms'; +import {ListItem} from '@app/classes/list-item'; +import {ServiceLog} from '@app/classes/models/service-log'; +import {AuditLog} from '@app/classes/models/audit-log'; + +export class LogsTableComponent implements OnChanges { + + ngOnChanges(changes: SimpleChanges) { + if (changes.hasOwnProperty('columns')) { + this.displayedColumns = this.columns.filter((column: ListItem): boolean => column.isChecked); + } + } + + @Input() + logs: ServiceLog[] | AuditLog[] = []; + + @Input() + columns: ListItem[] = []; + + @Input() + filtersForm: FormGroup; + + @Input() + totalCount: number = 0; + + displayedColumns: ListItem[] = []; + + isColumnDisplayed(key: string): boolean { + return this.displayedColumns.some((column: ListItem): boolean => column.value === key); + } + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/cc535c84/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/audit-log.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/audit-log.ts b/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/audit-log.ts index fbe0e46..380f14f 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/audit-log.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/audit-log.ts @@ -35,7 +35,7 @@ export interface AuditLog extends Log { repoType: number; repo: string; proxyUsers?: string[]; - evtTime: string; + evtTime: number; enforcer: string; reqContext?: string; cliType?: string; http://git-wip-us.apache.org/repos/asf/ambari/blob/cc535c84/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.html ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.html new file mode 100644 index 0000000..d6e9091 --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.html @@ -0,0 +1,54 @@ +<!-- + 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.auditLogsSorting.label" formControlName="auditLogsSorting" + [options]="filters.auditLogsSorting.options" [isRightAlign]="true"></filter-dropdown></form> +<div class="panel panel-default"> + <div class="panel-body"> + <table class="table"> + <thead> + <tr> + <th *ngIf="isColumnDisplayed('evtTime')">{{getColumnByName('evtTime').label | translate}}</th> + <ng-container *ngFor="let column of displayedColumns"> + <th *ngIf="customProcessedColumns.indexOf(column.value) === -1">{{column.label | translate}}</th> + </ng-container> + </tr> + </thead> + <tbody> + <tr *ngFor="let log of logs"> + <td *ngIf="isColumnDisplayed('evtTime')">{{log.evtTime | amTz: timeZone | amDateFormat: timeFormat}}</td> + <ng-container *ngFor="let column of displayedColumns"> + <td *ngIf="customProcessedColumns.indexOf(column.value) === -1">{{log[column.value]}}</td> + </ng-container> + </tr> + </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> + </div> +</div> http://git-wip-us.apache.org/repos/asf/ambari/blob/cc535c84/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.less ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.less new file mode 100644 index 0000000..d9b0a10 --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.less @@ -0,0 +1,21 @@ +/** + * 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. + */ + +th { + text-transform: uppercase; +} http://git-wip-us.apache.org/repos/asf/ambari/blob/cc535c84/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.spec.ts new file mode 100644 index 0000000..b6206db --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.spec.ts @@ -0,0 +1,157 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {StoreModule} from '@ngrx/store'; +import {MomentModule} from 'angular2-moment'; +import {MomentTimezoneModule} from 'angular-moment-timezone'; +import {TranslationModules} from '@app/test-config.spec'; +import {AuditLogsService, auditLogs} from '@app/services/storage/audit-logs.service'; +import {ServiceLogsService, serviceLogs} from '@app/services/storage/service-logs.service'; +import {AuditLogsFieldsService, auditLogsFields} from '@app/services/storage/audit-logs-fields.service'; +import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.service'; +import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/services/storage/service-logs-histogram-data.service'; +import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service'; +import {AppStateService, appState} from '@app/services/storage/app-state.service'; +import {AppSettingsService, appSettings} from '@app/services/storage/app-settings.service'; +import {TabsService, tabs} from '@app/services/storage/tabs.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 {LogsContainerService} from '@app/services/logs-container.service'; +import {UtilsService} from '@app/services/utils.service'; +import {HttpClientService} from '@app/services/http-client.service'; +import {PaginationComponent} from '@app/components/pagination/pagination.component'; +import {DropdownListComponent} from '@app/components/dropdown-list/dropdown-list.component'; + +import {AuditLogsTableComponent} from './audit-logs-table.component'; + +describe('AuditLogsTableComponent', () => { + let component: AuditLogsTableComponent; + let fixture: ComponentFixture<AuditLogsTableComponent>; + const httpClient = { + get: () => { + return { + subscribe: () => { + } + }; + } + }; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + AuditLogsTableComponent, + PaginationComponent, + DropdownListComponent + ], + imports: [ + FormsModule, + ReactiveFormsModule, + MomentModule, + MomentTimezoneModule, + ...TranslationModules, + StoreModule.provideStore({ + auditLogs, + serviceLogs, + auditLogsFields, + serviceLogsFields, + serviceLogsHistogramData, + serviceLogsTruncated, + appState, + appSettings, + tabs, + clusters, + components, + hosts + }) + ], + providers: [ + LogsContainerService, + UtilsService, + { + provide: HttpClientService, + useValue: httpClient + }, + AuditLogsService, + ServiceLogsService, + AuditLogsFieldsService, + ServiceLogsFieldsService, + ServiceLogsHistogramDataService, + ServiceLogsTruncatedService, + AppStateService, + AppSettingsService, + TabsService, + ClustersService, + ComponentsService, + HostsService + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AuditLogsTableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create component', () => { + expect(component).toBeTruthy(); + }); + + describe('#getColumnByName()', () => { + const cases = [ + { + name: 'v1', + result: { + label: 'l1', + value: 'v1' + }, + title: 'item is present' + }, + { + name: 'l1', + result: undefined, + title: 'item is absent' + } + ]; + + beforeEach(() => { + component.columns = [ + { + label: 'l0', + value: 'v0' + }, + { + label: 'l1', + value: 'v1' + } + ]; + }); + + cases.forEach(test => { + it(test.title, () => { + expect(component.getColumnByName(test.name)).toEqual(test.result); + }); + }); + }); +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/cc535c84/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.ts new file mode 100644 index 0000000..0e578ab --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/audit-logs-table/audit-logs-table.component.ts @@ -0,0 +1,55 @@ +/** + * 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} from '@angular/core'; +import {ListItem} from '@app/classes/list-item'; +import {LogsTableComponent} from '@app/classes/components/logs-table-component'; +import {LogsContainerService} from '@app/services/logs-container.service'; + +@Component({ + selector: 'audit-logs-table', + templateUrl: './audit-logs-table.component.html', + styleUrls: ['./audit-logs-table.component.less'] +}) +export class AuditLogsTableComponent extends LogsTableComponent { + + constructor(private logsContainer: LogsContainerService) { + super(); + } + + readonly customProcessedColumns: string[] = ['evtTime']; + + readonly timeFormat: string = 'YYYY-MM-DD HH:mm:ss,SSS'; + + get logsTypeMapObject(): object { + return this.logsContainer.logsTypeMap.auditLogs; + } + + get filters(): any { + return this.logsContainer.filters; + } + + get timeZone(): string { + return this.logsContainer.timeZone; + } + + getColumnByName(name: string): ListItem | undefined { + return this.columns.find((column: ListItem): boolean => column.value === name); + } + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/cc535c84/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts index 3314252..b41f7cd 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts @@ -97,7 +97,7 @@ export class FiltersPanelComponent { } isFilterConditionDisplayed(key: string): boolean { - return this.logsContainer.filtersFormItemsMap[this.logsType].indexOf(key) > -1 + return this.logsContainer.logsTypeMap[this.logsType].listFilters.indexOf(key) > -1 && Boolean(this.filtersForm.controls[key]); } http://git-wip-us.apache.org/repos/asf/ambari/blob/cc535c84/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 2c3a3b9..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 @@ -44,11 +44,12 @@ <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> + <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> http://git-wip-us.apache.org/repos/asf/ambari/blob/cc535c84/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 f8acd50..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,16 +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 {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'; @@ -47,35 +43,7 @@ export class LogsContainerComponent { private tabsStorage: TabsService, private logsContainer: LogsContainerService ) { this.logsContainer.loadColumnsNames(); - 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('activeLogsType').subscribe((value: string) => this.logsType = value); serviceLogsHistogramStorage.getAll().subscribe((data: BarGraph[]): void => { this.histogramData = this.logsContainer.getHistogramData(data); }); @@ -90,22 +58,10 @@ export class LogsContainerComponent { private logsType: string; - 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 = { @@ -142,6 +98,22 @@ 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.logsContainer.setCustomTimeRange(startTime, endTime); } http://git-wip-us.apache.org/repos/asf/ambari/blob/cc535c84/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 7de0b96..0000000 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.html +++ /dev/null @@ -1,72 +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" - [isRightAlign]="true" class="col-md-12"></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="!isServiceLogsFileView && (i === 0 || isDifferentDates(log.logtime, logs[i - 1].logtime))" - class="log-date-row" > - <th attr.colspan="{{displayedColumns.length + 1}}"> - {{log.logtime | amTz: timeZone | amDateFormat: dateFormat}} - </th> - </tr> - <tr class="log-item-row"> - <td class="log-action"> - <dropdown-button iconClass="fa fa-ellipsis-h action" [hideCaret]="true" [options]="logActions" - [additionalArgs]="[log]"></dropdown-button> - </td> - <td *ngIf="isColumnDisplayed('logtime')" class="log-time"> - <time> - {{log.logtime | amTz: timeZone | amDateFormat: timeFormat}} - </time> - </td> - <td *ngIf="isColumnDisplayed('level')" [ngClass]="'log-level ' + log.level.toLowerCase()"> - <log-level [logEntry]="log"></log-level> - </td> - <td *ngIf="isColumnDisplayed('type')" [ngClass]="'log-type'"> - {{log.type}} - </td> - <td *ngIf="isColumnDisplayed('log_message')" [ngClass]="'log-message'" width="*" - (contextmenu)="openMessageContextMenu($event)"> - <log-message [listenChangesOn]="displayedColumns">{{log.log_message}}</log-message> - </td> - <ng-container *ngFor="let column of displayedColumns"> - <td *ngIf="customStyledColumns.indexOf(column.name) === -1" - [ngClass]="'log-' + column.name">{{log[column.name]}}</td> - </ng-container> - </tr> - </ng-container> - </tbody> - <tfoot> - <tr> - <td attr.colspan="{{displayedColumns.length + 1}}"> - <pagination class="col-md-12" *ngIf="logs && logs.length" [totalCount]="totalCount" - [filtersForm]="filtersForm" [filterInstance]="filters.pageSize" [currentCount]="logs.length"></pagination> - </td> - </tr> - </tfoot> - </table> - <ul #contextmenu data-component="dropdown-list" class="dropdown-menu context-menu" [items]="contextMenuItems" - (selectedItemChange)="updateQuery($event)"></ul> - </div> -</div> http://git-wip-us.apache.org/repos/asf/ambari/blob/cc535c84/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 c5c4c5a..0000000 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.less +++ /dev/null @@ -1,97 +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'; - -:host { - /deep/ filter-dropdown { - justify-content: flex-end; - } - - .panel-body { - overflow: hidden; - width: 100%; - } - - table { - width: 100%; - } - - tr.log-date-row, tr.log-date-row:hover { - background: @list-header-background-color; - border: none transparent; - th { - border: none transparent; - } - } - tr.log-item-row td { - background: none transparent; - } - - td { - &.log-action { - min-width: 3em; - /deep/ .btn, /deep/ .filter-label { - font-size: 1em; - height: auto; - line-height: 1em; - padding: 0; - } - } - &.log-time { - color: @grey-color; - min-width: 7em; - text-align: right; - } - &.log-level { - text-transform: uppercase; - min-width: 8em; - .log-colors; - } - &.log-type { - color: @link-color; - } - &.log-message, &.log-path { - width: 100%; - } - } - - tr:hover td.log-action { - /deep/ .btn { - display: inline-block; - } - } - - .table.table-hover>tbody>tr{ - box-sizing: border-box; - border-width: 1px; - >td { - border-top: 0 none; - } - &:first-of-type { - border-top-color: transparent; - } - &:last-of-type { - border-bottom-color: transparent; - } - } - - .context-menu { - position: fixed; - } - -} http://git-wip-us.apache.org/repos/asf/ambari/blob/cc535c84/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 21c4f02..0000000 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-list/logs-list.component.spec.ts +++ /dev/null @@ -1,110 +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 {AuditLogsFieldsService, auditLogsFields} from '@app/services/storage/audit-logs-fields.service'; -import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.service'; -import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/services/storage/service-logs-histogram-data.service'; -import {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 {LogsContainerService} from '@app/services/logs-container.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, - auditLogsFields, - serviceLogsFields, - serviceLogsHistogramData, - serviceLogsTruncated, - tabs - }), - MomentModule, - MomentTimezoneModule, - ...TranslationModules - ], - providers: [ - { - provide: HttpClientService, - useValue: httpClient - }, - AuditLogsService, - ServiceLogsService, - AppSettingsService, - AppStateService, - ClustersService, - ComponentsService, - HostsService, - AuditLogsFieldsService, - ServiceLogsFieldsService, - ServiceLogsHistogramDataService, - ServiceLogsTruncatedService, - TabsService, - LogsContainerService, - 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/cc535c84/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 3a56fca..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 {LogsContainerService} from '@app/services/logs-container.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 logsContainer: LogsContainerService, 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.logsContainer.timeZone; - } - - get filters(): any { - return this.logsContainer.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.logsContainer.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/cc535c84/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> http://git-wip-us.apache.org/repos/asf/ambari/blob/cc535c84/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.less ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.less new file mode 100644 index 0000000..bd6d012 --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.less @@ -0,0 +1,97 @@ +/** + * 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'; + +:host { + /deep/ filter-dropdown { + justify-content: flex-end; + } + + .panel-body { + overflow: hidden; + width: 100%; + } + + table { + width: 100%; + } + + tr.log-date-row, tr.log-date-row:hover { + background: @list-header-background-color; + border: none transparent; + th { + border: none transparent; + } + } + tr.log-item-row td { + background: none transparent; + } + + td { + &.log-action { + min-width: 3em; + /deep/ .btn, /deep/ .filter-label { + font-size: 1em; + height: auto; + line-height: 1em; + padding: 0; + } + } + &.log-time { + color: @grey-color; + min-width: 7em; + text-align: right; + } + &.log-level { + text-transform: uppercase; + min-width: 8em; + .log-colors; + } + &.log-type { + color: @link-color; + } + &.log-message, &.log-path { + width: 100%; + } + } + + tr:hover td.log-action { + /deep/ .btn { + display: inline-block; + } + } + + .table.table-hover > tbody > tr { + box-sizing: border-box; + border-width: 1px; + > td { + border-top: 0 none; + } + &:first-of-type { + border-top-color: transparent; + } + &:last-of-type { + border-bottom-color: transparent; + } + } + + .context-menu { + position: fixed; + } + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/cc535c84/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.spec.ts new file mode 100644 index 0000000..0c323f7 --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.spec.ts @@ -0,0 +1,126 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {StoreModule} from '@ngrx/store'; +import {MomentModule} from 'angular2-moment'; +import {MomentTimezoneModule} from 'angular-moment-timezone'; +import {TranslationModules} from '@app/test-config.spec'; +import {AuditLogsService, auditLogs} from '@app/services/storage/audit-logs.service'; +import {ServiceLogsService, serviceLogs} from '@app/services/storage/service-logs.service'; +import {AuditLogsFieldsService, auditLogsFields} from '@app/services/storage/audit-logs-fields.service'; +import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.service'; +import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/services/storage/service-logs-histogram-data.service'; +import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service'; +import {AppStateService, appState} from '@app/services/storage/app-state.service'; +import {AppSettingsService, appSettings} from '@app/services/storage/app-settings.service'; +import {TabsService, tabs} from '@app/services/storage/tabs.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 {LogsContainerService} from '@app/services/logs-container.service'; +import {UtilsService} from '@app/services/utils.service'; +import {HttpClientService} from '@app/services/http-client.service'; +import {ComponentGeneratorService} from '@app/services/component-generator.service'; +import {ComponentActionsService} from '@app/services/component-actions.service'; +import {AuthService} from '@app/services/auth.service'; +import {PaginationComponent} from '@app/components/pagination/pagination.component'; +import {DropdownListComponent} from '@app/components/dropdown-list/dropdown-list.component'; + +import {ServiceLogsTableComponent} from './service-logs-table.component'; + +describe('ServiceLogsTableComponent', () => { + let component: ServiceLogsTableComponent; + let fixture: ComponentFixture<ServiceLogsTableComponent>; + const httpClient = { + get: () => { + return { + subscribe: () => { + } + }; + } + }; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + ServiceLogsTableComponent, + PaginationComponent, + DropdownListComponent + ], + imports: [ + FormsModule, + ReactiveFormsModule, + MomentModule, + MomentTimezoneModule, + ...TranslationModules, + StoreModule.provideStore({ + auditLogs, + serviceLogs, + auditLogsFields, + serviceLogsFields, + serviceLogsHistogramData, + serviceLogsTruncated, + appState, + appSettings, + tabs, + clusters, + components, + hosts + }) + ], + providers: [ + LogsContainerService, + UtilsService, + { + provide: HttpClientService, + useValue: httpClient + }, + AuditLogsService, + ServiceLogsService, + AuditLogsFieldsService, + ServiceLogsFieldsService, + ServiceLogsHistogramDataService, + ServiceLogsTruncatedService, + AppStateService, + AppSettingsService, + TabsService, + ClustersService, + ComponentsService, + HostsService, + ComponentGeneratorService, + ComponentActionsService, + AuthService + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ServiceLogsTableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create component', () => { + expect(component).toBeTruthy(); + }); +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/cc535c84/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts new file mode 100644 index 0000000..9f38371 --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts @@ -0,0 +1,135 @@ +/** + * 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, ViewChild, ElementRef} from '@angular/core'; +import {ListItem} from '@app/classes/list-item'; +import {LogsTableComponent} from '@app/classes/components/logs-table-component'; +import {LogsContainerService} from '@app/services/logs-container.service'; +import {UtilsService} from '@app/services/utils.service'; + +@Component({ + selector: 'service-logs-table', + templateUrl: './service-logs-table.component.html', + styleUrls: ['./service-logs-table.component.less'] +}) +export class ServiceLogsTableComponent extends LogsTableComponent implements AfterViewInit { + + constructor(private logsContainer: LogsContainerService, private utils: UtilsService) { + super(); + } + + ngAfterViewInit() { + if (this.contextMenu) { + this.contextMenuElement = this.contextMenu.nativeElement; + } + } + + @ViewChild('contextmenu', { + read: ElementRef + }) + contextMenu: ElementRef; + + readonly dateFormat: string = 'dddd, MMMM Do'; + + readonly timeFormat: string = 'h:mm:ss A'; + + 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 customStyledColumns: string[] = ['level', 'type', 'logtime', 'log_message']; + + readonly contextMenuItems: ListItem[] = [ + { + 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 + } + ]; + + private readonly messageFilterParameterName: string = 'log_message'; + + private contextMenuElement: HTMLElement; + + private selectedText: string = ''; + + get timeZone(): string { + return this.logsContainer.timeZone; + } + + get filters(): any { + return this.logsContainer.filters; + } + + get logsTypeMapObject(): object { + return this.logsContainer.logsTypeMap.serviceLogs; + } + + isDifferentDates(dateA, dateB): boolean { + return this.utils.isDifferentDates(dateA, dateB, this.timeZone); + } + + 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: ListItem): void { + this.logsContainer.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/cc535c84/ambari-logsearch/ambari-logsearch-web/src/app/mock-data.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/mock-data.ts b/ambari-logsearch/ambari-logsearch-web/src/app/mock-data.ts index 147efef..7578867 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/mock-data.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/mock-data.ts @@ -47,7 +47,7 @@ export const mockData = { proxyUsers: [ 'admin' ], - evtTime: '2017-05-29T11:30:22.531Z', + evtTime: 1496057422531, enforcer: 'ambari-acl', reqContext: 'ambari', cliType: 'GET', @@ -94,7 +94,7 @@ export const mockData = { proxyUsers: [ 'user' ], - evtTime: '2017-05-29T11:30:22.531Z', + evtTime: 1496057422531, enforcer: 'hdfs', reqContext: 'ambari_server', cliType: 'PUT', http://git-wip-us.apache.org/repos/asf/ambari/blob/cc535c84/ambari-logsearch/ambari-logsearch-web/src/app/services/auth.service.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/auth.service.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/auth.service.spec.ts index fd5a83e..65f0936 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/services/auth.service.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/auth.service.spec.ts @@ -55,7 +55,7 @@ describe('AuthService', () => { isError: false, postFormData: function () { const isError = this.isError; - return Observable.create(observer => observer.next(isError ? errorResponse : successResponse)).delay(1000); + return Observable.create(observer => observer.next(isError ? errorResponse : successResponse)).delay(1); } }; http://git-wip-us.apache.org/repos/asf/ambari/blob/cc535c84/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts index f45887b..90ab9b7 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts @@ -22,6 +22,9 @@ import {Response} from '@angular/http'; import {Subject} from 'rxjs/Subject'; import {Observable} from 'rxjs/Observable'; import 'rxjs/add/observable/timer'; +import 'rxjs/add/observable/combineLatest'; +import 'rxjs/add/operator/first'; +import 'rxjs/add/operator/map'; import 'rxjs/add/operator/takeUntil'; import * as moment from 'moment-timezone'; import {HttpClientService} from '@app/services/http-client.service'; @@ -41,7 +44,10 @@ import {ActiveServiceLogEntry} from '@app/classes/active-service-log-entry'; import {FilterCondition, TimeUnitListItem, SortingListItem} from '@app/classes/filtering'; import {ListItem} from '@app/classes/list-item'; import {Tab} from '@app/classes/models/tab'; +import {LogField} from '@app/classes/models/log-field'; +import {AuditLog} from '@app/classes/models/audit-log'; import {AuditLogField} from '@app/classes/models/audit-log-field'; +import {ServiceLog} from '@app/classes/models/service-log'; import {ServiceLogField} from '@app/classes/models/service-log-field'; import {BarGraph} from '@app/classes/models/bar-graph'; import {NodeItem} from '@app/classes/models/node-item'; @@ -383,7 +389,35 @@ export class LogsContainerService { options: [], defaultSelection: [] }, - sorting: { + auditLogsSorting: { + label: 'sorting.title', + options: [ + { + label: 'sorting.time.asc', + value: { + key: 'evtTime', + type: 'asc' + } + }, + { + label: 'sorting.time.desc', + value: { + key: 'evtTime', + type: 'desc' + } + } + ], + defaultSelection: [ + { + label: 'sorting.time.desc', + value: { + key: 'evtTime', + type: 'desc' + } + } + ] + }, + serviceLogsSorting: { label: 'sorting.title', options: [ { @@ -432,11 +466,6 @@ export class LogsContainerService { query: {} }; - readonly filtersFormItemsMap: {[key: string]: string[]} = { - serviceLogs: ['clusters', 'timeRange', 'components', 'levels', 'hosts', 'sorting', 'pageSize', 'page', 'query'], - auditLogs: ['clusters', 'timeRange', 'sorting', 'pageSize', 'page', 'query'] // TODO add all the required fields - }; - readonly colors = { WARN: '#FF8916', ERROR: '#E81D1D', @@ -447,13 +476,14 @@ export class LogsContainerService { UNKNOWN: '#BDBDBD' }; - private readonly listFilters = { + private readonly filtersMapping = { clusters: ['clusters'], timeRange: ['to', 'from'], components: ['mustBe'], levels: ['level'], hosts: ['hostList'], - sorting: ['sortType', 'sortBy'], + auditLogsSorting: ['sortType', 'sortBy'], + serviceLogsSorting: ['sortType', 'sortBy'], pageSize: ['pageSize'], page: ['page'], query: ['includeQuery', 'excludeQuery'] @@ -471,11 +501,16 @@ export class LogsContainerService { readonly logsTypeMap = { auditLogs: { logsModel: this.auditLogsStorage, - fieldsModel: this.auditLogsFieldsStorage + fieldsModel: this.auditLogsFieldsStorage, + // TODO add all the required fields + listFilters: ['clusters', 'timeRange', 'auditLogsSorting', 'pageSize', 'page', 'query'], + histogramFilters: ['clusters', 'timeRange', 'query'] }, serviceLogs: { logsModel: this.serviceLogsStorage, - fieldsModel: this.serviceLogsFieldsStorage + fieldsModel: this.serviceLogsFieldsStorage, + listFilters: ['clusters', 'timeRange', 'components', 'levels', 'hosts', 'serviceLogsSorting', 'pageSize', 'page', 'query'], + histogramFilters: ['clusters', 'timeRange', 'components', 'levels', 'hosts', 'query'] } }; @@ -502,6 +537,38 @@ export class LogsContainerService { private filtersFormChange: Subject<any> = new Subject(); + private columnsMapper<FieldT extends LogField>(fields: FieldT[]): ListItem[] { + return fields.filter((field: FieldT): boolean => field.isAvailable).map((field: FieldT): ListItem => { + return { + value: field.name, + label: field.displayName || field.name, + isChecked: field.isDisplayed + }; + }); + } + + private logsMapper<LogT extends AuditLog & ServiceLog>(result: [LogT[], ListItem[]]): LogT[] { + const [logs, fields] = result; + if (fields.length) { + const names = fields.map((field: ListItem): string => field.value); + return logs.map((log: LogT): LogT => { + return names.reduce((currentObject: object, key: string) => Object.assign(currentObject, { + [key]: log[key] + }), {}) as LogT; + }); + } else { + return []; + } + } + + auditLogsColumns: Observable<ListItem[]> = this.auditLogsFieldsStorage.getAll().map(this.columnsMapper); + + serviceLogsColumns: Observable<ListItem[]> = this.serviceLogsFieldsStorage.getAll().map(this.columnsMapper); + + serviceLogs: Observable<ServiceLog[]> = Observable.combineLatest(this.serviceLogsStorage.getAll(), this.serviceLogsColumns).map(this.logsMapper); + + auditLogs: Observable<AuditLog[]> = Observable.combineLatest(this.auditLogsStorage.getAll(), this.auditLogsColumns).map(this.logsMapper); + /** * Get instance for dropdown list from string * @param name {string} @@ -604,11 +671,11 @@ export class LogsContainerService { }); } - private getParams(filtersMapName: string): {[key: string]: string} { + private getParams(filtersMapName: string, logsType: string = this.activeLogsType): {[key: string]: string} { let params = {}; - Object.keys(this[filtersMapName]).forEach((key: string): void => { + this.logsTypeMap[logsType][filtersMapName].forEach((key: string): void => { const inputValue = this.filtersForm.getRawValue()[key], - paramNames = this[filtersMapName][key]; + paramNames = this.filtersMapping[key]; paramNames.forEach((paramName: string): void => { let value; const valueGetter = this.valueGetters[paramName] || this.defaultValueGetter; @@ -845,7 +912,7 @@ export class LogsContainerService { } getFiltersData(listType: string): object { - const itemsList = this.filtersFormItemsMap[listType], + const itemsList = this.logsTypeMap[listType].listFilters, keys = Object.keys(this.filters).filter((key: string): boolean => itemsList.indexOf(key) > -1); return keys.reduce((currentObject: object, key: string): object => { return Object.assign(currentObject, { http://git-wip-us.apache.org/repos/asf/ambari/blob/cc535c84/ambari-logsearch/ambari-logsearch-web/src/app/services/mock-api-data.service.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/mock-api-data.service.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/mock-api-data.service.ts index 8b157df..985b52f 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/services/mock-api-data.service.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/mock-api-data.service.ts @@ -65,6 +65,28 @@ export class mockApiDataService implements InMemoryDbService { isValuesList: true } } + }, + 'api/v1/audit/logs': { + pathToCollection: 'logList', + totalCountKey: 'totalCount', + filters: { + clusters: { + key: 'cluster', + isValuesList: true + }, + iMessage: { + key: 'log_message', + filterFunction: (value, filterValue) => value.toLowerCase().indexOf(filterValue.toLowerCase()) > -1 + }, + from: { + key: 'evtTime', + filterFunction: (value, filterValue) => value >= moment(filterValue).valueOf() + }, + to: { + key: 'evtTime', + filterFunction: (value, filterValue) => value < moment(filterValue).valueOf() + } + } } };
