2025-06-10 17:59:01 +08:00
|
|
|
import { debounce } from 'lodash';
|
2025-04-04 20:53:12 +08:00
|
|
|
import {
|
|
|
|
|
createContext,
|
|
|
|
|
Dispatch,
|
|
|
|
|
ReactNode,
|
|
|
|
|
SetStateAction,
|
|
|
|
|
useCallback,
|
|
|
|
|
useContext,
|
|
|
|
|
useEffect,
|
2025-06-01 20:29:49 +08:00
|
|
|
useMemo,
|
2025-04-04 20:53:12 +08:00
|
|
|
useState,
|
|
|
|
|
} from 'react';
|
2025-02-28 00:34:02 +08:00
|
|
|
|
2025-08-21 18:07:47 +08:00
|
|
|
import { createAssistantContextItem, OpenAssistantProps, useAssistant } from '@grafana/assistant';
|
2025-04-07 22:38:55 +08:00
|
|
|
import {
|
|
|
|
|
CoreApp,
|
2025-05-21 01:28:35 +08:00
|
|
|
DataFrame,
|
2025-04-07 22:38:55 +08:00
|
|
|
LogLevel,
|
|
|
|
|
LogRowModel,
|
|
|
|
|
LogsDedupStrategy,
|
|
|
|
|
LogsMetaItem,
|
|
|
|
|
LogsSortOrder,
|
|
|
|
|
shallowCompare,
|
|
|
|
|
store,
|
|
|
|
|
} from '@grafana/data';
|
2025-07-23 18:12:47 +08:00
|
|
|
import { t } from '@grafana/i18n';
|
2025-08-27 01:46:01 +08:00
|
|
|
import { config, getDataSourceSrv } from '@grafana/runtime';
|
2025-02-28 00:34:02 +08:00
|
|
|
import { PopoverContent } from '@grafana/ui';
|
|
|
|
|
|
2025-07-23 18:12:47 +08:00
|
|
|
import { checkLogsError, checkLogsSampled, downloadLogs as download, DownloadFormat } from '../../utils';
|
2025-07-16 23:42:14 +08:00
|
|
|
import { getDisplayedFieldsForLogs } from '../otel/formats';
|
2025-04-07 22:38:55 +08:00
|
|
|
|
2025-08-09 01:13:01 +08:00
|
|
|
import { LogLineTimestampResolution } from './LogLine';
|
2025-07-09 22:07:19 +08:00
|
|
|
import { LogLineDetailsMode } from './LogLineDetails';
|
2025-06-01 20:29:49 +08:00
|
|
|
import { GetRowContextQueryFn, LogLineMenuCustomItem } from './LogLineMenu';
|
2025-08-28 01:34:14 +08:00
|
|
|
import { LogListControlOptions, LogListFontSize } from './LogList';
|
2025-08-27 01:46:01 +08:00
|
|
|
import { reportInteractionOnce } from './analytics';
|
2025-05-21 01:28:35 +08:00
|
|
|
import { LogListModel } from './processing';
|
2025-08-19 03:06:31 +08:00
|
|
|
import { getScrollbarWidth, LOG_LIST_CONTROLS_WIDTH, LOG_LIST_MIN_WIDTH } from './virtualization';
|
2025-02-28 00:34:02 +08:00
|
|
|
|
2025-05-21 01:28:35 +08:00
|
|
|
export interface LogListContextData extends Omit<Props, 'containerElement' | 'logs' | 'logsMeta' | 'showControls'> {
|
|
|
|
|
closeDetails: () => void;
|
|
|
|
|
detailsDisplayed: (log: LogListModel) => boolean;
|
2025-07-09 22:07:19 +08:00
|
|
|
detailsMode: LogLineDetailsMode;
|
2025-05-21 01:28:35 +08:00
|
|
|
detailsWidth: number;
|
2025-04-07 22:38:55 +08:00
|
|
|
downloadLogs: (format: DownloadFormat) => void;
|
2025-05-21 01:28:35 +08:00
|
|
|
enableLogDetails: boolean;
|
2025-04-04 20:53:12 +08:00
|
|
|
filterLevels: LogLevel[];
|
2025-07-17 16:59:03 +08:00
|
|
|
forceEscape: boolean;
|
2025-06-01 20:29:49 +08:00
|
|
|
hasLogsWithErrors?: boolean;
|
|
|
|
|
hasSampledLogs?: boolean;
|
2025-07-17 16:59:03 +08:00
|
|
|
hasUnescapedContent: boolean;
|
2025-06-01 20:29:49 +08:00
|
|
|
logLineMenuCustomItems?: LogLineMenuCustomItem[];
|
2025-04-04 20:53:12 +08:00
|
|
|
setDedupStrategy: (dedupStrategy: LogsDedupStrategy) => void;
|
2025-07-09 22:07:19 +08:00
|
|
|
setDetailsMode: (mode: LogLineDetailsMode) => void;
|
2025-05-21 01:28:35 +08:00
|
|
|
setDetailsWidth: (width: number) => void;
|
2025-04-04 20:53:12 +08:00
|
|
|
setFilterLevels: (filterLevels: LogLevel[]) => void;
|
2025-06-10 17:59:01 +08:00
|
|
|
setFontSize: (size: LogListFontSize) => void;
|
2025-04-11 02:47:17 +08:00
|
|
|
setForceEscape: (forceEscape: boolean) => void;
|
2025-04-04 20:53:12 +08:00
|
|
|
setLogListState: Dispatch<SetStateAction<LogListState>>;
|
|
|
|
|
setPinnedLogs: (pinnedlogs: string[]) => void;
|
2025-04-07 22:38:55 +08:00
|
|
|
setPrettifyJSON: (prettifyJSON: boolean) => void;
|
2025-04-04 20:53:12 +08:00
|
|
|
setSyntaxHighlighting: (syntaxHighlighting: boolean) => void;
|
|
|
|
|
setShowTime: (showTime: boolean) => void;
|
2025-04-07 22:38:55 +08:00
|
|
|
setShowUniqueLabels: (showUniqueLabels: boolean) => void;
|
2025-04-04 20:53:12 +08:00
|
|
|
setSortOrder: (sortOrder: LogsSortOrder) => void;
|
2025-08-09 01:13:01 +08:00
|
|
|
setTimestampResolution: (format: LogLineTimestampResolution) => void;
|
2025-04-04 20:53:12 +08:00
|
|
|
setWrapLogMessage: (showTime: boolean) => void;
|
2025-05-21 01:28:35 +08:00
|
|
|
showDetails: LogListModel[];
|
2025-08-09 01:13:01 +08:00
|
|
|
timestampResolution: LogLineTimestampResolution;
|
2025-05-21 01:28:35 +08:00
|
|
|
toggleDetails: (log: LogListModel) => void;
|
2025-07-23 18:12:47 +08:00
|
|
|
isAssistantAvailable: boolean;
|
|
|
|
|
openAssistantByLog: ((log: LogListModel) => void) | undefined;
|
2025-02-28 00:34:02 +08:00
|
|
|
}
|
|
|
|
|
|
2025-04-04 20:53:12 +08:00
|
|
|
export const LogListContext = createContext<LogListContextData>({
|
|
|
|
|
app: CoreApp.Unknown,
|
2025-05-21 01:28:35 +08:00
|
|
|
closeDetails: () => {},
|
2025-04-04 20:53:12 +08:00
|
|
|
dedupStrategy: LogsDedupStrategy.none,
|
2025-05-21 01:28:35 +08:00
|
|
|
detailsDisplayed: () => false,
|
2025-07-09 22:07:19 +08:00
|
|
|
detailsMode: 'sidebar',
|
2025-05-21 01:28:35 +08:00
|
|
|
detailsWidth: 0,
|
2025-04-04 20:53:12 +08:00
|
|
|
displayedFields: [],
|
2025-04-07 22:38:55 +08:00
|
|
|
downloadLogs: () => {},
|
2025-05-21 01:28:35 +08:00
|
|
|
enableLogDetails: false,
|
2025-04-04 20:53:12 +08:00
|
|
|
filterLevels: [],
|
2025-07-17 16:59:03 +08:00
|
|
|
forceEscape: false,
|
2025-06-10 17:59:01 +08:00
|
|
|
fontSize: 'default',
|
2025-04-11 02:47:17 +08:00
|
|
|
hasUnescapedContent: false,
|
2025-07-18 19:13:51 +08:00
|
|
|
noInteractions: false,
|
2025-04-04 20:53:12 +08:00
|
|
|
setDedupStrategy: () => {},
|
2025-07-09 22:07:19 +08:00
|
|
|
setDetailsMode: () => {},
|
2025-05-21 01:28:35 +08:00
|
|
|
setDetailsWidth: () => {},
|
2025-04-04 20:53:12 +08:00
|
|
|
setFilterLevels: () => {},
|
2025-06-10 17:59:01 +08:00
|
|
|
setFontSize: () => {},
|
2025-04-11 02:47:17 +08:00
|
|
|
setForceEscape: () => {},
|
2025-04-04 20:53:12 +08:00
|
|
|
setLogListState: () => {},
|
|
|
|
|
setPinnedLogs: () => {},
|
2025-04-07 22:38:55 +08:00
|
|
|
setPrettifyJSON: () => {},
|
2025-04-04 20:53:12 +08:00
|
|
|
setShowTime: () => {},
|
2025-04-07 22:38:55 +08:00
|
|
|
setShowUniqueLabels: () => {},
|
2025-04-04 20:53:12 +08:00
|
|
|
setSortOrder: () => {},
|
|
|
|
|
setSyntaxHighlighting: () => {},
|
2025-08-09 01:13:01 +08:00
|
|
|
setTimestampResolution: () => {},
|
2025-04-04 20:53:12 +08:00
|
|
|
setWrapLogMessage: () => {},
|
2025-05-21 01:28:35 +08:00
|
|
|
showDetails: [],
|
2025-04-04 20:53:12 +08:00
|
|
|
showTime: true,
|
|
|
|
|
sortOrder: LogsSortOrder.Ascending,
|
|
|
|
|
syntaxHighlighting: true,
|
2025-08-09 01:13:01 +08:00
|
|
|
timestampResolution: 'ns',
|
2025-05-21 01:28:35 +08:00
|
|
|
toggleDetails: () => {},
|
2025-04-04 20:53:12 +08:00
|
|
|
wrapLogMessage: false,
|
2025-07-23 18:12:47 +08:00
|
|
|
isAssistantAvailable: false,
|
|
|
|
|
openAssistantByLog: () => {},
|
2025-04-04 20:53:12 +08:00
|
|
|
});
|
2025-02-28 00:34:02 +08:00
|
|
|
|
|
|
|
|
export const useLogListContextData = (key: keyof LogListContextData) => {
|
|
|
|
|
const data: LogListContextData = useContext(LogListContext);
|
|
|
|
|
return data[key];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const useLogListContext = (): LogListContextData => {
|
|
|
|
|
return useContext(LogListContext);
|
|
|
|
|
};
|
|
|
|
|
|
2025-05-23 02:12:06 +08:00
|
|
|
export const useLogIsPinned = (log: LogListModel) => {
|
2025-02-28 00:34:02 +08:00
|
|
|
const { pinnedLogs } = useContext(LogListContext);
|
|
|
|
|
return pinnedLogs?.some((logId) => logId === log.rowId);
|
|
|
|
|
};
|
2025-04-04 20:53:12 +08:00
|
|
|
|
2025-05-23 02:12:06 +08:00
|
|
|
export const useLogIsPermalinked = (log: LogListModel) => {
|
|
|
|
|
const { permalinkedLogId } = useContext(LogListContext);
|
|
|
|
|
return permalinkedLogId && permalinkedLogId === log.uid;
|
|
|
|
|
};
|
|
|
|
|
|
2025-04-04 20:53:12 +08:00
|
|
|
export type LogListState = Pick<
|
|
|
|
|
LogListContextData,
|
|
|
|
|
| 'dedupStrategy'
|
2025-06-10 17:59:01 +08:00
|
|
|
| 'fontSize'
|
2025-04-11 02:47:17 +08:00
|
|
|
| 'forceEscape'
|
2025-04-04 20:53:12 +08:00
|
|
|
| 'filterLevels'
|
|
|
|
|
| 'pinnedLogs'
|
2025-04-07 22:38:55 +08:00
|
|
|
| 'showUniqueLabels'
|
2025-04-04 20:53:12 +08:00
|
|
|
| 'showTime'
|
|
|
|
|
| 'sortOrder'
|
|
|
|
|
| 'syntaxHighlighting'
|
2025-08-09 01:13:01 +08:00
|
|
|
| 'timestampResolution'
|
2025-04-04 20:53:12 +08:00
|
|
|
>;
|
|
|
|
|
|
2025-08-28 01:34:14 +08:00
|
|
|
export type LogListOption = keyof LogListState | 'wrapLogMessage' | 'prettifyJSON';
|
|
|
|
|
|
2025-04-04 20:53:12 +08:00
|
|
|
export interface Props {
|
|
|
|
|
app: CoreApp;
|
|
|
|
|
children?: ReactNode;
|
2025-06-10 17:59:01 +08:00
|
|
|
// Only ControlledLogRows can send an undefined containerElement. See LogList.tsx
|
2025-05-21 01:28:35 +08:00
|
|
|
containerElement?: HTMLDivElement;
|
2025-07-09 22:07:19 +08:00
|
|
|
detailsMode?: LogLineDetailsMode;
|
2025-04-04 20:53:12 +08:00
|
|
|
dedupStrategy: LogsDedupStrategy;
|
|
|
|
|
displayedFields: string[];
|
2025-05-21 01:28:35 +08:00
|
|
|
enableLogDetails: boolean;
|
2025-04-04 20:53:12 +08:00
|
|
|
filterLevels?: LogLevel[];
|
2025-06-10 17:59:01 +08:00
|
|
|
fontSize: LogListFontSize;
|
2025-04-04 20:53:12 +08:00
|
|
|
getRowContextQuery?: GetRowContextQueryFn;
|
2025-05-21 01:28:35 +08:00
|
|
|
isLabelFilterActive?: (key: string, value: string, refId?: string) => Promise<boolean>;
|
2025-04-07 22:38:55 +08:00
|
|
|
logs: LogRowModel[];
|
2025-06-01 20:29:49 +08:00
|
|
|
logLineMenuCustomItems?: LogLineMenuCustomItem[];
|
2025-04-07 22:38:55 +08:00
|
|
|
logsMeta?: LogsMetaItem[];
|
2025-04-04 20:53:12 +08:00
|
|
|
logOptionsStorageKey?: string;
|
|
|
|
|
logSupportsContext?: (row: LogRowModel) => boolean;
|
2025-07-18 19:13:51 +08:00
|
|
|
noInteractions?: 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-08-28 01:34:14 +08:00
|
|
|
onLogOptionsChange?: (option: LogListControlOptions, value: string | boolean | string[]) => void;
|
2025-04-04 20:53:12 +08:00
|
|
|
onLogLineHover?: (row?: LogRowModel) => void;
|
|
|
|
|
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-04-04 20:53:12 +08:00
|
|
|
pinLineButtonTooltipTitle?: PopoverContent;
|
|
|
|
|
pinnedLogs?: string[];
|
2025-04-07 22:38:55 +08:00
|
|
|
prettifyJSON?: boolean;
|
2025-07-08 17:57:28 +08:00
|
|
|
setDisplayedFields?: (displayedFields: string[]) => void;
|
2025-04-04 20:53:12 +08:00
|
|
|
showControls: boolean;
|
2025-04-07 22:38:55 +08:00
|
|
|
showUniqueLabels?: boolean;
|
2025-04-04 20:53:12 +08:00
|
|
|
showTime: boolean;
|
|
|
|
|
sortOrder: LogsSortOrder;
|
|
|
|
|
syntaxHighlighting?: boolean;
|
2025-08-09 01:13:01 +08:00
|
|
|
timestampResolution?: LogLineTimestampResolution;
|
2025-04-04 20:53:12 +08:00
|
|
|
wrapLogMessage: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const LogListContextProvider = ({
|
|
|
|
|
app,
|
|
|
|
|
children,
|
2025-05-21 01:28:35 +08:00
|
|
|
containerElement,
|
|
|
|
|
enableLogDetails,
|
2025-07-09 22:07:19 +08:00
|
|
|
detailsMode: detailsModeProp,
|
2025-04-04 20:53:12 +08:00
|
|
|
dedupStrategy,
|
|
|
|
|
displayedFields,
|
2025-04-11 02:47:17 +08:00
|
|
|
filterLevels,
|
2025-06-10 17:59:01 +08:00
|
|
|
fontSize,
|
2025-05-21 01:28:35 +08:00
|
|
|
isLabelFilterActive,
|
2025-04-04 20:53:12 +08:00
|
|
|
getRowContextQuery,
|
2025-04-07 22:38:55 +08:00
|
|
|
logs,
|
2025-06-01 20:29:49 +08:00
|
|
|
logLineMenuCustomItems,
|
2025-04-07 22:38:55 +08:00
|
|
|
logsMeta,
|
2025-04-04 20:53:12 +08:00
|
|
|
logOptionsStorageKey,
|
|
|
|
|
logSupportsContext,
|
2025-07-18 19:13:51 +08:00
|
|
|
noInteractions,
|
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,
|
2025-08-28 01:34:14 +08:00
|
|
|
prettifyJSON: prettifyJSONProp = logOptionsStorageKey
|
|
|
|
|
? store.getBool(`${logOptionsStorageKey}.prettifyLogMessage`, true)
|
|
|
|
|
: true,
|
2025-07-08 17:57:28 +08:00
|
|
|
setDisplayedFields,
|
2025-04-04 20:53:12 +08:00
|
|
|
showControls,
|
|
|
|
|
showTime,
|
2025-04-07 22:38:55 +08:00
|
|
|
showUniqueLabels,
|
2025-04-04 20:53:12 +08:00
|
|
|
sortOrder,
|
2025-04-07 22:38:55 +08:00
|
|
|
syntaxHighlighting,
|
2025-08-09 01:13:01 +08:00
|
|
|
timestampResolution = logOptionsStorageKey
|
|
|
|
|
? (store.get(`${logOptionsStorageKey}.timestampResolution`) ?? 'ms')
|
|
|
|
|
: 'ms',
|
2025-08-28 01:34:14 +08:00
|
|
|
wrapLogMessage: wrapLogMessageProp,
|
2025-04-04 20:53:12 +08:00
|
|
|
}: Props) => {
|
|
|
|
|
const [logListState, setLogListState] = useState<LogListState>({
|
|
|
|
|
dedupStrategy,
|
|
|
|
|
filterLevels:
|
|
|
|
|
filterLevels ?? (logOptionsStorageKey ? store.getObject(`${logOptionsStorageKey}.filterLevels`, []) : []),
|
2025-06-10 17:59:01 +08:00
|
|
|
fontSize,
|
2025-07-17 16:59:03 +08:00
|
|
|
forceEscape: logOptionsStorageKey ? store.getBool(`${logOptionsStorageKey}.forceEscape`, false) : false,
|
2025-04-04 20:53:12 +08:00
|
|
|
pinnedLogs,
|
|
|
|
|
showTime,
|
2025-04-07 22:38:55 +08:00
|
|
|
showUniqueLabels,
|
2025-04-04 20:53:12 +08:00
|
|
|
sortOrder,
|
|
|
|
|
syntaxHighlighting,
|
2025-08-09 01:13:01 +08:00
|
|
|
timestampResolution,
|
2025-04-04 20:53:12 +08:00
|
|
|
});
|
2025-05-21 01:28:35 +08:00
|
|
|
const [showDetails, setShowDetails] = useState<LogListModel[]>([]);
|
2025-08-19 03:06:31 +08:00
|
|
|
const [detailsWidth, setDetailsWidthState] = useState(
|
|
|
|
|
getDetailsWidth(containerElement, logOptionsStorageKey, undefined, detailsModeProp, showControls)
|
|
|
|
|
);
|
2025-07-09 22:07:19 +08:00
|
|
|
const [detailsMode, setDetailsMode] = useState<LogLineDetailsMode>(detailsModeProp ?? 'sidebar');
|
2025-07-23 18:12:47 +08:00
|
|
|
const [isAssistantAvailable, openAssistant] = useAssistant();
|
2025-08-28 01:34:14 +08:00
|
|
|
const [prettifyJSON, setPrettifyJSONState] = useState(prettifyJSONProp);
|
|
|
|
|
const [wrapLogMessage, setWrapLogMessageState] = useState(wrapLogMessageProp);
|
2025-07-23 18:12:47 +08:00
|
|
|
|
2025-07-18 19:13:51 +08:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (noInteractions) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
reportInteractionOnce(`logs_log_list_${app}_logs_displayed`, {
|
|
|
|
|
dedupStrategy,
|
|
|
|
|
fontSize,
|
|
|
|
|
forceEscape: logListState.forceEscape,
|
|
|
|
|
showTime,
|
|
|
|
|
syntaxHighlighting,
|
|
|
|
|
wrapLogMessage,
|
|
|
|
|
detailsWidth,
|
|
|
|
|
detailsMode,
|
|
|
|
|
withDisplayedFields: displayedFields.length > 0,
|
2025-08-09 01:13:01 +08:00
|
|
|
timestampResolution: logListState.timestampResolution,
|
2025-07-18 19:13:51 +08:00
|
|
|
});
|
|
|
|
|
// Just once
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-08-09 01:13:01 +08:00
|
|
|
// OTel displayed fields
|
2025-07-16 23:42:14 +08:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (displayedFields.length > 0 || !config.featureToggles.otelLogsFormatting || !setDisplayedFields) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const otelDisplayedFields = getDisplayedFieldsForLogs(logs);
|
|
|
|
|
if (otelDisplayedFields.length) {
|
|
|
|
|
setDisplayedFields(otelDisplayedFields);
|
|
|
|
|
}
|
|
|
|
|
}, [displayedFields.length, logs, setDisplayedFields]);
|
|
|
|
|
|
2025-08-09 01:13:01 +08:00
|
|
|
// Sync state
|
2025-04-04 20:53:12 +08:00
|
|
|
useEffect(() => {
|
|
|
|
|
// Props are updated in the context only of the panel is being externally controlled.
|
|
|
|
|
if (showControls && app !== CoreApp.PanelEditor) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const newState = {
|
|
|
|
|
...logListState,
|
|
|
|
|
dedupStrategy,
|
|
|
|
|
showTime,
|
|
|
|
|
sortOrder,
|
|
|
|
|
syntaxHighlighting,
|
|
|
|
|
wrapLogMessage,
|
|
|
|
|
};
|
|
|
|
|
if (!shallowCompare(logListState, newState)) {
|
|
|
|
|
setLogListState(newState);
|
|
|
|
|
}
|
|
|
|
|
}, [
|
|
|
|
|
app,
|
|
|
|
|
dedupStrategy,
|
|
|
|
|
logListState,
|
|
|
|
|
pinnedLogs,
|
|
|
|
|
showControls,
|
|
|
|
|
showTime,
|
|
|
|
|
sortOrder,
|
|
|
|
|
syntaxHighlighting,
|
|
|
|
|
wrapLogMessage,
|
|
|
|
|
]);
|
|
|
|
|
|
2025-08-09 01:13:01 +08:00
|
|
|
// Sync filter levels
|
2025-04-04 20:53:12 +08:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (filterLevels === undefined) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-06-23 17:29:30 +08:00
|
|
|
setLogListState((logListState) => {
|
|
|
|
|
if (!shallowCompare(logListState.filterLevels, filterLevels)) {
|
|
|
|
|
return { ...logListState, filterLevels };
|
|
|
|
|
}
|
|
|
|
|
return logListState;
|
|
|
|
|
});
|
|
|
|
|
}, [filterLevels]);
|
2025-04-04 20:53:12 +08:00
|
|
|
|
2025-08-09 01:13:01 +08:00
|
|
|
// Sync font size
|
2025-06-10 17:59:01 +08:00
|
|
|
useEffect(() => {
|
|
|
|
|
setLogListState((logListState) => ({ ...logListState, fontSize }));
|
|
|
|
|
}, [fontSize]);
|
|
|
|
|
|
2025-08-09 01:13:01 +08:00
|
|
|
// Sync pinned logs
|
2025-05-23 02:12:06 +08:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (!shallowCompare(logListState.pinnedLogs ?? [], pinnedLogs ?? [])) {
|
|
|
|
|
setLogListState({ ...logListState, pinnedLogs });
|
|
|
|
|
}
|
|
|
|
|
}, [logListState, pinnedLogs]);
|
|
|
|
|
|
2025-08-09 01:13:01 +08:00
|
|
|
// Sync show details
|
2025-06-01 20:29:49 +08:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (!showDetails.length) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const newShowDetails = showDetails.filter(
|
|
|
|
|
(expandedLog) => logs.findIndex((log) => log.uid === expandedLog.uid) >= 0
|
|
|
|
|
);
|
|
|
|
|
if (newShowDetails.length !== showDetails.length) {
|
|
|
|
|
setShowDetails(newShowDetails);
|
|
|
|
|
}
|
|
|
|
|
}, [logs, showDetails]);
|
|
|
|
|
|
2025-08-19 03:06:31 +08:00
|
|
|
// Sync log details inline and sidebar width
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
setDetailsWidthState(getDetailsWidth(containerElement, logOptionsStorageKey, undefined, detailsMode, showControls));
|
|
|
|
|
}, [containerElement, detailsMode, logOptionsStorageKey, showControls]);
|
|
|
|
|
|
2025-08-09 01:13:01 +08:00
|
|
|
// Sync log details width
|
2025-06-10 17:59:01 +08:00
|
|
|
useEffect(() => {
|
2025-08-19 03:06:31 +08:00
|
|
|
if (!containerElement) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-06-10 17:59:01 +08:00
|
|
|
const handleResize = debounce(() => {
|
2025-08-19 03:06:31 +08:00
|
|
|
setDetailsWidthState((detailsWidth) =>
|
|
|
|
|
getDetailsWidth(containerElement, logOptionsStorageKey, detailsWidth, detailsMode, showControls)
|
|
|
|
|
);
|
2025-06-10 17:59:01 +08:00
|
|
|
}, 50);
|
2025-08-19 03:06:31 +08:00
|
|
|
const observer = new ResizeObserver(() => handleResize());
|
|
|
|
|
observer.observe(containerElement);
|
|
|
|
|
return () => observer.disconnect();
|
|
|
|
|
}, [containerElement, detailsMode, logOptionsStorageKey, showControls]);
|
2025-06-10 17:59:01 +08:00
|
|
|
|
2025-08-09 01:13:01 +08:00
|
|
|
// Sync timestamp resolution
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
setLogListState((state) => ({
|
|
|
|
|
...state,
|
|
|
|
|
timestampResolution,
|
|
|
|
|
}));
|
|
|
|
|
}, [timestampResolution]);
|
|
|
|
|
|
2025-05-21 01:28:35 +08:00
|
|
|
const detailsDisplayed = useCallback(
|
|
|
|
|
(log: LogListModel) => !!showDetails.find((shownLog) => shownLog.uid === log.uid),
|
|
|
|
|
[showDetails]
|
|
|
|
|
);
|
|
|
|
|
|
2025-04-04 20:53:12 +08:00
|
|
|
const setDedupStrategy = useCallback(
|
|
|
|
|
(dedupStrategy: LogsDedupStrategy) => {
|
|
|
|
|
setLogListState({ ...logListState, dedupStrategy });
|
|
|
|
|
onLogOptionsChange?.('dedupStrategy', dedupStrategy);
|
|
|
|
|
},
|
|
|
|
|
[logListState, onLogOptionsChange]
|
|
|
|
|
);
|
|
|
|
|
|
2025-06-10 17:59:01 +08:00
|
|
|
const setFontSize = useCallback(
|
|
|
|
|
(fontSize: LogListFontSize) => {
|
|
|
|
|
if (logOptionsStorageKey) {
|
|
|
|
|
store.set(`${logOptionsStorageKey}.fontSize`, fontSize);
|
|
|
|
|
}
|
|
|
|
|
setLogListState((logListState) => ({ ...logListState, fontSize }));
|
2025-08-28 01:34:14 +08:00
|
|
|
onLogOptionsChange?.('fontSize', fontSize);
|
2025-06-10 17:59:01 +08:00
|
|
|
},
|
2025-08-28 01:34:14 +08:00
|
|
|
[logOptionsStorageKey, onLogOptionsChange]
|
2025-06-10 17:59:01 +08:00
|
|
|
);
|
|
|
|
|
|
2025-04-11 02:47:17 +08:00
|
|
|
const setForceEscape = useCallback(
|
|
|
|
|
(forceEscape: boolean) => {
|
|
|
|
|
setLogListState({ ...logListState, forceEscape });
|
|
|
|
|
},
|
|
|
|
|
[logListState]
|
|
|
|
|
);
|
|
|
|
|
|
2025-04-04 20:53:12 +08:00
|
|
|
const setFilterLevels = useCallback(
|
|
|
|
|
(filterLevels: LogLevel[]) => {
|
|
|
|
|
setLogListState({ ...logListState, filterLevels });
|
|
|
|
|
onLogOptionsChange?.('filterLevels', filterLevels);
|
|
|
|
|
},
|
|
|
|
|
[logListState, onLogOptionsChange]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const setPinnedLogs = useCallback(
|
|
|
|
|
(pinnedLogs: string[]) => {
|
|
|
|
|
setLogListState({ ...logListState, pinnedLogs });
|
|
|
|
|
onLogOptionsChange?.('pinnedLogs', pinnedLogs);
|
|
|
|
|
},
|
|
|
|
|
[logListState, onLogOptionsChange]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const setShowTime = useCallback(
|
|
|
|
|
(showTime: boolean) => {
|
2025-08-09 01:13:01 +08:00
|
|
|
const newTimestampFormat = showTime === false ? 'ms' : logListState.timestampResolution;
|
|
|
|
|
setLogListState({
|
|
|
|
|
...logListState,
|
|
|
|
|
showTime,
|
|
|
|
|
timestampResolution: newTimestampFormat,
|
|
|
|
|
});
|
2025-04-04 20:53:12 +08:00
|
|
|
onLogOptionsChange?.('showTime', showTime);
|
|
|
|
|
if (logOptionsStorageKey) {
|
|
|
|
|
store.set(`${logOptionsStorageKey}.showTime`, showTime);
|
2025-08-09 01:13:01 +08:00
|
|
|
store.set(`${logOptionsStorageKey}.timestampResolution`, newTimestampFormat);
|
2025-04-04 20:53:12 +08:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[logListState, logOptionsStorageKey, onLogOptionsChange]
|
|
|
|
|
);
|
|
|
|
|
|
2025-04-07 22:38:55 +08:00
|
|
|
const setShowUniqueLabels = useCallback(
|
|
|
|
|
(showUniqueLabels: boolean) => {
|
|
|
|
|
setLogListState({ ...logListState, showUniqueLabels });
|
|
|
|
|
onLogOptionsChange?.('showUniqueLabels', showUniqueLabels);
|
|
|
|
|
if (logOptionsStorageKey) {
|
|
|
|
|
store.set(`${logOptionsStorageKey}.showLabels`, showUniqueLabels);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[logListState, logOptionsStorageKey, onLogOptionsChange]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const setPrettifyJSON = useCallback(
|
|
|
|
|
(prettifyJSON: boolean) => {
|
2025-08-28 01:34:14 +08:00
|
|
|
setPrettifyJSONState(prettifyJSON);
|
2025-04-07 22:38:55 +08:00
|
|
|
if (logOptionsStorageKey) {
|
|
|
|
|
store.set(`${logOptionsStorageKey}.prettifyLogMessage`, prettifyJSON);
|
|
|
|
|
}
|
2025-08-28 01:34:14 +08:00
|
|
|
onLogOptionsChange?.('prettifyJSON', prettifyJSON);
|
2025-04-07 22:38:55 +08:00
|
|
|
},
|
2025-08-28 01:34:14 +08:00
|
|
|
[logOptionsStorageKey, onLogOptionsChange]
|
2025-04-07 22:38:55 +08:00
|
|
|
);
|
|
|
|
|
|
2025-04-04 20:53:12 +08:00
|
|
|
const setSyntaxHighlighting = useCallback(
|
|
|
|
|
(syntaxHighlighting: boolean) => {
|
|
|
|
|
setLogListState({ ...logListState, syntaxHighlighting });
|
|
|
|
|
onLogOptionsChange?.('syntaxHighlighting', syntaxHighlighting);
|
|
|
|
|
if (logOptionsStorageKey) {
|
|
|
|
|
store.set(`${logOptionsStorageKey}.syntaxHighlighting`, syntaxHighlighting);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[logListState, logOptionsStorageKey, onLogOptionsChange]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const setSortOrder = useCallback(
|
|
|
|
|
(sortOrder: LogsSortOrder) => {
|
|
|
|
|
setLogListState({ ...logListState, sortOrder });
|
|
|
|
|
onLogOptionsChange?.('sortOrder', sortOrder);
|
|
|
|
|
if (logOptionsStorageKey) {
|
|
|
|
|
store.set(`${logOptionsStorageKey}.sortOrder`, sortOrder);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[logListState, logOptionsStorageKey, onLogOptionsChange]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const setWrapLogMessage = useCallback(
|
|
|
|
|
(wrapLogMessage: boolean) => {
|
2025-08-28 01:34:14 +08:00
|
|
|
setWrapLogMessageState(wrapLogMessage);
|
2025-04-04 20:53:12 +08:00
|
|
|
if (logOptionsStorageKey) {
|
|
|
|
|
store.set(`${logOptionsStorageKey}.wrapLogMessage`, wrapLogMessage);
|
|
|
|
|
}
|
2025-08-28 01:34:14 +08:00
|
|
|
onLogOptionsChange?.('wrapLogMessage', wrapLogMessage);
|
2025-04-04 20:53:12 +08:00
|
|
|
},
|
2025-08-28 01:34:14 +08:00
|
|
|
[logOptionsStorageKey, onLogOptionsChange]
|
2025-04-04 20:53:12 +08:00
|
|
|
);
|
|
|
|
|
|
2025-04-07 22:38:55 +08:00
|
|
|
const downloadLogs = useCallback(
|
|
|
|
|
(format: DownloadFormat) => {
|
|
|
|
|
const filteredLogs =
|
|
|
|
|
logListState.filterLevels.length === 0
|
|
|
|
|
? logs
|
|
|
|
|
: logs.filter((log) => logListState.filterLevels.includes(log.logLevel));
|
|
|
|
|
download(format, filteredLogs, logsMeta);
|
|
|
|
|
},
|
|
|
|
|
[logListState.filterLevels, logs, logsMeta]
|
|
|
|
|
);
|
|
|
|
|
|
2025-05-21 01:28:35 +08:00
|
|
|
const closeDetails = useCallback(() => {
|
2025-07-21 19:02:20 +08:00
|
|
|
showDetails.forEach((log) => removeDetailsScrollPosition(log));
|
2025-05-21 01:28:35 +08:00
|
|
|
setShowDetails([]);
|
2025-07-16 00:17:20 +08:00
|
|
|
}, [showDetails]);
|
2025-05-21 01:28:35 +08:00
|
|
|
|
|
|
|
|
const toggleDetails = useCallback(
|
|
|
|
|
(log: LogListModel) => {
|
|
|
|
|
if (!enableLogDetails) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-07-21 19:02:20 +08:00
|
|
|
const found = showDetails.find((stateLog) => stateLog === log || stateLog.uid === log.uid);
|
|
|
|
|
if (found) {
|
|
|
|
|
removeDetailsScrollPosition(found);
|
2025-05-21 01:28:35 +08:00
|
|
|
setShowDetails(showDetails.filter((stateLog) => stateLog !== log && stateLog.uid !== log.uid));
|
|
|
|
|
} else {
|
|
|
|
|
// Supporting one displayed details for now
|
2025-07-21 19:02:20 +08:00
|
|
|
setShowDetails([...showDetails, log]);
|
2025-05-21 01:28:35 +08:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[enableLogDetails, showDetails]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const setDetailsWidth = useCallback(
|
|
|
|
|
(width: number) => {
|
2025-06-10 17:59:01 +08:00
|
|
|
if (!logOptionsStorageKey || !containerElement) {
|
2025-05-21 01:28:35 +08:00
|
|
|
return;
|
|
|
|
|
}
|
2025-06-10 17:59:01 +08:00
|
|
|
|
|
|
|
|
const maxWidth = containerElement.clientWidth - LOG_LIST_MIN_WIDTH;
|
|
|
|
|
if (width > maxWidth) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-21 01:28:35 +08:00
|
|
|
store.set(`${logOptionsStorageKey}.detailsWidth`, width);
|
2025-06-10 17:59:01 +08:00
|
|
|
setDetailsWidthState(width);
|
2025-05-21 01:28:35 +08:00
|
|
|
},
|
2025-06-10 17:59:01 +08:00
|
|
|
[containerElement, logOptionsStorageKey]
|
2025-05-21 01:28:35 +08:00
|
|
|
);
|
|
|
|
|
|
2025-08-09 01:13:01 +08:00
|
|
|
const setTimestampResolution = useCallback(
|
|
|
|
|
(timestampResolution: LogLineTimestampResolution) => {
|
|
|
|
|
if (logOptionsStorageKey) {
|
|
|
|
|
store.set(`${logOptionsStorageKey}.timestampResolution`, timestampResolution);
|
|
|
|
|
}
|
|
|
|
|
setLogListState((state) => ({
|
|
|
|
|
...state,
|
|
|
|
|
timestampResolution,
|
|
|
|
|
}));
|
|
|
|
|
},
|
|
|
|
|
[logOptionsStorageKey]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const openAssistantByLog = useCallback(
|
|
|
|
|
(log: LogListModel) => {
|
|
|
|
|
if (!openAssistant) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
handleOpenAssistant(openAssistant, log);
|
|
|
|
|
},
|
|
|
|
|
[openAssistant]
|
|
|
|
|
);
|
|
|
|
|
|
2025-06-01 20:29:49 +08:00
|
|
|
const hasLogsWithErrors = useMemo(() => logs.some((log) => !!checkLogsError(log)), [logs]);
|
|
|
|
|
const hasSampledLogs = useMemo(() => logs.some((log) => !!checkLogsSampled(log)), [logs]);
|
2025-07-17 16:59:03 +08:00
|
|
|
const hasUnescapedContent = useMemo(() => logs.some((r) => r.hasUnescapedContent), [logs]);
|
2025-06-01 20:29:49 +08:00
|
|
|
|
2025-04-04 20:53:12 +08:00
|
|
|
return (
|
|
|
|
|
<LogListContext.Provider
|
|
|
|
|
value={{
|
|
|
|
|
app,
|
2025-05-21 01:28:35 +08:00
|
|
|
closeDetails,
|
|
|
|
|
detailsDisplayed,
|
2025-04-04 20:53:12 +08:00
|
|
|
dedupStrategy: logListState.dedupStrategy,
|
2025-07-09 22:07:19 +08:00
|
|
|
detailsMode,
|
2025-06-10 17:59:01 +08:00
|
|
|
detailsWidth,
|
2025-05-21 01:28:35 +08:00
|
|
|
displayedFields,
|
2025-04-07 22:38:55 +08:00
|
|
|
downloadLogs,
|
2025-05-21 01:28:35 +08:00
|
|
|
enableLogDetails,
|
2025-04-04 20:53:12 +08:00
|
|
|
filterLevels: logListState.filterLevels,
|
2025-06-10 17:59:01 +08:00
|
|
|
fontSize: logListState.fontSize,
|
2025-04-11 02:47:17 +08:00
|
|
|
forceEscape: logListState.forceEscape,
|
2025-06-01 20:29:49 +08:00
|
|
|
hasLogsWithErrors,
|
|
|
|
|
hasSampledLogs,
|
2025-07-17 16:59:03 +08:00
|
|
|
hasUnescapedContent,
|
2025-05-21 01:28:35 +08:00
|
|
|
isLabelFilterActive,
|
2025-04-04 20:53:12 +08:00
|
|
|
getRowContextQuery,
|
|
|
|
|
logSupportsContext,
|
2025-06-01 20:29:49 +08:00
|
|
|
logLineMenuCustomItems,
|
2025-07-08 17:57:28 +08:00
|
|
|
logOptionsStorageKey,
|
2025-07-18 19:13:51 +08:00
|
|
|
noInteractions: noInteractions ?? false,
|
2025-05-21 01:28:35 +08:00
|
|
|
onClickFilterLabel,
|
|
|
|
|
onClickFilterOutLabel,
|
|
|
|
|
onClickFilterString,
|
|
|
|
|
onClickFilterOutString,
|
|
|
|
|
onClickShowField,
|
|
|
|
|
onClickHideField,
|
2025-04-04 20:53:12 +08:00
|
|
|
onLogLineHover,
|
|
|
|
|
onPermalinkClick,
|
|
|
|
|
onPinLine,
|
|
|
|
|
onOpenContext,
|
|
|
|
|
onUnpinLine,
|
2025-05-23 02:12:06 +08:00
|
|
|
permalinkedLogId,
|
2025-04-04 20:53:12 +08:00
|
|
|
pinLineButtonTooltipTitle,
|
|
|
|
|
pinnedLogs: logListState.pinnedLogs,
|
2025-08-28 01:34:14 +08:00
|
|
|
prettifyJSON,
|
2025-04-04 20:53:12 +08:00
|
|
|
setDedupStrategy,
|
2025-07-09 22:07:19 +08:00
|
|
|
setDetailsMode,
|
2025-05-21 01:28:35 +08:00
|
|
|
setDetailsWidth,
|
2025-07-08 17:57:28 +08:00
|
|
|
setDisplayedFields,
|
2025-04-04 20:53:12 +08:00
|
|
|
setFilterLevels,
|
2025-06-10 17:59:01 +08:00
|
|
|
setFontSize,
|
2025-04-11 02:47:17 +08:00
|
|
|
setForceEscape,
|
2025-04-04 20:53:12 +08:00
|
|
|
setLogListState,
|
|
|
|
|
setPinnedLogs,
|
2025-04-07 22:38:55 +08:00
|
|
|
setPrettifyJSON,
|
2025-04-04 20:53:12 +08:00
|
|
|
setShowTime,
|
2025-04-07 22:38:55 +08:00
|
|
|
setShowUniqueLabels,
|
2025-04-04 20:53:12 +08:00
|
|
|
setSortOrder,
|
|
|
|
|
setSyntaxHighlighting,
|
2025-08-09 01:13:01 +08:00
|
|
|
setTimestampResolution,
|
2025-04-04 20:53:12 +08:00
|
|
|
setWrapLogMessage,
|
2025-05-21 01:28:35 +08:00
|
|
|
showDetails,
|
2025-04-04 20:53:12 +08:00
|
|
|
showTime: logListState.showTime,
|
2025-04-07 22:38:55 +08:00
|
|
|
showUniqueLabels: logListState.showUniqueLabels,
|
2025-04-04 20:53:12 +08:00
|
|
|
sortOrder: logListState.sortOrder,
|
|
|
|
|
syntaxHighlighting: logListState.syntaxHighlighting,
|
2025-08-09 01:13:01 +08:00
|
|
|
timestampResolution: logListState.timestampResolution,
|
2025-05-21 01:28:35 +08:00
|
|
|
toggleDetails,
|
2025-08-28 01:34:14 +08:00
|
|
|
wrapLogMessage,
|
2025-07-23 18:12:47 +08:00
|
|
|
isAssistantAvailable,
|
|
|
|
|
openAssistantByLog,
|
2025-04-04 20:53:12 +08:00
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{children}
|
|
|
|
|
</LogListContext.Provider>
|
|
|
|
|
);
|
|
|
|
|
};
|
2025-04-07 22:38:55 +08:00
|
|
|
|
|
|
|
|
export function isLogsSortOrder(value: unknown): value is LogsSortOrder {
|
|
|
|
|
return value === LogsSortOrder.Ascending || value === LogsSortOrder.Descending;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function isDedupStrategy(value: unknown): value is LogsDedupStrategy {
|
|
|
|
|
return (
|
|
|
|
|
value === LogsDedupStrategy.exact ||
|
|
|
|
|
value === LogsDedupStrategy.none ||
|
|
|
|
|
value === LogsDedupStrategy.numbers ||
|
|
|
|
|
value === LogsDedupStrategy.signature
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-06-10 17:59:01 +08:00
|
|
|
|
|
|
|
|
// Only ControlledLogRows can send an undefined containerElement. See LogList.tsx
|
|
|
|
|
function getDetailsWidth(
|
|
|
|
|
containerElement: HTMLDivElement | undefined,
|
|
|
|
|
logOptionsStorageKey?: string,
|
2025-08-19 03:06:31 +08:00
|
|
|
currentWidth?: number,
|
|
|
|
|
detailsMode: LogLineDetailsMode = 'sidebar',
|
|
|
|
|
showControls?: boolean
|
2025-06-10 17:59:01 +08:00
|
|
|
) {
|
|
|
|
|
if (!containerElement) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2025-08-19 03:06:31 +08:00
|
|
|
if (detailsMode === 'inline') {
|
|
|
|
|
return containerElement.clientWidth - getScrollbarWidth() - (showControls ? LOG_LIST_CONTROLS_WIDTH : 0);
|
|
|
|
|
}
|
2025-06-10 17:59:01 +08:00
|
|
|
const defaultWidth = containerElement.clientWidth * 0.4;
|
|
|
|
|
const detailsWidth =
|
|
|
|
|
currentWidth ||
|
2025-07-09 22:07:19 +08:00
|
|
|
(logOptionsStorageKey
|
|
|
|
|
? parseInt(store.get(`${logOptionsStorageKey}.detailsWidth`) ?? defaultWidth, 10)
|
|
|
|
|
: defaultWidth);
|
2025-06-10 17:59:01 +08:00
|
|
|
|
|
|
|
|
const maxWidth = containerElement.clientWidth - LOG_LIST_MIN_WIDTH;
|
|
|
|
|
|
|
|
|
|
// The user might have resized the screen.
|
|
|
|
|
if (detailsWidth >= containerElement.clientWidth || detailsWidth > maxWidth) {
|
|
|
|
|
return currentWidth ?? defaultWidth;
|
|
|
|
|
}
|
|
|
|
|
return detailsWidth;
|
|
|
|
|
}
|
2025-07-16 00:17:20 +08:00
|
|
|
|
|
|
|
|
const detailsScrollMap = new Map<string, number>();
|
|
|
|
|
|
|
|
|
|
export function saveDetailsScrollPosition(log: LogListModel, position: number) {
|
|
|
|
|
detailsScrollMap.set(log.uid, position);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getDetailsScrollPosition(log: LogListModel) {
|
|
|
|
|
return detailsScrollMap.get(log.uid) ?? 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function removeDetailsScrollPosition(log: LogListModel) {
|
|
|
|
|
detailsScrollMap.delete(log.uid);
|
|
|
|
|
}
|
2025-07-18 19:13:51 +08:00
|
|
|
|
2025-08-09 01:13:01 +08:00
|
|
|
async function handleOpenAssistant(openAssistant: (props: OpenAssistantProps) => void, log: LogListModel) {
|
|
|
|
|
const datasource = await getDataSourceSrv().get(log.datasourceUid);
|
|
|
|
|
const context = [];
|
|
|
|
|
if (datasource) {
|
2025-08-21 18:07:47 +08:00
|
|
|
context.push(createAssistantContextItem('datasource', { datasourceUid: datasource.uid }));
|
2025-08-09 01:13:01 +08:00
|
|
|
}
|
|
|
|
|
openAssistant({
|
|
|
|
|
prompt: `${t('logs.log-line-menu.log-line-explainer', 'Explain this log line in a concise way')}:
|
2025-08-21 18:07:47 +08:00
|
|
|
|
|
|
|
|
\`\`\`
|
|
|
|
|
${log.entry.replaceAll('`', '\\`')}
|
|
|
|
|
\`\`\`
|
|
|
|
|
`,
|
|
|
|
|
origin: 'explain-log-line',
|
2025-08-09 01:13:01 +08:00
|
|
|
context: [
|
|
|
|
|
...context,
|
2025-08-21 18:07:47 +08:00
|
|
|
createAssistantContextItem('structured', {
|
2025-08-09 01:13:01 +08:00
|
|
|
title: t('logs.log-line-menu.log-line', 'Log line'),
|
|
|
|
|
data: {
|
|
|
|
|
labels: log.labels,
|
|
|
|
|
value: log.entry,
|
|
|
|
|
timestamp: log.timestamp,
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
}
|