blob: e8d2ee0a86068cf7ad123742f466dbaf336195ce [file] [log] [blame]
/**
* 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, OnInit, ElementRef, ViewChild, HostListener, Input, OnDestroy, ChangeDetectorRef} from '@angular/core';
import {FormGroup} from '@angular/forms';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/debounceTime';
import {LogsContainerService} from '@app/services/logs-container.service';
import {ServiceLogsHistogramDataService} from '@app/services/storage/service-logs-histogram-data.service';
import {AuditLogsGraphDataService} from '@app/services/storage/audit-logs-graph-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 {LogTypeTab} from '@app/classes/models/log-type-tab';
import {BarGraph} from '@app/classes/models/bar-graph';
import {ActiveServiceLogEntry} from '@app/classes/active-service-log-entry';
import {ListItem} from '@app/classes/list-item';
import {HomogeneousObject, LogLevelObject} from '@app/classes/object';
import {LogsType, LogLevel} from '@app/classes/string';
import {FiltersPanelComponent} from '@app/components/filters-panel/filters-panel.component';
import {Subscription} from 'rxjs/Subscription';
import {LogsFilteringUtilsService} from '@app/services/logs-filtering-utils.service';
import {ActivatedRoute, Router} from '@angular/router';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {LogsStateService} from '@app/services/storage/logs-state.service';
@Component({
selector: 'logs-container',
templateUrl: './logs-container.component.html',
styleUrls: ['./logs-container.component.less']
})
export class LogsContainerComponent implements OnInit, OnDestroy {
private isFilterPanelFixedPostioned = false;
tabs: Observable<LogTypeTab[]> = this.tabsStorage.getAll().map((tabs: LogTypeTab[]) => {
return tabs.map((tab: LogTypeTab) => {
const params = this.logsFilteringUtilsService.getParamsFromActiveFilter(
tab.activeFilters, tab.appState.activeLogsType
);
return Object.assign({}, tab, {params});
});
});
private logsType: LogsType;
serviceLogsHistogramData: HomogeneousObject<HomogeneousObject<number>>;
auditLogsGraphData: HomogeneousObject<HomogeneousObject<number>>;
serviceLogsHistogramColors: HomogeneousObject<string> = this.logsContainerService.logLevels.reduce((
currentObject: HomogeneousObject<string>, level: LogLevelObject
): HomogeneousObject<string> => {
return Object.assign({}, currentObject, {
[level.name]: level.color
});
}, {});
isServiceLogContextView = false;
private activeTabId$: BehaviorSubject<string> = new BehaviorSubject(
this.router.routerState.snapshot.root.firstChild && this.router.routerState.snapshot.root.firstChild.params.activeTab
);
@ViewChild('container') containerRef: ElementRef;
@ViewChild('filtersPanel') filtersPanelRef: FiltersPanelComponent;
@Input()
routerPath: string[] = ['/logs'];
private subscriptions: Subscription[] = [];
private paramsSyncInProgress: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
private isServiceLogsFileView$: Observable<boolean> = this.appState.getParameter('isServiceLogsFileView');
constructor(
private appState: AppStateService,
private tabsStorage: TabsService,
private logsContainerService: LogsContainerService,
private logsFilteringUtilsService: LogsFilteringUtilsService,
private serviceLogsHistogramStorage: ServiceLogsHistogramDataService,
private auditLogsGraphStorage: AuditLogsGraphDataService,
private router: Router,
private activatedRoute: ActivatedRoute,
private logsStateService: LogsStateService
) {}
ngOnInit() {
this.logsContainerService.loadColumnsNames();
// set te logsType when the activeLogsType state has changed
this.subscriptions.push(
this.appState.getParameter('activeLogsType').subscribe((value: LogsType) => this.logsType = value)
);
// set the hhistogramm data
this.subscriptions.push(
this.serviceLogsHistogramStorage.getAll().subscribe((data: BarGraph[]): void => {
this.serviceLogsHistogramData = this.logsContainerService.getGraphData(data, this.logsContainerService.logLevels.map((
level: LogLevelObject
): LogLevel => {
return level.name;
}));
})
);
// audit graph data set
this.subscriptions.push(
this.auditLogsGraphStorage.getAll().subscribe((data: BarGraph[]): void => {
this.auditLogsGraphData = this.logsContainerService.getGraphData(data);
})
);
// service log context flag subscription
this.subscriptions.push(
this.appState.getParameter('isServiceLogContextView').subscribe((value: boolean): void => {
this.isServiceLogContextView = value;
})
);
this.activatedRoute.params.first().map(params => params.activeTab).subscribe((tabId) => {
this.logsContainerService.setActiveTabById(tabId);
});
//// SYNC BETWEEN PARAMS AND FORM
// sync to filters form when the query params changed (only when there is no other way sync)
this.subscriptions.push(
this.activatedRoute.params.filter(() => !this.paramsSyncInProgress.getValue())
.subscribe(this.onParamsChange)
);
// Sync from form to params on form values change
this.subscriptions.push(
this.filtersForm.valueChanges
.filter(() => !this.logsContainerService.filtersFormSyncInProgress.getValue())
.subscribe(this.onFiltersFormChange)
);
//// SYNC BETWEEN PARAMS AND FORM END
//// TAB CHANGE
// when the activeTabId$ behaviour subject change, this depends on the params' changes
this.subscriptions.push(
this.activeTabId$.distinctUntilChanged().subscribe(this.onActiveTabIdChange)
);
// set the position of the filter panel depending on the scroll height: so it is fixed when it would be out from the screen
this.subscriptions.push(
Observable.fromEvent(window, 'scroll').debounceTime(10).subscribe(this.setFixedPositionValue)
);
}
ngOnDestroy() {
this.subscriptions.forEach((subscription: Subscription) => subscription.unsubscribe());
}
get filtersForm(): FormGroup {
return this.logsContainerService.filtersForm;
};
get totalCount(): number {
return this.logsContainerService.totalCount;
}
get autoRefreshRemainingSeconds(): number {
return this.logsContainerService.autoRefreshRemainingSeconds;
}
get autoRefreshMessageParams(): object {
return {
remainingSeconds: this.autoRefreshRemainingSeconds
};
}
/**
* The goal is to provide the single source for the parameters of 'xyz events found' message.
* @returns {Object}
*/
get totalEventsFoundMessageParams(): {totalCount: number} {
return {
totalCount: this.totalCount
};
}
get isServiceLogsFileView(): boolean {
return this.logsContainerService.isServiceLogsFileView;
}
get activeLog(): ActiveServiceLogEntry | null {
return this.logsContainerService.activeLog;
}
get auditLogs(): Observable<AuditLog[]> {
return this.logsContainerService.auditLogs;
}
get auditLogsColumns(): Observable<ListItem[]> {
return this.logsContainerService.auditLogsColumns;
}
get serviceLogs(): Observable<ServiceLog[]> {
return this.logsContainerService.serviceLogs;
}
get serviceLogsColumns(): Observable<ListItem[]> {
return this.logsContainerService.serviceLogsColumns;
}
//
// SECTION: TABS
//
/**
* Set the active params in the store corresponding to the URL param (activeTab)
* @param {string} tabId The 'activeTab' segment of the URL (eg.: #/logs/serviceLogs where the serviceLogs is the activeTab parameter)
*/
private onActiveTabIdChange = (tabId: string): void => {
this.logsContainerService.setActiveTabById(tabId);
}
//
// SECTION END: TABS
//
//
// SECTION: FILTER SYNCHRONIZATION
//
/**
* Turn on the 'query params in sync' flag, so that the query to form sync don't run.
* So when we actualize the query params to reflect the filters form values we have to turn of the back sync (query params change to form)
*/
private paramsSyncStart = (): void => {
this.paramsSyncInProgress.next(true);
}
/**
* Turn off the 'query params in sync' flag
*/
private paramsSyncStop = (): void => {
this.paramsSyncInProgress.next(false);
}
/**
* The goal is to make the app always bookmarkable.
* @param filters
*/
private syncFiltersToParams(filters): void {
const params = this.logsFilteringUtilsService.getParamsFromActiveFilter(
filters, this.logsContainerService.activeLogsType
);
this.paramsSyncStart(); // turn on the 'sync in progress' flag
this.router.navigate([params], { relativeTo: this.activatedRoute })
.then(this.paramsSyncStop, this.paramsSyncStop) // turn off the 'sync in progress' flag
.catch(this.paramsSyncStop); // turn off the 'sync in progress' flag
}
/**
* This will call the LogsContainerService to reset the filter form with the given values.
* It will add default values where it is missing from the object.
* @param values {[key: string]: any} The new values for the filter form
*/
private resetFiltersForm(values: {[key: string]: any}): void {
if (Object.keys(values).length) {
this.logsContainerService.resetFiltersForms({
...this.logsFilteringUtilsService.defaultFilterSelections,
...values
});
}
}
/**
* It will request the LogsContainerService to store the given filters to the given tab
* in order to apply these filters when there is no filter params in the URL.
* @param filters {[key: string]: any} The values for the filters form
* @param tabId string The tab where it should be stored (in activeFilters property)
*/
private syncFilterToTabStore(filters: {[key: string]: any}, tabId: string): void {
this.logsContainerService.syncFiltersToTabFilters(filters, tabId);
}
/**
* Handle the filters' form changes and sync it to the query parameters
* @param values The new filter values. This is the raw value of the form group
*/
private onFiltersFormChange = (filters): void => {
this.syncFiltersToParams(filters);
}
private onParamsChange = (params: {[key: string]: any}) => {
const {activeTab, ...filtersParams} = params;
this.tabsStorage.findInCollection((tab: LogTypeTab) => tab.id === params.activeTab)
.first()
.subscribe((tab) => {
if (tab) {
const filtersFromParams: {[key: string]: any} = this.logsFilteringUtilsService.getFilterFromParams(
filtersParams,
tab.appState.activeLogsType
);
// we dont't have to reset the form with the new values when there is tab changes
// because the onActiveTabIdChange will call the setActiveTabById on LogsContainerService
// which will reset the form to the tab's activeFilters prop.
// If we do reset wvery time then the form will be reseted twice with every tab changes... not a big deal anyway
if (this.activeTabId$.getValue() === activeTab) {
this.resetFiltersForm(filtersFromParams);
}
this.syncFilterToTabStore(filtersFromParams, activeTab);
this.activeTabId$.next(activeTab);
}
});
}
//
// SECTION END: FILTER SYNCHRONIZATION
//
/**
* The goal is to set the fixed position of the filter panel when it is scrolled to the top. So that the panel
* can be always visible for the user.
*/
private setFixedPositionValue = (): void => {
const el: Element = this.containerRef.nativeElement;
const top: number = el.getBoundingClientRect().top;
const valueBefore: boolean = this.isFilterPanelFixedPostioned;
if (valueBefore !== (top <= 0)) {
const fpEl: Element = this.filtersPanelRef.containerEl;
this.isFilterPanelFixedPostioned = top <= 0;
const filtersPanelHeight: number = fpEl.getBoundingClientRect().height;
const containerPaddingTop: number = parseFloat(window.getComputedStyle(el).paddingTop);
const htmlEl: HTMLElement = this.containerRef.nativeElement;
if (this.isFilterPanelFixedPostioned) {
htmlEl.style.paddingTop = (containerPaddingTop + filtersPanelHeight) + 'px';
} else {
htmlEl.style.paddingTop = (containerPaddingTop - filtersPanelHeight) + 'px';
}
}
}
setCustomTimeRange(startTime: number, endTime: number): void {
this.logsContainerService.setCustomTimeRange(startTime, endTime);
}
onSwitchTab(activeTab: LogTypeTab): void {
this.logsContainerService.switchTab(activeTab);
}
onCloseTab(activeTab: LogTypeTab, newActiveTab: LogTypeTab): void {
const activateNewTab: boolean = activeTab.isActive;
this.tabsStorage.deleteObjectInstance(activeTab);
if (activateNewTab && newActiveTab) {
this.router.navigate(['/logs', ...this.logsFilteringUtilsService.getNavigationForTab(newActiveTab)]);
}
}
}