blob: 7dfb9af0bf2b32d9a9ed50a684e5d476ae93ccb3 [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, AfterViewChecked, ViewChild, ElementRef, Input, ChangeDetectorRef} from '@angular/core';
import {ListItem} from '@app/classes/list-item';
import {LogsTableComponent} from '@app/classes/components/logs-table/logs-table-component';
import {ServiceLog} from '@app/classes/models/service-log';
import {LogsContainerService} from '@app/services/logs-container.service';
import {UtilsService} from '@app/services/utils.service';
import {NotificationService} from '@modules/shared/services/notification.service';
export enum ListLayout {
Table = 'TABLE',
Flex = 'FLEX'
}
@Component({
selector: 'service-logs-table',
templateUrl: './service-logs-table.component.html',
styleUrls: ['./service-logs-table.component.less']
})
export class ServiceLogsTableComponent extends LogsTableComponent implements AfterViewChecked {
constructor(
private logsContainer: LogsContainerService,
private utils: UtilsService,
private cdRef: ChangeDetectorRef,
private notificationService: NotificationService
) {
super();
}
ngAfterViewChecked() {
this.checkListLayout();
this.cdRef.detectChanges();
}
/**
* The element reference is used to check if the table is broken or not.
*/
@ViewChild('tableListEl', {
read: ElementRef
})
private tableListElRef: ElementRef;
/**
* The element reference is used to check if the table is broken or not.
*/
@ViewChild('tableWrapperEl', {
read: ElementRef
})
private tableWrapperElRef: ElementRef;
/**
* We only show the labels in flex layout when this property is TRUE.
* @type {boolean}
*/
@Input()
showLabels: boolean = false;
/**
* The minimum width for the log message column. It is used when we check if the layout is broken or not.
* @type {number}
*/
@Input()
logMessageColumnMinWidth: number = 175;
/**
* We use this property in the broken table layout check process when the log message is displayed.
* @type {string}
*/
@Input()
logMessageColumnCssSelector: string = 'tbody tr td.log-message';
/**
* Set the layout for the list.
* It can be:
* 'TABLE': good for comparison, but it is not useful whe the user wants to display too much fields
* 'FLEX': flexible layout (with flex box) is good for display lot of column or display the log list on a relative
* narrow display.
* @type {Layout}
*/
@Input()
layout: ListLayout = ListLayout.Table;
readonly dateFormat: string = 'dddd, MMMM Do';
readonly timeFormat: string = 'h:mm:ss A';
private copyLog = (log: ServiceLog): void => {
if (document.queryCommandSupported('copy')) {
const text = log.log_message,
node = document.createElement('textarea');
node.value = text;
Object.assign(node.style, {
position: 'fixed',
top: '0',
left: '0',
width: '1px',
height: '1px',
border: 'none',
outline: 'none',
boxShadow: 'none',
backgroundColor: 'transparent',
padding: '0'
});
document.body.appendChild(node);
node.select();
if (document.queryCommandEnabled('copy')) {
document.execCommand('copy');
this.notificationService.addNotification({
type: 'success',
title: 'logs.copy.title',
message: 'logs.copy.success'
});
} else {
this.notificationService.addNotification({
type: 'success',
title: 'logs.copy.title',
message: 'logs.copy.failed'
});
}
document.body.removeChild(node);
} else {
this.notificationService.addNotification({
type: 'success',
title: 'logs.copy.title',
message: 'logs.copy.notSupported'
});
}
};
private openLog = (log: ServiceLog): void => {
this.logsContainer.openServiceLog(log);
};
private openContext = (log: ServiceLog): void => {
this.logsContainer.loadLogContext(log.id, log.host, log.type);
};
readonly logActions = [
{
label: 'logs.copy',
iconClass: 'fa fa-files-o',
onSelect: this.copyLog
},
{
label: 'logs.open',
iconClass: 'fa fa-external-link',
onSelect: this.openLog
},
{
label: 'logs.context',
iconClass: 'fa fa-crosshairs',
onSelect: this.openContext
}
];
readonly customStyledColumns: string[] = ['level', 'type', 'logtime', 'log_message', 'path'];
private readonly messageFilterParameterName: string = 'log_message';
private readonly logsType: string = 'serviceLogs';
private selectedText: string = '';
/**
* This is a private flag to store the table layout check result. It is used to show user notifications about
* non-visible information.
* @type {boolean}
*/
private tooManyColumnsSelected: boolean = false;
get contextMenuItems(): ListItem[] {
return this.logsContainer.queryContextMenuItems;
}
get timeZone(): string {
return this.logsContainer.timeZone;
}
get filters(): any {
return this.logsContainer.filters;
}
get logsTypeMapObject(): object {
return this.logsContainer.logsTypeMap.serviceLogs;
}
get isContextMenuDisplayed(): boolean {
return Boolean(this.selectedText);
};
/**
* 'left' CSS property value for context menu dropdown
* @type {number}
*/
contextMenuLeft: number = 0;
/**
* 'top' CSS property value for context menu dropdown
* @type {number}
*/
contextMenuTop: number = 0;
isDifferentDates(dateA, dateB): boolean {
return this.utils.isDifferentDates(dateA, dateB, this.timeZone);
}
openMessageContextMenu(event: MouseEvent): void {
const selectedText = getSelection().toString();
if (selectedText) {
this.contextMenuLeft = event.clientX;
this.contextMenuTop = event.clientY;
this.selectedText = selectedText;
event.preventDefault();
}
}
updateQuery(event: ListItem): void {
this.logsContainer.queryParameterAdd.next({
name: this.messageFilterParameterName,
value: this.selectedText,
isExclude: event.value
});
}
/**
* Handle the event when the contextual menu component hide itself.
*/
onContextMenuDismiss(): void {
this.selectedText = '';
};
/**
* The goal is to check if the log message column is readable or not. Doing this by checking if it is displayed or not
* and by checking the current width and comparing with the minimum configured width.
* @returns {boolean}
*/
isLogMessageVisible(): boolean {
let visible:boolean = this.isColumnDisplayed('log_message');
if (this.logs.length && visible && this.layout === ListLayout.Table) {
const tableElement: HTMLElement = this.tableListElRef.nativeElement;
const lastTdElement = (tableElement && <HTMLElement>tableElement.querySelectorAll(this.logMessageColumnCssSelector)[0]) || undefined;
const minWidth = parseFloat(window.getComputedStyle(lastTdElement).minWidth) || this.logMessageColumnMinWidth;
const lastTdElementInfo = lastTdElement.getBoundingClientRect();
visible = lastTdElementInfo.width >= minWidth;
}
return visible;
}
/**
* Check if the log list (table) fits its container. The goal is to decide if the layout is broken or not.
* @returns {boolean}
*/
isLogListFitToTheContainer(): boolean {
let result = this.layout === ListLayout.Flex;
if (!result) {
const tableElement: HTMLElement = this.tableListElRef.nativeElement;
const tableElementInfo = tableElement.getBoundingClientRect();
const wrapperElement: HTMLElement = this.tableWrapperElRef.nativeElement;
const wrapperElementInfo = wrapperElement.getBoundingClientRect();
result = wrapperElementInfo.width >= tableElementInfo.width;
}
return result;
}
/**
* The goal of this function is to check either the log message column is readable if displayed or the all table
* columns are visible otherwise.
*/
private checkListLayout(): void {
this.tooManyColumnsSelected = this.isColumnDisplayed('log_message') ? !this.isLogMessageVisible() : !this.isLogListFitToTheContainer();
}
/**
* The goal is to enable the layout change to the user so that he/she can decide which view is more readable.
* @param {Layout} layout
*/
public setLayout(layout: ListLayout): void {
this.layout = layout;
}
/**
* Find the label for the given field in the @columns ListItem array
* @param {string} field
* @returns {string}
*/
private getLabelForField(field: string): string {
const column: ListItem = this.columns.find(currentColumn => currentColumn.value === field);
return column && (column.label || column.value);
}
/**
* Toggle the true/false value of the showLabels property. The goal is to show/hide the labels in the flex box layout,
* so that the user can decide if he/she wants to see the labels and lost some space.
*/
private toggleShowLabels(): void {
this.showLabels = !this.showLabels;
}
updateSelectedColumns(columns: string[]): void {
this.logsContainer.updateSelectedColumns(columns, this.logsType);
}
}