2025-02-27 18:31:55 +08:00
|
|
|
import { css } from '@emotion/css';
|
2025-02-05 01:40:17 +08:00
|
|
|
import { debounce } from 'lodash';
|
2025-04-04 20:53:12 +08:00
|
|
|
import { Grammar } from 'prismjs';
|
2025-06-10 17:59:01 +08:00
|
|
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, MouseEvent } from 'react';
|
2025-02-14 19:52:34 +08:00
|
|
|
import { VariableSizeList } from 'react-window';
|
2025-02-05 01:40:17 +08:00
|
|
|
|
2025-02-27 18:31:55 +08:00
|
|
|
import {
|
|
|
|
AbsoluteTimeRange,
|
|
|
|
CoreApp,
|
2025-05-21 01:28:35 +08:00
|
|
|
DataFrame,
|
2025-02-27 18:31:55 +08:00
|
|
|
EventBus,
|
2025-04-04 20:53:12 +08:00
|
|
|
EventBusSrv,
|
2025-06-10 17:59:01 +08:00
|
|
|
GrafanaTheme2,
|
2025-04-04 20:53:12 +08:00
|
|
|
LogLevel,
|
2025-02-27 18:31:55 +08:00
|
|
|
LogRowModel,
|
2025-04-04 20:53:12 +08:00
|
|
|
LogsDedupStrategy,
|
2025-04-07 22:38:55 +08:00
|
|
|
LogsMetaItem,
|
2025-02-27 18:31:55 +08:00
|
|
|
LogsSortOrder,
|
2025-04-07 22:38:55 +08:00
|
|
|
store,
|
2025-02-27 18:31:55 +08:00
|
|
|
TimeRange,
|
|
|
|
} from '@grafana/data';
|
2025-06-10 17:59:01 +08:00
|
|
|
import { Trans, useTranslate } from '@grafana/i18n';
|
|
|
|
import { ConfirmModal, Icon, PopoverContent, useTheme2 } from '@grafana/ui';
|
|
|
|
import { PopoverMenu } from 'app/features/explore/Logs/PopoverMenu';
|
2025-04-15 22:08:29 +08:00
|
|
|
import { GetFieldLinksFn } from 'app/plugins/panel/logs/types';
|
2025-02-05 01:40:17 +08:00
|
|
|
|
2025-02-14 19:52:34 +08:00
|
|
|
import { InfiniteScroll } from './InfiniteScroll';
|
2025-02-27 18:31:55 +08:00
|
|
|
import { getGridTemplateColumns } from './LogLine';
|
2025-05-21 01:28:35 +08:00
|
|
|
import { LogLineDetails } from './LogLineDetails';
|
2025-06-01 20:29:49 +08:00
|
|
|
import { GetRowContextQueryFn, LogLineMenuCustomItem } from './LogLineMenu';
|
2025-04-04 20:53:12 +08:00
|
|
|
import { LogListContextProvider, LogListState, useLogListContext } from './LogListContext';
|
|
|
|
import { LogListControls } from './LogListControls';
|
2025-03-26 19:48:26 +08:00
|
|
|
import { preProcessLogs, LogListModel } from './processing';
|
2025-06-10 17:59:01 +08:00
|
|
|
import { usePopoverMenu } from './usePopoverMenu';
|
2025-02-05 01:40:17 +08:00
|
|
|
import {
|
2025-03-26 19:48:26 +08:00
|
|
|
calculateFieldDimensions,
|
2025-02-05 01:40:17 +08:00
|
|
|
getLogLineSize,
|
|
|
|
init as initVirtualization,
|
2025-03-26 19:48:26 +08:00
|
|
|
LogFieldDimension,
|
2025-02-05 01:40:17 +08:00
|
|
|
resetLogLineSizes,
|
|
|
|
ScrollToLogsEvent,
|
|
|
|
storeLogLineSize,
|
|
|
|
} from './virtualization';
|
|
|
|
|
2025-05-21 01:28:35 +08:00
|
|
|
export interface Props {
|
2025-02-05 01:40:17 +08:00
|
|
|
app: CoreApp;
|
|
|
|
containerElement: HTMLDivElement;
|
2025-04-04 20:53:12 +08:00
|
|
|
dedupStrategy: LogsDedupStrategy;
|
2025-02-27 18:31:55 +08:00
|
|
|
displayedFields: string[];
|
2025-05-21 01:28:35 +08:00
|
|
|
enableLogDetails: boolean;
|
2025-04-04 20:53:12 +08:00
|
|
|
eventBus?: EventBus;
|
|
|
|
filterLevels?: LogLevel[];
|
2025-06-10 17:59:01 +08:00
|
|
|
fontSize?: LogListFontSize;
|
2025-02-27 18:31:55 +08:00
|
|
|
getFieldLinks?: GetFieldLinksFn;
|
2025-02-28 00:34:02 +08:00
|
|
|
getRowContextQuery?: GetRowContextQueryFn;
|
2025-04-04 20:53:12 +08:00
|
|
|
grammar?: Grammar;
|
2025-02-14 19:52:34 +08:00
|
|
|
initialScrollPosition?: 'top' | 'bottom';
|
2025-05-21 01:28:35 +08:00
|
|
|
isLabelFilterActive?: (key: string, value: string, refId?: string) => Promise<boolean>;
|
2025-05-07 06:26:54 +08:00
|
|
|
loading?: boolean;
|
2025-02-14 19:52:34 +08:00
|
|
|
loadMore?: (range: AbsoluteTimeRange) => void;
|
2025-06-01 20:29:49 +08:00
|
|
|
logLineMenuCustomItems?: LogLineMenuCustomItem[];
|
2025-04-04 20:53:12 +08:00
|
|
|
logOptionsStorageKey?: string;
|
2025-02-27 18:31:55 +08:00
|
|
|
logs: LogRowModel[];
|
2025-04-07 22:38:55 +08:00
|
|
|
logsMeta?: LogsMetaItem[];
|
2025-02-28 00:34:02 +08:00
|
|
|
logSupportsContext?: (row: LogRowModel) => boolean;
|
2025-05-21 01:28:35 +08:00
|
|
|
onClickFilterLabel?: (key: string, value: string, frame?: DataFrame) => void;
|
|
|
|
onClickFilterOutLabel?: (key: string, value: string, frame?: DataFrame) => void;
|
|
|
|
onClickFilterString?: (value: string, refId?: string) => void;
|
|
|
|
onClickFilterOutString?: (value: string, refId?: string) => void;
|
|
|
|
onClickShowField?: (key: string) => void;
|
|
|
|
onClickHideField?: (key: string) => void;
|
2025-04-04 20:53:12 +08:00
|
|
|
onLogOptionsChange?: (option: keyof LogListControlOptions, value: string | boolean | string[]) => void;
|
|
|
|
onLogLineHover?: (row?: LogRowModel) => void;
|
2025-02-28 00:34:02 +08:00
|
|
|
onPermalinkClick?: (row: LogRowModel) => Promise<void>;
|
|
|
|
onPinLine?: (row: LogRowModel) => void;
|
|
|
|
onOpenContext?: (row: LogRowModel, onClose: () => void) => void;
|
|
|
|
onUnpinLine?: (row: LogRowModel) => void;
|
2025-05-23 02:12:06 +08:00
|
|
|
permalinkedLogId?: string;
|
2025-02-28 00:34:02 +08:00
|
|
|
pinLineButtonTooltipTitle?: PopoverContent;
|
|
|
|
pinnedLogs?: string[];
|
2025-04-04 20:53:12 +08:00
|
|
|
showControls: boolean;
|
2025-02-05 01:40:17 +08:00
|
|
|
showTime: boolean;
|
|
|
|
sortOrder: LogsSortOrder;
|
2025-02-14 19:52:34 +08:00
|
|
|
timeRange: TimeRange;
|
2025-02-05 01:40:17 +08:00
|
|
|
timeZone: string;
|
2025-04-04 20:53:12 +08:00
|
|
|
syntaxHighlighting?: boolean;
|
2025-02-05 01:40:17 +08:00
|
|
|
wrapLogMessage: boolean;
|
|
|
|
}
|
|
|
|
|
2025-06-10 17:59:01 +08:00
|
|
|
export type LogListFontSize = 'default' | 'small';
|
|
|
|
|
2025-04-04 20:53:12 +08:00
|
|
|
export type LogListControlOptions = LogListState;
|
|
|
|
|
|
|
|
type LogListComponentProps = Omit<
|
|
|
|
Props,
|
2025-05-21 01:28:35 +08:00
|
|
|
| 'app'
|
|
|
|
| 'dedupStrategy'
|
|
|
|
| 'displayedFields'
|
|
|
|
| 'enableLogDetails'
|
2025-05-23 02:12:06 +08:00
|
|
|
| 'permalinkedLogId'
|
2025-05-21 01:28:35 +08:00
|
|
|
| 'showTime'
|
|
|
|
| 'sortOrder'
|
|
|
|
| 'syntaxHighlighting'
|
|
|
|
| 'wrapLogMessage'
|
2025-04-04 20:53:12 +08:00
|
|
|
>;
|
|
|
|
|
2025-02-05 01:40:17 +08:00
|
|
|
export const LogList = ({
|
|
|
|
app,
|
2025-04-04 20:53:12 +08:00
|
|
|
displayedFields,
|
2025-02-05 01:40:17 +08:00
|
|
|
containerElement,
|
2025-04-04 20:53:12 +08:00
|
|
|
dedupStrategy,
|
2025-05-21 01:28:35 +08:00
|
|
|
enableLogDetails,
|
2025-02-05 01:40:17 +08:00
|
|
|
eventBus,
|
2025-04-04 20:53:12 +08:00
|
|
|
filterLevels,
|
2025-06-10 17:59:01 +08:00
|
|
|
logOptionsStorageKey,
|
|
|
|
fontSize = logOptionsStorageKey ? (store.get(`${logOptionsStorageKey}.fontSize`) ?? 'default') : 'default',
|
2025-02-27 18:31:55 +08:00
|
|
|
getFieldLinks,
|
2025-04-04 20:53:12 +08:00
|
|
|
getRowContextQuery,
|
|
|
|
grammar,
|
2025-02-14 19:52:34 +08:00
|
|
|
initialScrollPosition = 'top',
|
2025-05-21 01:28:35 +08:00
|
|
|
isLabelFilterActive,
|
2025-05-07 06:26:54 +08:00
|
|
|
loading,
|
2025-02-14 19:52:34 +08:00
|
|
|
loadMore,
|
2025-06-01 20:29:49 +08:00
|
|
|
logLineMenuCustomItems,
|
2025-02-14 19:52:34 +08:00
|
|
|
logs,
|
2025-04-07 22:38:55 +08:00
|
|
|
logsMeta,
|
2025-04-04 20:53:12 +08:00
|
|
|
logSupportsContext,
|
2025-05-21 01:28:35 +08:00
|
|
|
onClickFilterLabel,
|
|
|
|
onClickFilterOutLabel,
|
|
|
|
onClickFilterString,
|
|
|
|
onClickFilterOutString,
|
|
|
|
onClickShowField,
|
|
|
|
onClickHideField,
|
2025-04-04 20:53:12 +08:00
|
|
|
onLogOptionsChange,
|
|
|
|
onLogLineHover,
|
|
|
|
onPermalinkClick,
|
|
|
|
onPinLine,
|
|
|
|
onOpenContext,
|
|
|
|
onUnpinLine,
|
2025-05-23 02:12:06 +08:00
|
|
|
permalinkedLogId,
|
2025-04-04 20:53:12 +08:00
|
|
|
pinLineButtonTooltipTitle,
|
|
|
|
pinnedLogs,
|
|
|
|
showControls,
|
2025-02-05 01:40:17 +08:00
|
|
|
showTime,
|
|
|
|
sortOrder,
|
2025-04-07 22:38:55 +08:00
|
|
|
syntaxHighlighting = logOptionsStorageKey ? store.getBool(`${logOptionsStorageKey}.syntaxHighlighting`, true) : true,
|
2025-02-14 19:52:34 +08:00
|
|
|
timeRange,
|
2025-02-05 01:40:17 +08:00
|
|
|
timeZone,
|
|
|
|
wrapLogMessage,
|
|
|
|
}: Props) => {
|
2025-04-04 20:53:12 +08:00
|
|
|
return (
|
|
|
|
<LogListContextProvider
|
|
|
|
app={app}
|
2025-05-21 01:28:35 +08:00
|
|
|
containerElement={containerElement}
|
2025-04-04 20:53:12 +08:00
|
|
|
dedupStrategy={dedupStrategy}
|
|
|
|
displayedFields={displayedFields}
|
2025-05-21 01:28:35 +08:00
|
|
|
enableLogDetails={enableLogDetails}
|
2025-04-04 20:53:12 +08:00
|
|
|
filterLevels={filterLevels}
|
2025-06-10 17:59:01 +08:00
|
|
|
fontSize={fontSize}
|
2025-04-04 20:53:12 +08:00
|
|
|
getRowContextQuery={getRowContextQuery}
|
2025-05-21 01:28:35 +08:00
|
|
|
isLabelFilterActive={isLabelFilterActive}
|
2025-04-07 22:38:55 +08:00
|
|
|
logs={logs}
|
|
|
|
logsMeta={logsMeta}
|
2025-06-01 20:29:49 +08:00
|
|
|
logLineMenuCustomItems={logLineMenuCustomItems}
|
2025-04-04 20:53:12 +08:00
|
|
|
logOptionsStorageKey={logOptionsStorageKey}
|
|
|
|
logSupportsContext={logSupportsContext}
|
2025-05-21 01:28:35 +08:00
|
|
|
onClickFilterLabel={onClickFilterLabel}
|
|
|
|
onClickFilterOutLabel={onClickFilterOutLabel}
|
|
|
|
onClickFilterString={onClickFilterString}
|
|
|
|
onClickFilterOutString={onClickFilterOutString}
|
|
|
|
onClickShowField={onClickShowField}
|
|
|
|
onClickHideField={onClickHideField}
|
2025-04-04 20:53:12 +08:00
|
|
|
onLogOptionsChange={onLogOptionsChange}
|
|
|
|
onLogLineHover={onLogLineHover}
|
|
|
|
onPermalinkClick={onPermalinkClick}
|
|
|
|
onPinLine={onPinLine}
|
|
|
|
onOpenContext={onOpenContext}
|
|
|
|
onUnpinLine={onUnpinLine}
|
2025-05-23 02:12:06 +08:00
|
|
|
permalinkedLogId={permalinkedLogId}
|
2025-04-04 20:53:12 +08:00
|
|
|
pinLineButtonTooltipTitle={pinLineButtonTooltipTitle}
|
|
|
|
pinnedLogs={pinnedLogs}
|
|
|
|
showControls={showControls}
|
|
|
|
showTime={showTime}
|
|
|
|
sortOrder={sortOrder}
|
|
|
|
syntaxHighlighting={syntaxHighlighting}
|
|
|
|
wrapLogMessage={wrapLogMessage}
|
|
|
|
>
|
|
|
|
<LogListComponent
|
|
|
|
containerElement={containerElement}
|
|
|
|
eventBus={eventBus}
|
|
|
|
getFieldLinks={getFieldLinks}
|
|
|
|
grammar={grammar}
|
|
|
|
initialScrollPosition={initialScrollPosition}
|
2025-05-07 06:26:54 +08:00
|
|
|
loading={loading}
|
2025-04-04 20:53:12 +08:00
|
|
|
loadMore={loadMore}
|
|
|
|
logs={logs}
|
|
|
|
showControls={showControls}
|
|
|
|
timeRange={timeRange}
|
|
|
|
timeZone={timeZone}
|
|
|
|
/>
|
|
|
|
</LogListContextProvider>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const LogListComponent = ({
|
|
|
|
containerElement,
|
|
|
|
eventBus = new EventBusSrv(),
|
|
|
|
getFieldLinks,
|
|
|
|
grammar,
|
|
|
|
initialScrollPosition = 'top',
|
2025-05-07 06:26:54 +08:00
|
|
|
loading,
|
2025-04-04 20:53:12 +08:00
|
|
|
loadMore,
|
|
|
|
logs,
|
|
|
|
showControls,
|
|
|
|
timeRange,
|
|
|
|
timeZone,
|
|
|
|
}: LogListComponentProps) => {
|
2025-05-21 01:28:35 +08:00
|
|
|
const {
|
|
|
|
app,
|
|
|
|
displayedFields,
|
|
|
|
dedupStrategy,
|
|
|
|
filterLevels,
|
2025-06-10 17:59:01 +08:00
|
|
|
fontSize,
|
2025-05-21 01:28:35 +08:00
|
|
|
forceEscape,
|
2025-06-01 20:29:49 +08:00
|
|
|
hasLogsWithErrors,
|
|
|
|
hasSampledLogs,
|
2025-06-10 17:59:01 +08:00
|
|
|
onClickFilterString,
|
|
|
|
onClickFilterOutString,
|
2025-05-23 02:12:06 +08:00
|
|
|
permalinkedLogId,
|
2025-05-21 01:28:35 +08:00
|
|
|
showDetails,
|
|
|
|
showTime,
|
|
|
|
sortOrder,
|
|
|
|
toggleDetails,
|
|
|
|
wrapLogMessage,
|
|
|
|
} = useLogListContext();
|
2025-02-14 19:52:34 +08:00
|
|
|
const [processedLogs, setProcessedLogs] = useState<LogListModel[]>([]);
|
2025-02-05 01:40:17 +08:00
|
|
|
const [listHeight, setListHeight] = useState(
|
2025-05-23 02:12:06 +08:00
|
|
|
app === CoreApp.Explore
|
|
|
|
? Math.max(window.innerHeight * 0.8, containerElement.clientHeight)
|
|
|
|
: containerElement.clientHeight
|
2025-02-05 01:40:17 +08:00
|
|
|
);
|
|
|
|
const theme = useTheme2();
|
|
|
|
const listRef = useRef<VariableSizeList | null>(null);
|
|
|
|
const widthRef = useRef(containerElement.clientWidth);
|
2025-05-21 01:28:35 +08:00
|
|
|
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
2025-02-14 19:52:34 +08:00
|
|
|
const scrollRef = useRef<HTMLDivElement | null>(null);
|
2025-02-27 18:31:55 +08:00
|
|
|
const dimensions = useMemo(
|
|
|
|
() => (wrapLogMessage ? [] : calculateFieldDimensions(processedLogs, displayedFields)),
|
|
|
|
[displayedFields, processedLogs, wrapLogMessage]
|
|
|
|
);
|
2025-06-10 17:59:01 +08:00
|
|
|
const styles = getStyles(dimensions, { showTime }, theme);
|
2025-05-21 01:28:35 +08:00
|
|
|
const widthContainer = wrapperRef.current ?? containerElement;
|
2025-06-10 17:59:01 +08:00
|
|
|
const {
|
|
|
|
closePopoverMenu,
|
|
|
|
handleTextSelection,
|
|
|
|
onDisableCancel,
|
|
|
|
onDisableConfirm,
|
|
|
|
onDisablePopoverMenu,
|
|
|
|
popoverState,
|
|
|
|
showDisablePopoverOptions,
|
|
|
|
} = usePopoverMenu(wrapperRef.current);
|
|
|
|
const { t } = useTranslate();
|
2025-05-21 01:28:35 +08:00
|
|
|
|
|
|
|
const debouncedResetAfterIndex = useMemo(() => {
|
|
|
|
return debounce((index: number) => {
|
|
|
|
listRef.current?.resetAfterIndex(index);
|
|
|
|
overflowIndexRef.current = Infinity;
|
|
|
|
}, 25);
|
|
|
|
}, []);
|
2025-02-05 01:40:17 +08:00
|
|
|
|
|
|
|
useEffect(() => {
|
2025-06-10 17:59:01 +08:00
|
|
|
initVirtualization(theme, fontSize);
|
|
|
|
}, [fontSize, theme]);
|
2025-02-05 01:40:17 +08:00
|
|
|
|
|
|
|
useEffect(() => {
|
2025-02-14 19:52:34 +08:00
|
|
|
const subscription = eventBus.subscribe(ScrollToLogsEvent, (e: ScrollToLogsEvent) =>
|
|
|
|
handleScrollToEvent(e, logs.length, listRef.current)
|
|
|
|
);
|
2025-02-05 01:40:17 +08:00
|
|
|
return () => subscription.unsubscribe();
|
2025-02-14 19:52:34 +08:00
|
|
|
}, [eventBus, logs.length]);
|
2025-02-05 01:40:17 +08:00
|
|
|
|
|
|
|
useEffect(() => {
|
2025-05-07 06:26:54 +08:00
|
|
|
if (loading) {
|
|
|
|
return;
|
|
|
|
}
|
2025-04-11 02:47:17 +08:00
|
|
|
setProcessedLogs(
|
|
|
|
preProcessLogs(logs, { getFieldLinks, escape: forceEscape ?? false, order: sortOrder, timeZone }, grammar)
|
|
|
|
);
|
2025-05-07 06:26:54 +08:00
|
|
|
resetLogLineSizes();
|
|
|
|
listRef.current?.resetAfterIndex(0);
|
|
|
|
}, [forceEscape, getFieldLinks, grammar, loading, logs, sortOrder, timeZone]);
|
2025-03-26 19:48:26 +08:00
|
|
|
|
|
|
|
useEffect(() => {
|
2025-02-05 01:40:17 +08:00
|
|
|
listRef.current?.resetAfterIndex(0);
|
2025-06-01 20:29:49 +08:00
|
|
|
}, [wrapLogMessage, showDetails, displayedFields, dedupStrategy]);
|
2025-02-05 01:40:17 +08:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
const handleResize = debounce(() => {
|
2025-05-23 02:12:06 +08:00
|
|
|
setListHeight(
|
|
|
|
app === CoreApp.Explore
|
|
|
|
? Math.max(window.innerHeight * 0.8, containerElement.clientHeight)
|
|
|
|
: containerElement.clientHeight
|
|
|
|
);
|
2025-02-05 01:40:17 +08:00
|
|
|
}, 50);
|
|
|
|
window.addEventListener('resize', handleResize);
|
|
|
|
handleResize();
|
|
|
|
return () => {
|
|
|
|
window.removeEventListener('resize', handleResize);
|
|
|
|
};
|
|
|
|
}, [app, containerElement.clientHeight]);
|
|
|
|
|
|
|
|
useLayoutEffect(() => {
|
2025-05-21 01:28:35 +08:00
|
|
|
if (widthRef.current === widthContainer.clientWidth) {
|
2025-02-05 01:40:17 +08:00
|
|
|
return;
|
|
|
|
}
|
2025-05-21 01:28:35 +08:00
|
|
|
widthRef.current = widthContainer.clientWidth;
|
|
|
|
debouncedResetAfterIndex(0);
|
2025-02-05 01:40:17 +08:00
|
|
|
});
|
|
|
|
|
2025-05-21 01:28:35 +08:00
|
|
|
const overflowIndexRef = useRef(Infinity);
|
2025-02-05 01:40:17 +08:00
|
|
|
const handleOverflow = useCallback(
|
2025-05-07 06:26:54 +08:00
|
|
|
(index: number, id: string, height?: number) => {
|
2025-05-21 01:28:35 +08:00
|
|
|
if (height !== undefined) {
|
2025-06-10 17:59:01 +08:00
|
|
|
storeLogLineSize(id, widthContainer, height, fontSize);
|
|
|
|
}
|
|
|
|
if (index === overflowIndexRef.current) {
|
|
|
|
return;
|
2025-02-05 01:40:17 +08:00
|
|
|
}
|
2025-05-21 01:28:35 +08:00
|
|
|
overflowIndexRef.current = index < overflowIndexRef.current ? index : overflowIndexRef.current;
|
|
|
|
debouncedResetAfterIndex(overflowIndexRef.current);
|
2025-02-05 01:40:17 +08:00
|
|
|
},
|
2025-06-10 17:59:01 +08:00
|
|
|
[debouncedResetAfterIndex, fontSize, widthContainer]
|
2025-02-05 01:40:17 +08:00
|
|
|
);
|
|
|
|
|
2025-02-14 19:52:34 +08:00
|
|
|
const handleScrollPosition = useCallback(() => {
|
2025-05-23 02:12:06 +08:00
|
|
|
if (permalinkedLogId) {
|
|
|
|
const index = processedLogs.findIndex((log) => log.uid === permalinkedLogId);
|
|
|
|
if (index >= 0) {
|
|
|
|
listRef.current?.scrollToItem(index, 'start');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
listRef.current?.scrollToItem(initialScrollPosition === 'top' ? 0 : processedLogs.length - 1);
|
|
|
|
}, [initialScrollPosition, permalinkedLogId, processedLogs]);
|
2025-02-05 01:40:17 +08:00
|
|
|
|
|
|
|
if (!containerElement || listHeight == null) {
|
|
|
|
// Wait for container to be rendered
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2025-05-21 01:28:35 +08:00
|
|
|
const handleLogLineClick = useCallback(
|
2025-06-10 17:59:01 +08:00
|
|
|
(e: MouseEvent<HTMLElement>, log: LogListModel) => {
|
|
|
|
if (handleTextSelection(e, log)) {
|
|
|
|
// Event handled by the parent.
|
2025-05-23 02:12:06 +08:00
|
|
|
return;
|
|
|
|
}
|
2025-05-21 01:28:35 +08:00
|
|
|
toggleDetails(log);
|
|
|
|
},
|
2025-06-10 17:59:01 +08:00
|
|
|
[handleTextSelection, toggleDetails]
|
2025-05-21 01:28:35 +08:00
|
|
|
);
|
|
|
|
|
|
|
|
const handleLogDetailsResize = useCallback(() => {
|
|
|
|
debouncedResetAfterIndex(0);
|
|
|
|
}, [debouncedResetAfterIndex]);
|
|
|
|
|
2025-04-04 20:53:12 +08:00
|
|
|
const filteredLogs = useMemo(
|
|
|
|
() =>
|
|
|
|
filterLevels.length === 0 ? processedLogs : processedLogs.filter((log) => filterLevels.includes(log.logLevel)),
|
|
|
|
[filterLevels, processedLogs]
|
|
|
|
);
|
|
|
|
|
2025-02-05 01:40:17 +08:00
|
|
|
return (
|
2025-04-04 20:53:12 +08:00
|
|
|
<div className={styles.logListContainer}>
|
2025-05-21 01:28:35 +08:00
|
|
|
<div className={styles.logListWrapper} ref={wrapperRef}>
|
2025-06-10 17:59:01 +08:00
|
|
|
{popoverState.selection && popoverState.selectedRow && (
|
|
|
|
<PopoverMenu
|
|
|
|
close={closePopoverMenu}
|
|
|
|
row={popoverState.selectedRow}
|
|
|
|
selection={popoverState.selection}
|
|
|
|
{...popoverState.popoverMenuCoordinates}
|
|
|
|
onClickFilterString={onClickFilterString}
|
|
|
|
onClickFilterOutString={onClickFilterOutString}
|
|
|
|
onDisable={onDisablePopoverMenu}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
{showDisablePopoverOptions && (
|
|
|
|
<ConfirmModal
|
|
|
|
isOpen
|
|
|
|
title={t('logs.log-rows.disable-popover.title', 'Disable menu')}
|
|
|
|
body={
|
|
|
|
<>
|
|
|
|
<Trans i18nKey="logs.log-rows.disable-popover.message">
|
|
|
|
You are about to disable the logs filter menu. To re-enable it, select text in a log line while
|
|
|
|
holding the alt key.
|
|
|
|
</Trans>
|
|
|
|
<div className={styles.shortcut}>
|
|
|
|
<Icon name="keyboard" />
|
|
|
|
<Trans i18nKey="logs.log-rows.disable-popover-message.shortcut">alt+select to enable again</Trans>
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
}
|
|
|
|
confirmText={t('logs.log-rows.disable-popover.confirm', 'Confirm')}
|
|
|
|
icon="exclamation-triangle"
|
|
|
|
onConfirm={onDisableConfirm}
|
|
|
|
onDismiss={onDisableCancel}
|
|
|
|
/>
|
|
|
|
)}
|
2025-05-21 01:28:35 +08:00
|
|
|
<InfiniteScroll
|
|
|
|
displayedFields={displayedFields}
|
|
|
|
handleOverflow={handleOverflow}
|
|
|
|
logs={filteredLogs}
|
|
|
|
loadMore={loadMore}
|
|
|
|
onClick={handleLogLineClick}
|
|
|
|
scrollElement={scrollRef.current}
|
|
|
|
showTime={showTime}
|
|
|
|
sortOrder={sortOrder}
|
|
|
|
timeRange={timeRange}
|
|
|
|
timeZone={timeZone}
|
|
|
|
setInitialScrollPosition={handleScrollPosition}
|
|
|
|
wrapLogMessage={wrapLogMessage}
|
|
|
|
>
|
|
|
|
{({ getItemKey, itemCount, onItemsRendered, Renderer }) => (
|
|
|
|
<VariableSizeList
|
|
|
|
className={styles.logList}
|
|
|
|
height={listHeight}
|
|
|
|
itemCount={itemCount}
|
|
|
|
itemSize={getLogLineSize.bind(null, filteredLogs, widthContainer, displayedFields, {
|
2025-06-10 17:59:01 +08:00
|
|
|
fontSize,
|
2025-06-01 20:29:49 +08:00
|
|
|
hasLogsWithErrors,
|
|
|
|
hasSampledLogs,
|
2025-05-21 01:28:35 +08:00
|
|
|
showDuplicates: dedupStrategy !== LogsDedupStrategy.none,
|
|
|
|
showTime,
|
|
|
|
wrap: wrapLogMessage,
|
|
|
|
})}
|
|
|
|
itemKey={getItemKey}
|
|
|
|
layout="vertical"
|
|
|
|
onItemsRendered={onItemsRendered}
|
|
|
|
outerRef={scrollRef}
|
2025-06-01 20:29:49 +08:00
|
|
|
overscanCount={5}
|
2025-05-21 01:28:35 +08:00
|
|
|
ref={listRef}
|
|
|
|
style={{ overflowY: 'scroll' }}
|
|
|
|
width="100%"
|
|
|
|
>
|
|
|
|
{Renderer}
|
|
|
|
</VariableSizeList>
|
|
|
|
)}
|
|
|
|
</InfiniteScroll>
|
|
|
|
</div>
|
|
|
|
{showDetails.length > 0 && (
|
|
|
|
<LogLineDetails
|
|
|
|
containerElement={containerElement}
|
|
|
|
getFieldLinks={getFieldLinks}
|
|
|
|
logs={filteredLogs}
|
|
|
|
onResize={handleLogDetailsResize}
|
|
|
|
/>
|
|
|
|
)}
|
2025-04-04 20:53:12 +08:00
|
|
|
{showControls && <LogListControls eventBus={eventBus} />}
|
|
|
|
</div>
|
2025-02-05 01:40:17 +08:00
|
|
|
);
|
|
|
|
};
|
2025-02-14 19:52:34 +08:00
|
|
|
|
2025-06-10 17:59:01 +08:00
|
|
|
function getStyles(dimensions: LogFieldDimension[], { showTime }: { showTime: boolean }, theme: GrafanaTheme2) {
|
2025-02-27 18:31:55 +08:00
|
|
|
const columns = showTime ? dimensions : dimensions.filter((_, index) => index > 0);
|
|
|
|
return {
|
|
|
|
logList: css({
|
|
|
|
'& .unwrapped-log-line': {
|
|
|
|
display: 'grid',
|
|
|
|
gridTemplateColumns: getGridTemplateColumns(columns),
|
|
|
|
},
|
|
|
|
}),
|
2025-04-04 20:53:12 +08:00
|
|
|
logListContainer: css({
|
|
|
|
display: 'flex',
|
2025-06-10 17:59:01 +08:00
|
|
|
// Minimum width to prevent rendering issues and a sausage-like logs panel.
|
|
|
|
minWidth: theme.spacing(35),
|
2025-04-04 20:53:12 +08:00
|
|
|
}),
|
2025-05-21 01:28:35 +08:00
|
|
|
logListWrapper: css({
|
|
|
|
width: '100%',
|
2025-06-10 17:59:01 +08:00
|
|
|
position: 'relative',
|
|
|
|
}),
|
|
|
|
shortcut: css({
|
|
|
|
display: 'inline-flex',
|
|
|
|
alignItems: 'center',
|
|
|
|
gap: theme.spacing(1),
|
|
|
|
color: theme.colors.text.secondary,
|
|
|
|
opacity: 0.7,
|
|
|
|
fontSize: theme.typography.bodySmall.fontSize,
|
|
|
|
marginTop: theme.spacing(1),
|
2025-05-21 01:28:35 +08:00
|
|
|
}),
|
2025-02-27 18:31:55 +08:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2025-02-14 19:52:34 +08:00
|
|
|
function handleScrollToEvent(event: ScrollToLogsEvent, logsCount: number, list: VariableSizeList | null) {
|
|
|
|
if (event.payload.scrollTo === 'top') {
|
|
|
|
list?.scrollTo(0);
|
|
|
|
} else {
|
|
|
|
list?.scrollToItem(logsCount - 1);
|
|
|
|
}
|
|
|
|
}
|