2025-04-04 20:53:12 +08:00
|
|
|
import {
|
|
|
|
|
createContext,
|
|
|
|
|
Dispatch,
|
|
|
|
|
ReactNode,
|
|
|
|
|
SetStateAction,
|
|
|
|
|
useCallback,
|
|
|
|
|
useContext,
|
|
|
|
|
useEffect,
|
|
|
|
|
useState,
|
|
|
|
|
} from 'react';
|
2025-02-28 00:34:02 +08:00
|
|
|
|
2025-04-07 22:38:55 +08:00
|
|
|
import {
|
|
|
|
|
CoreApp,
|
|
|
|
|
LogLevel,
|
|
|
|
|
LogRowModel,
|
|
|
|
|
LogsDedupStrategy,
|
|
|
|
|
LogsMetaItem,
|
|
|
|
|
LogsSortOrder,
|
|
|
|
|
shallowCompare,
|
|
|
|
|
store,
|
|
|
|
|
} from '@grafana/data';
|
2025-02-28 00:34:02 +08:00
|
|
|
import { PopoverContent } from '@grafana/ui';
|
|
|
|
|
|
2025-04-07 22:38:55 +08:00
|
|
|
import { DownloadFormat, downloadLogs as download } from '../../utils';
|
|
|
|
|
|
2025-02-28 00:34:02 +08:00
|
|
|
import { GetRowContextQueryFn } from './LogLineMenu';
|
|
|
|
|
|
2025-04-07 22:38:55 +08:00
|
|
|
export interface LogListContextData extends Omit<Props, 'logs' | 'logsMeta' | 'showControls'> {
|
|
|
|
|
downloadLogs: (format: DownloadFormat) => void;
|
2025-04-04 20:53:12 +08:00
|
|
|
filterLevels: LogLevel[];
|
2025-04-11 02:47:17 +08:00
|
|
|
hasUnescapedContent?: boolean;
|
2025-04-04 20:53:12 +08:00
|
|
|
setDedupStrategy: (dedupStrategy: LogsDedupStrategy) => void;
|
|
|
|
|
setDisplayedFields: (displayedFields: string[]) => void;
|
|
|
|
|
setFilterLevels: (filterLevels: LogLevel[]) => 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;
|
|
|
|
|
setWrapLogMessage: (showTime: boolean) => void;
|
2025-02-28 00:34:02 +08:00
|
|
|
}
|
|
|
|
|
|
2025-04-04 20:53:12 +08:00
|
|
|
export const LogListContext = createContext<LogListContextData>({
|
|
|
|
|
app: CoreApp.Unknown,
|
|
|
|
|
dedupStrategy: LogsDedupStrategy.none,
|
|
|
|
|
displayedFields: [],
|
2025-04-07 22:38:55 +08:00
|
|
|
downloadLogs: () => {},
|
2025-04-04 20:53:12 +08:00
|
|
|
filterLevels: [],
|
2025-04-11 02:47:17 +08:00
|
|
|
hasUnescapedContent: false,
|
2025-04-04 20:53:12 +08:00
|
|
|
setDedupStrategy: () => {},
|
|
|
|
|
setDisplayedFields: () => {},
|
|
|
|
|
setFilterLevels: () => {},
|
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: () => {},
|
|
|
|
|
setWrapLogMessage: () => {},
|
|
|
|
|
showTime: true,
|
|
|
|
|
sortOrder: LogsSortOrder.Ascending,
|
|
|
|
|
syntaxHighlighting: true,
|
|
|
|
|
wrapLogMessage: false,
|
|
|
|
|
});
|
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);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const useLogIsPinned = (log: LogRowModel) => {
|
|
|
|
|
const { pinnedLogs } = useContext(LogListContext);
|
|
|
|
|
return pinnedLogs?.some((logId) => logId === log.rowId);
|
|
|
|
|
};
|
2025-04-04 20:53:12 +08:00
|
|
|
|
|
|
|
|
export type LogListState = Pick<
|
|
|
|
|
LogListContextData,
|
|
|
|
|
| 'dedupStrategy'
|
|
|
|
|
| 'displayedFields'
|
2025-04-11 02:47:17 +08:00
|
|
|
| 'forceEscape'
|
2025-04-04 20:53:12 +08:00
|
|
|
| 'filterLevels'
|
2025-04-11 02:47:17 +08:00
|
|
|
| 'hasUnescapedContent'
|
2025-04-04 20:53:12 +08:00
|
|
|
| 'pinnedLogs'
|
2025-04-07 22:38:55 +08:00
|
|
|
| 'prettifyJSON'
|
|
|
|
|
| 'showUniqueLabels'
|
2025-04-04 20:53:12 +08:00
|
|
|
| 'showTime'
|
|
|
|
|
| 'sortOrder'
|
|
|
|
|
| 'syntaxHighlighting'
|
|
|
|
|
| 'wrapLogMessage'
|
|
|
|
|
>;
|
|
|
|
|
|
|
|
|
|
export interface Props {
|
|
|
|
|
app: CoreApp;
|
|
|
|
|
children?: ReactNode;
|
|
|
|
|
dedupStrategy: LogsDedupStrategy;
|
|
|
|
|
displayedFields: string[];
|
|
|
|
|
filterLevels?: LogLevel[];
|
2025-04-11 02:47:17 +08:00
|
|
|
forceEscape?: boolean;
|
|
|
|
|
hasUnescapedContent?: boolean;
|
2025-04-04 20:53:12 +08:00
|
|
|
getRowContextQuery?: GetRowContextQueryFn;
|
2025-04-07 22:38:55 +08:00
|
|
|
logs: LogRowModel[];
|
|
|
|
|
logsMeta?: LogsMetaItem[];
|
2025-04-04 20:53:12 +08:00
|
|
|
logOptionsStorageKey?: string;
|
|
|
|
|
logSupportsContext?: (row: LogRowModel) => boolean;
|
|
|
|
|
onLogOptionsChange?: (option: keyof LogListState, value: string | boolean | string[]) => void;
|
|
|
|
|
onLogLineHover?: (row?: LogRowModel) => void;
|
|
|
|
|
onPermalinkClick?: (row: LogRowModel) => Promise<void>;
|
|
|
|
|
onPinLine?: (row: LogRowModel) => void;
|
|
|
|
|
onOpenContext?: (row: LogRowModel, onClose: () => void) => void;
|
|
|
|
|
onUnpinLine?: (row: LogRowModel) => void;
|
|
|
|
|
pinLineButtonTooltipTitle?: PopoverContent;
|
|
|
|
|
pinnedLogs?: string[];
|
2025-04-07 22:38:55 +08:00
|
|
|
prettifyJSON?: boolean;
|
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;
|
|
|
|
|
wrapLogMessage: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const LogListContextProvider = ({
|
|
|
|
|
app,
|
|
|
|
|
children,
|
|
|
|
|
dedupStrategy,
|
|
|
|
|
displayedFields,
|
2025-04-11 02:47:17 +08:00
|
|
|
filterLevels,
|
|
|
|
|
forceEscape = false,
|
|
|
|
|
hasUnescapedContent,
|
2025-04-04 20:53:12 +08:00
|
|
|
getRowContextQuery,
|
2025-04-07 22:38:55 +08:00
|
|
|
logs,
|
|
|
|
|
logsMeta,
|
2025-04-04 20:53:12 +08:00
|
|
|
logOptionsStorageKey,
|
|
|
|
|
logSupportsContext,
|
|
|
|
|
onLogOptionsChange,
|
|
|
|
|
onLogLineHover,
|
|
|
|
|
onPermalinkClick,
|
|
|
|
|
onPinLine,
|
|
|
|
|
onOpenContext,
|
|
|
|
|
onUnpinLine,
|
|
|
|
|
pinLineButtonTooltipTitle,
|
|
|
|
|
pinnedLogs,
|
2025-04-07 22:38:55 +08:00
|
|
|
prettifyJSON,
|
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-04-04 20:53:12 +08:00
|
|
|
wrapLogMessage,
|
|
|
|
|
}: Props) => {
|
|
|
|
|
const [logListState, setLogListState] = useState<LogListState>({
|
|
|
|
|
dedupStrategy,
|
|
|
|
|
displayedFields,
|
|
|
|
|
filterLevels:
|
|
|
|
|
filterLevels ?? (logOptionsStorageKey ? store.getObject(`${logOptionsStorageKey}.filterLevels`, []) : []),
|
2025-04-11 02:47:17 +08:00
|
|
|
forceEscape,
|
|
|
|
|
hasUnescapedContent,
|
2025-04-04 20:53:12 +08:00
|
|
|
pinnedLogs,
|
2025-04-07 22:38:55 +08:00
|
|
|
prettifyJSON,
|
2025-04-04 20:53:12 +08:00
|
|
|
showTime,
|
2025-04-07 22:38:55 +08:00
|
|
|
showUniqueLabels,
|
2025-04-04 20:53:12 +08:00
|
|
|
sortOrder,
|
|
|
|
|
syntaxHighlighting,
|
|
|
|
|
wrapLogMessage,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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.displayedFields, displayedFields)) {
|
|
|
|
|
newState.displayedFields = displayedFields;
|
|
|
|
|
}
|
|
|
|
|
if (!shallowCompare(logListState.pinnedLogs ?? [], pinnedLogs ?? [])) {
|
|
|
|
|
newState.pinnedLogs = pinnedLogs;
|
|
|
|
|
}
|
|
|
|
|
if (!shallowCompare(logListState, newState)) {
|
|
|
|
|
setLogListState(newState);
|
|
|
|
|
}
|
|
|
|
|
}, [
|
|
|
|
|
app,
|
|
|
|
|
dedupStrategy,
|
|
|
|
|
displayedFields,
|
|
|
|
|
logListState,
|
|
|
|
|
pinnedLogs,
|
|
|
|
|
showControls,
|
|
|
|
|
showTime,
|
|
|
|
|
sortOrder,
|
|
|
|
|
syntaxHighlighting,
|
|
|
|
|
wrapLogMessage,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (filterLevels === undefined) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!shallowCompare(logListState.filterLevels, filterLevels)) {
|
|
|
|
|
setLogListState({ ...logListState, filterLevels });
|
|
|
|
|
}
|
|
|
|
|
}, [filterLevels, logListState]);
|
|
|
|
|
|
2025-04-11 02:47:17 +08:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (logListState.hasUnescapedContent !== hasUnescapedContent) {
|
|
|
|
|
setLogListState({ ...logListState, hasUnescapedContent });
|
|
|
|
|
}
|
|
|
|
|
}, [hasUnescapedContent, logListState]);
|
|
|
|
|
|
2025-04-04 20:53:12 +08:00
|
|
|
const setDedupStrategy = useCallback(
|
|
|
|
|
(dedupStrategy: LogsDedupStrategy) => {
|
|
|
|
|
setLogListState({ ...logListState, dedupStrategy });
|
|
|
|
|
onLogOptionsChange?.('dedupStrategy', dedupStrategy);
|
|
|
|
|
},
|
|
|
|
|
[logListState, onLogOptionsChange]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const setDisplayedFields = useCallback(
|
|
|
|
|
(displayedFields: string[]) => {
|
|
|
|
|
setLogListState({ ...logListState, displayedFields });
|
|
|
|
|
onLogOptionsChange?.('displayedFields', displayedFields);
|
|
|
|
|
},
|
|
|
|
|
[logListState, onLogOptionsChange]
|
|
|
|
|
);
|
|
|
|
|
|
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) => {
|
|
|
|
|
setLogListState({ ...logListState, showTime });
|
|
|
|
|
onLogOptionsChange?.('showTime', showTime);
|
|
|
|
|
if (logOptionsStorageKey) {
|
|
|
|
|
store.set(`${logOptionsStorageKey}.showTime`, showTime);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[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) => {
|
|
|
|
|
setLogListState({ ...logListState, prettifyJSON });
|
|
|
|
|
onLogOptionsChange?.('prettifyJSON', prettifyJSON);
|
|
|
|
|
if (logOptionsStorageKey) {
|
|
|
|
|
store.set(`${logOptionsStorageKey}.prettifyLogMessage`, prettifyJSON);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[logListState, logOptionsStorageKey, onLogOptionsChange]
|
|
|
|
|
);
|
|
|
|
|
|
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) => {
|
|
|
|
|
setLogListState({ ...logListState, wrapLogMessage });
|
|
|
|
|
onLogOptionsChange?.('wrapLogMessage', wrapLogMessage);
|
|
|
|
|
if (logOptionsStorageKey) {
|
|
|
|
|
store.set(`${logOptionsStorageKey}.wrapLogMessage`, wrapLogMessage);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[logListState, logOptionsStorageKey, onLogOptionsChange]
|
|
|
|
|
);
|
|
|
|
|
|
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-04-04 20:53:12 +08:00
|
|
|
return (
|
|
|
|
|
<LogListContext.Provider
|
|
|
|
|
value={{
|
|
|
|
|
app,
|
|
|
|
|
dedupStrategy: logListState.dedupStrategy,
|
|
|
|
|
displayedFields: logListState.displayedFields,
|
2025-04-07 22:38:55 +08:00
|
|
|
downloadLogs,
|
2025-04-04 20:53:12 +08:00
|
|
|
filterLevels: logListState.filterLevels,
|
2025-04-11 02:47:17 +08:00
|
|
|
forceEscape: logListState.forceEscape,
|
|
|
|
|
hasUnescapedContent: logListState.hasUnescapedContent,
|
2025-04-04 20:53:12 +08:00
|
|
|
getRowContextQuery,
|
|
|
|
|
logSupportsContext,
|
|
|
|
|
onLogLineHover,
|
|
|
|
|
onPermalinkClick,
|
|
|
|
|
onPinLine,
|
|
|
|
|
onOpenContext,
|
|
|
|
|
onUnpinLine,
|
|
|
|
|
pinLineButtonTooltipTitle,
|
|
|
|
|
pinnedLogs: logListState.pinnedLogs,
|
2025-04-07 22:38:55 +08:00
|
|
|
prettifyJSON: logListState.prettifyJSON,
|
2025-04-04 20:53:12 +08:00
|
|
|
setDedupStrategy,
|
|
|
|
|
setDisplayedFields,
|
|
|
|
|
setFilterLevels,
|
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,
|
|
|
|
|
setWrapLogMessage,
|
|
|
|
|
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,
|
|
|
|
|
wrapLogMessage: logListState.wrapLogMessage,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{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
|
|
|
|
|
);
|
|
|
|
|
}
|