mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-06-04 11:25:52 +08:00
feat: add list and table views for logs (#2163)
* feat: add list and table views for logs * chore: some of the changes are updated * chore: some of the refactoring is done * chore: px to updated to rem * chore: constant is moved to local storage * refactor: some of the refactoring is updated * chore: some of the changes are updated * fix: resize log table issue * chore: logs is updated * chore: resize header is updated * chore: font observer is added in package json and hook is added for same * chore: no logs text is updated * chore: no logs text is updated * chore: updated some feedback in raw logs line * chore: types is added --------- Co-authored-by: Palash Gupta <palashgdev@gmail.com> Co-authored-by: Pranay Prateek <pranay@signoz.io> Co-authored-by: Vishal Sharma <makeavish786@gmail.com> Co-authored-by: Chintan Sudani <csudani7@gmail.com>
This commit is contained in:
parent
8965b9b503
commit
bad80def90
@ -55,6 +55,7 @@
|
|||||||
"event-source-polyfill": "1.0.31",
|
"event-source-polyfill": "1.0.31",
|
||||||
"file-loader": "6.1.1",
|
"file-loader": "6.1.1",
|
||||||
"flat": "^5.0.2",
|
"flat": "^5.0.2",
|
||||||
|
"fontfaceobserver": "2.3.0",
|
||||||
"history": "4.10.1",
|
"history": "4.10.1",
|
||||||
"html-webpack-plugin": "5.1.0",
|
"html-webpack-plugin": "5.1.0",
|
||||||
"i18next": "^21.6.12",
|
"i18next": "^21.6.12",
|
||||||
@ -127,6 +128,7 @@
|
|||||||
"@types/d3-tip": "^3.5.5",
|
"@types/d3-tip": "^3.5.5",
|
||||||
"@types/event-source-polyfill": "^1.0.0",
|
"@types/event-source-polyfill": "^1.0.0",
|
||||||
"@types/flat": "^5.0.2",
|
"@types/flat": "^5.0.2",
|
||||||
|
"@types/fontfaceobserver": "2.1.0",
|
||||||
"@types/jest": "^27.5.1",
|
"@types/jest": "^27.5.1",
|
||||||
"@types/lodash-es": "^4.17.4",
|
"@types/lodash-es": "^4.17.4",
|
||||||
"@types/mini-css-extract-plugin": "^2.5.1",
|
"@types/mini-css-extract-plugin": "^2.5.1",
|
||||||
|
@ -4,17 +4,21 @@ import { Button, Divider, Row, Typography } from 'antd';
|
|||||||
import { map } from 'd3';
|
import { map } from 'd3';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
|
// utils
|
||||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useCopyToClipboard } from 'react-use';
|
import { useCopyToClipboard } from 'react-use';
|
||||||
|
// interfaces
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
import { ILogsReducer } from 'types/reducer/logs';
|
import { ILogsReducer } from 'types/reducer/logs';
|
||||||
|
|
||||||
|
// components
|
||||||
import AddToQueryHOC from '../AddToQueryHOC';
|
import AddToQueryHOC from '../AddToQueryHOC';
|
||||||
import CopyClipboardHOC from '../CopyClipboardHOC';
|
import CopyClipboardHOC from '../CopyClipboardHOC';
|
||||||
|
// styles
|
||||||
import { Container, LogContainer, Text, TextContainer } from './styles';
|
import { Container, LogContainer, Text, TextContainer } from './styles';
|
||||||
import { isValidLogField } from './util';
|
import { isValidLogField } from './util';
|
||||||
|
|
||||||
@ -37,6 +41,7 @@ function LogGeneralField({ fieldKey, fieldValue }: LogFieldProps): JSX.Element {
|
|||||||
</TextContainer>
|
</TextContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function LogSelectedField({
|
function LogSelectedField({
|
||||||
fieldKey = '',
|
fieldKey = '',
|
||||||
fieldValue = '',
|
fieldValue = '',
|
||||||
@ -70,15 +75,17 @@ function LogSelectedField({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LogItemProps {
|
interface ListLogViewProps {
|
||||||
logData: ILog;
|
logData: ILog;
|
||||||
}
|
}
|
||||||
function LogItem({ logData }: LogItemProps): JSX.Element {
|
function ListLogView({ logData }: ListLogViewProps): JSX.Element {
|
||||||
const {
|
const {
|
||||||
fields: { selected },
|
fields: { selected },
|
||||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const flattenLogData = useMemo(() => FlatLogData(logData), [logData]);
|
const flattenLogData = useMemo(() => FlatLogData(logData), [logData]);
|
||||||
|
|
||||||
const [, setCopy] = useCopyToClipboard();
|
const [, setCopy] = useCopyToClipboard();
|
||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
@ -152,4 +159,4 @@ function LogItem({ logData }: LogItemProps): JSX.Element {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LogItem;
|
export default ListLogView;
|
5
frontend/src/components/Logs/RawLogView/config.ts
Normal file
5
frontend/src/components/Logs/RawLogView/config.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export const rawLineStyle: React.CSSProperties = {
|
||||||
|
marginBottom: 0,
|
||||||
|
fontFamily: "'Fira Code', monospace",
|
||||||
|
fontWeight: 300,
|
||||||
|
};
|
48
frontend/src/components/Logs/RawLogView/index.tsx
Normal file
48
frontend/src/components/Logs/RawLogView/index.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { ExpandAltOutlined } from '@ant-design/icons';
|
||||||
|
import { Typography } from 'antd';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
// hooks
|
||||||
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
|
import React, { useCallback, useMemo } from 'react';
|
||||||
|
// interfaces
|
||||||
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
|
||||||
|
import { rawLineStyle } from './config';
|
||||||
|
// styles
|
||||||
|
import { ExpandIconWrapper, RawLogViewContainer } from './styles';
|
||||||
|
|
||||||
|
interface RawLogViewProps {
|
||||||
|
data: ILog;
|
||||||
|
linesPerRow: number;
|
||||||
|
onClickExpand: (log: ILog) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RawLogView(props: RawLogViewProps): JSX.Element {
|
||||||
|
const { data, linesPerRow, onClickExpand } = props;
|
||||||
|
|
||||||
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
|
const text = useMemo(
|
||||||
|
() => `${dayjs(data.timestamp / 1e6).format()} | ${data.body}`,
|
||||||
|
[data.timestamp, data.body],
|
||||||
|
);
|
||||||
|
|
||||||
|
const ellipsis = useMemo(() => ({ rows: linesPerRow }), [linesPerRow]);
|
||||||
|
|
||||||
|
const handleClickExpand = useCallback(() => {
|
||||||
|
onClickExpand(data);
|
||||||
|
}, [onClickExpand, data]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RawLogViewContainer wrap={false} align="middle" $isDarkMode={isDarkMode}>
|
||||||
|
<ExpandIconWrapper flex="30px" onClick={handleClickExpand}>
|
||||||
|
<ExpandAltOutlined />
|
||||||
|
</ExpandIconWrapper>
|
||||||
|
<Typography.Paragraph style={rawLineStyle} ellipsis={ellipsis}>
|
||||||
|
{text}
|
||||||
|
</Typography.Paragraph>
|
||||||
|
</RawLogViewContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RawLogView;
|
24
frontend/src/components/Logs/RawLogView/styles.ts
Normal file
24
frontend/src/components/Logs/RawLogView/styles.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { blue } from '@ant-design/colors';
|
||||||
|
import { Col, Row } from 'antd';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const RawLogViewContainer = styled(Row)<{ $isDarkMode: boolean }>`
|
||||||
|
width: 100%;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.625rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
|
||||||
|
transition: background-color 0.2s ease-in;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: ${({ $isDarkMode }): string =>
|
||||||
|
$isDarkMode ? 'rgba(255,255,255,0.1)' : 'rgba(0, 0, 0, 0.1)'};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ExpandIconWrapper = styled(Col)`
|
||||||
|
color: ${blue[6]};
|
||||||
|
padding: 0.25rem 0.375rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
`;
|
12
frontend/src/components/Logs/TableView/config.ts
Normal file
12
frontend/src/components/Logs/TableView/config.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { TableProps } from 'antd';
|
||||||
|
|
||||||
|
export const defaultCellStyle: React.CSSProperties = {
|
||||||
|
paddingTop: 4,
|
||||||
|
paddingBottom: 6,
|
||||||
|
paddingRight: 8,
|
||||||
|
paddingLeft: 8,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const tableScroll: TableProps<Record<string, unknown>>['scroll'] = {
|
||||||
|
x: true,
|
||||||
|
};
|
106
frontend/src/components/Logs/TableView/index.tsx
Normal file
106
frontend/src/components/Logs/TableView/index.tsx
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import { ExpandAltOutlined } from '@ant-design/icons';
|
||||||
|
import { Table, Typography } from 'antd';
|
||||||
|
import { ColumnsType, ColumnType } from 'antd/es/table';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
// utils
|
||||||
|
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { IField } from 'types/api/logs/fields';
|
||||||
|
// interfaces
|
||||||
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
|
||||||
|
// styles
|
||||||
|
import { ExpandIconWrapper } from '../RawLogView/styles';
|
||||||
|
// config
|
||||||
|
import { defaultCellStyle, tableScroll } from './config';
|
||||||
|
|
||||||
|
type ColumnTypeRender<T = unknown> = ReturnType<
|
||||||
|
NonNullable<ColumnType<T>['render']>
|
||||||
|
>;
|
||||||
|
|
||||||
|
type LogsTableViewProps = {
|
||||||
|
logs: ILog[];
|
||||||
|
fields: IField[];
|
||||||
|
linesPerRow: number;
|
||||||
|
onClickExpand: (log: ILog) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function LogsTableView(props: LogsTableViewProps): JSX.Element {
|
||||||
|
const { logs, fields, linesPerRow, onClickExpand } = props;
|
||||||
|
|
||||||
|
const flattenLogData = useMemo(() => logs.map((log) => FlatLogData(log)), [
|
||||||
|
logs,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const columns: ColumnsType<Record<string, unknown>> = useMemo(() => {
|
||||||
|
const fieldColumns: ColumnsType<Record<string, unknown>> = fields.map(
|
||||||
|
({ name }) => ({
|
||||||
|
title: name,
|
||||||
|
dataIndex: name,
|
||||||
|
key: name,
|
||||||
|
render: (field): ColumnTypeRender<Record<string, unknown>> => ({
|
||||||
|
props: {
|
||||||
|
style: defaultCellStyle,
|
||||||
|
},
|
||||||
|
children: (
|
||||||
|
<Typography.Paragraph ellipsis={{ rows: linesPerRow }}>
|
||||||
|
{field}
|
||||||
|
</Typography.Paragraph>
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: '',
|
||||||
|
dataIndex: 'id',
|
||||||
|
key: 'expand',
|
||||||
|
// https://github.com/ant-design/ant-design/discussions/36886
|
||||||
|
render: (_, item): ColumnTypeRender<Record<string, unknown>> => ({
|
||||||
|
props: {
|
||||||
|
style: defaultCellStyle,
|
||||||
|
},
|
||||||
|
children: (
|
||||||
|
<ExpandIconWrapper
|
||||||
|
onClick={(): void => {
|
||||||
|
onClickExpand((item as unknown) as ILog);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ExpandAltOutlined />
|
||||||
|
</ExpandIconWrapper>
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Timestamp',
|
||||||
|
dataIndex: 'timestamp',
|
||||||
|
key: 'timestamp',
|
||||||
|
// https://github.com/ant-design/ant-design/discussions/36886
|
||||||
|
render: (field): ColumnTypeRender<Record<string, unknown>> => {
|
||||||
|
const date = dayjs(field / 1e6).format();
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
style: defaultCellStyle,
|
||||||
|
},
|
||||||
|
children: <span>{date}</span>,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...fieldColumns,
|
||||||
|
];
|
||||||
|
}, [fields, linesPerRow, onClickExpand]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={flattenLogData}
|
||||||
|
pagination={false}
|
||||||
|
rowKey="id"
|
||||||
|
bordered
|
||||||
|
scroll={tableScroll}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LogsTableView;
|
@ -17,13 +17,6 @@ function ResizableHeader(props: ResizableHeaderProps): JSX.Element {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const draggableOpts = useMemo(
|
|
||||||
() => ({
|
|
||||||
enableUserSelectHack,
|
|
||||||
}),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!width) {
|
if (!width) {
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
return <th {...restProps} />;
|
return <th {...restProps} />;
|
||||||
@ -35,7 +28,7 @@ function ResizableHeader(props: ResizableHeaderProps): JSX.Element {
|
|||||||
height={0}
|
height={0}
|
||||||
handle={handle}
|
handle={handle}
|
||||||
onResize={onResize}
|
onResize={onResize}
|
||||||
draggableOpts={draggableOpts}
|
draggableOpts={enableUserSelectHack}
|
||||||
>
|
>
|
||||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||||
<th {...restProps} />
|
<th {...restProps} />
|
||||||
|
@ -4,4 +4,6 @@ export enum LOCALSTORAGE {
|
|||||||
AUTH_TOKEN = 'AUTH_TOKEN',
|
AUTH_TOKEN = 'AUTH_TOKEN',
|
||||||
REFRESH_AUTH_TOKEN = 'REFRESH_AUTH_TOKEN',
|
REFRESH_AUTH_TOKEN = 'REFRESH_AUTH_TOKEN',
|
||||||
THEME = 'THEME',
|
THEME = 'THEME',
|
||||||
|
LOGS_VIEW_MODE = 'LOGS_VIEW_MODE',
|
||||||
|
LOGS_LINES_PER_ROW = 'LOGS_LINES_PER_ROW',
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,12 @@ import {
|
|||||||
import { Button, Divider, Select } from 'antd';
|
import { Button, Divider, Select } from 'antd';
|
||||||
import { getGlobalTime } from 'container/LogsSearchFilter/utils';
|
import { getGlobalTime } from 'container/LogsSearchFilter/utils';
|
||||||
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
|
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
|
||||||
|
import { defaultSelectStyle } from 'pages/Logs/config';
|
||||||
import React, { memo, useMemo } from 'react';
|
import React, { memo, useMemo } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
|
import AppActions from 'types/actions';
|
||||||
import {
|
import {
|
||||||
GET_NEXT_LOG_LINES,
|
GET_NEXT_LOG_LINES,
|
||||||
GET_PREVIOUS_LOG_LINES,
|
GET_PREVIOUS_LOG_LINES,
|
||||||
@ -21,8 +24,6 @@ import { ILogsReducer } from 'types/reducer/logs';
|
|||||||
import { ITEMS_PER_PAGE_OPTIONS } from './config';
|
import { ITEMS_PER_PAGE_OPTIONS } from './config';
|
||||||
import { Container } from './styles';
|
import { Container } from './styles';
|
||||||
|
|
||||||
const { Option } = Select;
|
|
||||||
|
|
||||||
function LogControls(): JSX.Element | null {
|
function LogControls(): JSX.Element | null {
|
||||||
const {
|
const {
|
||||||
logLinesPerPage,
|
logLinesPerPage,
|
||||||
@ -34,13 +35,14 @@ function LogControls(): JSX.Element | null {
|
|||||||
const globalTime = useSelector<AppState, GlobalReducer>(
|
const globalTime = useSelector<AppState, GlobalReducer>(
|
||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
);
|
);
|
||||||
const dispatch = useDispatch();
|
|
||||||
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
|
|
||||||
const handleLogLinesPerPageChange = (e: number): void => {
|
const handleLogLinesPerPageChange = (e: number): void => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SET_LOG_LINES_PER_PAGE,
|
type: SET_LOG_LINES_PER_PAGE,
|
||||||
payload: {
|
payload: {
|
||||||
logLinesPerPage: e,
|
logsLinesPerPage: e,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -52,13 +54,17 @@ function LogControls(): JSX.Element | null {
|
|||||||
globalTime.maxTime,
|
globalTime.maxTime,
|
||||||
);
|
);
|
||||||
|
|
||||||
dispatch({
|
const updatedGlobalTime = getGlobalTime(globalTime.selectedTime, {
|
||||||
type: RESET_ID_START_AND_END,
|
maxTime,
|
||||||
payload: getGlobalTime(globalTime.selectedTime, {
|
minTime,
|
||||||
maxTime,
|
|
||||||
minTime,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (updatedGlobalTime) {
|
||||||
|
dispatch({
|
||||||
|
type: RESET_ID_START_AND_END,
|
||||||
|
payload: updatedGlobalTime,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNavigatePrevious = (): void => {
|
const handleNavigatePrevious = (): void => {
|
||||||
@ -117,12 +123,16 @@ function LogControls(): JSX.Element | null {
|
|||||||
Next <RightOutlined />
|
Next <RightOutlined />
|
||||||
</Button>
|
</Button>
|
||||||
<Select
|
<Select
|
||||||
|
style={defaultSelectStyle}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
value={logLinesPerPage}
|
value={logLinesPerPage}
|
||||||
onChange={handleLogLinesPerPageChange}
|
onChange={handleLogLinesPerPageChange}
|
||||||
>
|
>
|
||||||
{ITEMS_PER_PAGE_OPTIONS.map((count) => (
|
{ITEMS_PER_PAGE_OPTIONS.map((count) => (
|
||||||
<Option key={count} value={count}>{`${count} / page`}</Option>
|
<Select.Option
|
||||||
|
key={count}
|
||||||
|
value={count}
|
||||||
|
>{`${count} / page`}</Select.Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Container>
|
</Container>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { CloseOutlined, PlusCircleFilled } from '@ant-design/icons';
|
import { CloseOutlined, PlusCircleFilled } from '@ant-design/icons';
|
||||||
import { Input } from 'antd';
|
import { Col, Input } from 'antd';
|
||||||
import CategoryHeading from 'components/Logs/CategoryHeading';
|
import CategoryHeading from 'components/Logs/CategoryHeading';
|
||||||
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
|
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
@ -9,7 +9,7 @@ import { ILogsReducer } from 'types/reducer/logs';
|
|||||||
|
|
||||||
import { ICON_STYLE } from './config';
|
import { ICON_STYLE } from './config';
|
||||||
import FieldItem from './FieldItem';
|
import FieldItem from './FieldItem';
|
||||||
import { CategoryContainer, Container, FieldContainer } from './styles';
|
import { CategoryContainer, FieldContainer } from './styles';
|
||||||
import { IHandleInterestProps, IHandleRemoveInterestProps } from './types';
|
import { IHandleInterestProps, IHandleRemoveInterestProps } from './types';
|
||||||
import { onHandleAddInterest, onHandleRemoveInterest } from './utils';
|
import { onHandleAddInterest, onHandleRemoveInterest } from './utils';
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ function LogsFilters(): JSX.Element {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container flex="450px">
|
<Col flex="250px">
|
||||||
<Input
|
<Input
|
||||||
placeholder="Filter Values"
|
placeholder="Filter Values"
|
||||||
onInput={handleSearch}
|
onInput={handleSearch}
|
||||||
@ -110,7 +110,7 @@ function LogsFilters(): JSX.Element {
|
|||||||
))}
|
))}
|
||||||
</FieldContainer>
|
</FieldContainer>
|
||||||
</CategoryContainer>
|
</CategoryContainer>
|
||||||
</Container>
|
</Col>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,7 @@
|
|||||||
import { blue, grey } from '@ant-design/colors';
|
import { blue, grey } from '@ant-design/colors';
|
||||||
import { Col, Typography } from 'antd';
|
import { Typography } from 'antd';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
export const Container = styled(Col)`
|
|
||||||
padding-top: 0.3rem;
|
|
||||||
min-width: 15.625rem;
|
|
||||||
max-width: 21.875rem;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const CategoryContainer = styled.div`
|
export const CategoryContainer = styled.div`
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
padding-left: 0.2rem;
|
padding-left: 0.2rem;
|
||||||
|
@ -48,6 +48,7 @@ function SearchFilter({
|
|||||||
AppState,
|
AppState,
|
||||||
ILogsReducer
|
ILogsReducer
|
||||||
>((state) => state.logs);
|
>((state) => state.logs);
|
||||||
|
|
||||||
const globalTime = useSelector<AppState, GlobalReducer>(
|
const globalTime = useSelector<AppState, GlobalReducer>(
|
||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
);
|
);
|
||||||
|
@ -1,19 +1,53 @@
|
|||||||
import { Typography } from 'antd';
|
import { Card, Typography } from 'antd';
|
||||||
import LogItem from 'components/Logs/LogItem';
|
// components
|
||||||
|
import ListLogView from 'components/Logs/ListLogView';
|
||||||
|
import RawLogView from 'components/Logs/RawLogView';
|
||||||
|
import LogsTableView from 'components/Logs/TableView';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
|
import { contentStyle } from 'container/Trace/Search/config';
|
||||||
|
import useFontFaceObserver from 'hooks/useFontObserver';
|
||||||
import React, { memo, useCallback, useMemo } from 'react';
|
import React, { memo, useCallback, useMemo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { Virtuoso } from 'react-virtuoso';
|
import { Virtuoso } from 'react-virtuoso';
|
||||||
|
// interfaces
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
|
import { ILog } from 'types/api/logs/log';
|
||||||
import { ILogsReducer } from 'types/reducer/logs';
|
import { ILogsReducer } from 'types/reducer/logs';
|
||||||
|
|
||||||
|
// styles
|
||||||
import { Container, Heading } from './styles';
|
import { Container, Heading } from './styles';
|
||||||
|
|
||||||
function LogsTable(): JSX.Element {
|
export type LogViewMode = 'raw' | 'table' | 'list';
|
||||||
const { logs, isLoading, liveTail } = useSelector<AppState, ILogsReducer>(
|
|
||||||
(state) => state.logs,
|
type LogsTableProps = {
|
||||||
|
viewMode: LogViewMode;
|
||||||
|
linesPerRow: number;
|
||||||
|
onClickExpand: (logData: ILog) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function LogsTable(props: LogsTableProps): JSX.Element {
|
||||||
|
const { viewMode, onClickExpand, linesPerRow } = props;
|
||||||
|
|
||||||
|
useFontFaceObserver(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
family: 'Fira Code',
|
||||||
|
weight: '300',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
viewMode === 'raw',
|
||||||
|
{
|
||||||
|
timeout: 5000,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
logs,
|
||||||
|
fields: { selected },
|
||||||
|
isLoading,
|
||||||
|
liveTail,
|
||||||
|
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||||
|
|
||||||
const isLiveTail = useMemo(() => logs.length === 0 && liveTail === 'PLAYING', [
|
const isLiveTail = useMemo(() => logs.length === 0 && liveTail === 'PLAYING', [
|
||||||
logs?.length,
|
logs?.length,
|
||||||
liveTail,
|
liveTail,
|
||||||
@ -27,29 +61,63 @@ function LogsTable(): JSX.Element {
|
|||||||
const getItemContent = useCallback(
|
const getItemContent = useCallback(
|
||||||
(index: number): JSX.Element => {
|
(index: number): JSX.Element => {
|
||||||
const log = logs[index];
|
const log = logs[index];
|
||||||
return <LogItem key={log.id} logData={log} />;
|
|
||||||
|
if (viewMode === 'raw') {
|
||||||
|
return (
|
||||||
|
<RawLogView
|
||||||
|
key={log.id}
|
||||||
|
data={log}
|
||||||
|
linesPerRow={linesPerRow}
|
||||||
|
onClickExpand={onClickExpand}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ListLogView key={log.id} logData={log} />;
|
||||||
},
|
},
|
||||||
[logs],
|
[logs, linesPerRow, viewMode, onClickExpand],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const renderContent = useMemo(() => {
|
||||||
|
if (viewMode === 'table') {
|
||||||
|
return (
|
||||||
|
<LogsTableView
|
||||||
|
logs={logs}
|
||||||
|
fields={selected}
|
||||||
|
linesPerRow={linesPerRow}
|
||||||
|
onClickExpand={onClickExpand}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card bodyStyle={contentStyle}>
|
||||||
|
<Virtuoso
|
||||||
|
useWindowScroll
|
||||||
|
totalCount={logs.length}
|
||||||
|
itemContent={getItemContent}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}, [getItemContent, linesPerRow, logs, onClickExpand, selected, viewMode]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <Spinner height={20} tip="Getting Logs" />;
|
return <Spinner height={20} tip="Getting Logs" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container flex="auto">
|
<Container>
|
||||||
<Heading>
|
{viewMode !== 'table' && (
|
||||||
<Typography.Text>Event</Typography.Text>
|
<Heading>
|
||||||
</Heading>
|
<Typography.Text>Event</Typography.Text>
|
||||||
|
</Heading>
|
||||||
|
)}
|
||||||
|
|
||||||
{isLiveTail && <Typography>Getting live logs...</Typography>}
|
{isLiveTail && <Typography>Getting live logs...</Typography>}
|
||||||
|
|
||||||
{isNoLogs && <Typography>No log lines found</Typography>}
|
{isNoLogs && <Typography>No logs lines found</Typography>}
|
||||||
|
|
||||||
<Virtuoso
|
{renderContent}
|
||||||
useWindowScroll
|
|
||||||
totalCount={logs.length}
|
|
||||||
itemContent={getItemContent}
|
|
||||||
/>
|
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import { Card, Col } from 'antd';
|
import { Card } from 'antd';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
export const Container = styled(Col)`
|
export const Container = styled.div`
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Heading = styled(Card)`
|
export const Heading = styled(Card)`
|
||||||
margin-bottom: 0.1rem;
|
margin-bottom: 0.1rem;
|
||||||
|
height: 32px;
|
||||||
.ant-card-body {
|
.ant-card-body {
|
||||||
padding: 0.3rem 0.5rem;
|
padding: 0.3rem 0.5rem;
|
||||||
}
|
}
|
||||||
|
69
frontend/src/hooks/useFontObserver.tsx
Normal file
69
frontend/src/hooks/useFontObserver.tsx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import FontFaceObserver from 'fontfaceobserver';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export interface FontFace {
|
||||||
|
family: string;
|
||||||
|
weight?:
|
||||||
|
| `light`
|
||||||
|
| `normal`
|
||||||
|
| `bold`
|
||||||
|
| `bolder`
|
||||||
|
| `100`
|
||||||
|
| `200`
|
||||||
|
| `300`
|
||||||
|
| `400`
|
||||||
|
| `500`
|
||||||
|
| `600`
|
||||||
|
| `700`
|
||||||
|
| `800`
|
||||||
|
| `900`;
|
||||||
|
style?: `normal` | `italic` | `oblique`;
|
||||||
|
stretch?:
|
||||||
|
| `normal`
|
||||||
|
| `ultra-condensed`
|
||||||
|
| `extra-condensed`
|
||||||
|
| `condensed`
|
||||||
|
| `semi-condensed`
|
||||||
|
| `semi-expanded`
|
||||||
|
| `expanded`
|
||||||
|
| `extra-expanded`
|
||||||
|
| `ultra-expanded`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Options {
|
||||||
|
testString?: string;
|
||||||
|
timeout?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Config {
|
||||||
|
showErrors: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useFontFaceObserver(
|
||||||
|
fontFaces: FontFace[] = [],
|
||||||
|
isEnabled = true,
|
||||||
|
{ testString, timeout }: Options = {},
|
||||||
|
{ showErrors }: Config = { showErrors: false },
|
||||||
|
): boolean {
|
||||||
|
const [isResolved, setIsResolved] = useState(false);
|
||||||
|
const fontFacesString = JSON.stringify(fontFaces);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isEnabled) {
|
||||||
|
const promises = JSON.parse(fontFacesString).map(
|
||||||
|
({ family, weight, style, stretch }: FontFace) =>
|
||||||
|
new FontFaceObserver(family, {
|
||||||
|
weight,
|
||||||
|
style,
|
||||||
|
stretch,
|
||||||
|
}).load(testString, timeout),
|
||||||
|
);
|
||||||
|
|
||||||
|
Promise.all(promises).then(() => setIsResolved(true));
|
||||||
|
}
|
||||||
|
}, [fontFacesString, testString, timeout, showErrors, isEnabled]);
|
||||||
|
|
||||||
|
return isResolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useFontFaceObserver;
|
@ -50,8 +50,14 @@
|
|||||||
<meta data-react-helmet="true" name="docusaurus_locale" content="en" />
|
<meta data-react-helmet="true" name="docusaurus_locale" content="en" />
|
||||||
<meta data-react-helmet="true" name="docusaurus_tag" content="default" />
|
<meta data-react-helmet="true" name="docusaurus_tag" content="default" />
|
||||||
<link data-react-helmet="true" rel="shortcut icon" href="/favicon.ico" />
|
<link data-react-helmet="true" rel="shortcut icon" href="/favicon.ico" />
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css?family=Fira+Code"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body style="margin: 0; padding: 0; box-sizing: border-box;">
|
<body style="margin: 0; padding: 0; box-sizing: border-box">
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
</body>
|
</body>
|
||||||
|
28
frontend/src/pages/Logs/PopoverContent.tsx
Normal file
28
frontend/src/pages/Logs/PopoverContent.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { InputNumber, Row, Space, Typography } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface PopoverContentProps {
|
||||||
|
linesPerRow: number;
|
||||||
|
handleLinesPerRowChange: (l: unknown) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PopoverContent({
|
||||||
|
linesPerRow,
|
||||||
|
handleLinesPerRowChange,
|
||||||
|
}: PopoverContentProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Row align="middle">
|
||||||
|
<Space align="center">
|
||||||
|
<Typography>Max lines per Row </Typography>
|
||||||
|
<InputNumber
|
||||||
|
min={1}
|
||||||
|
max={10}
|
||||||
|
value={linesPerRow}
|
||||||
|
onChange={handleLinesPerRowChange}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PopoverContent;
|
25
frontend/src/pages/Logs/config.ts
Normal file
25
frontend/src/pages/Logs/config.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { ViewModeOption } from './types';
|
||||||
|
|
||||||
|
export const viewModeOptionList: ViewModeOption[] = [
|
||||||
|
{
|
||||||
|
key: 'raw',
|
||||||
|
label: 'Raw',
|
||||||
|
value: 'raw',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'table',
|
||||||
|
label: 'Table',
|
||||||
|
value: 'table',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'list',
|
||||||
|
label: 'List',
|
||||||
|
value: 'list',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const logsOptions = ['raw', 'table'];
|
||||||
|
|
||||||
|
export const defaultSelectStyle: React.CSSProperties = {
|
||||||
|
minWidth: '6rem',
|
||||||
|
};
|
78
frontend/src/pages/Logs/hooks.ts
Normal file
78
frontend/src/pages/Logs/hooks.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// utils
|
||||||
|
import get from 'api/browser/localstorage/get';
|
||||||
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
|
// interfaces
|
||||||
|
import { LogViewMode } from 'container/LogsTable';
|
||||||
|
import { useCallback, useLayoutEffect, useMemo } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { setLinesPerRow } from 'store/actions/logs/setLInesPerRow';
|
||||||
|
// actions
|
||||||
|
import { setViewMode } from 'store/actions/logs/setViewMode';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
|
|
||||||
|
import { viewModeOptionList } from './config';
|
||||||
|
import { SelectedLogViewData } from './types';
|
||||||
|
import { isLogViewMode } from './utils';
|
||||||
|
|
||||||
|
export const useSelectedLogView = (): SelectedLogViewData => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const viewMode = useSelector<AppState, LogViewMode>(
|
||||||
|
(state) => state.logs.viewMode,
|
||||||
|
);
|
||||||
|
const linesPerRow = useSelector<AppState, number>(
|
||||||
|
(state) => state.logs.linesPerRow,
|
||||||
|
);
|
||||||
|
|
||||||
|
const viewModeOption = useMemo(
|
||||||
|
() =>
|
||||||
|
viewModeOptionList.find(
|
||||||
|
(viewModeOption) => viewModeOption.value === viewMode,
|
||||||
|
) ?? viewModeOptionList[0],
|
||||||
|
[viewMode],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleViewModeChange = useCallback(
|
||||||
|
(selectedViewMode: LogViewMode) => {
|
||||||
|
dispatch(setViewMode(selectedViewMode));
|
||||||
|
},
|
||||||
|
[dispatch],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleViewModeOptionChange = useCallback(
|
||||||
|
({ key }: { key: string }) => {
|
||||||
|
if (isLogViewMode(key)) handleViewModeChange(key);
|
||||||
|
},
|
||||||
|
[handleViewModeChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleLinesPerRowChange = useCallback(
|
||||||
|
(selectedLinesPerRow: unknown) => {
|
||||||
|
if (typeof selectedLinesPerRow === 'number') {
|
||||||
|
dispatch(setLinesPerRow(selectedLinesPerRow));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch],
|
||||||
|
);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const storedViewMode = get(LOCALSTORAGE.LOGS_VIEW_MODE);
|
||||||
|
if (storedViewMode) {
|
||||||
|
handleViewModeChange(storedViewMode as LogViewMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
const storedLinesPerRow = get(LOCALSTORAGE.LOGS_LINES_PER_ROW);
|
||||||
|
if (storedLinesPerRow) {
|
||||||
|
handleLinesPerRowChange(+storedLinesPerRow);
|
||||||
|
}
|
||||||
|
}, [handleViewModeChange, handleLinesPerRowChange]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
viewModeOptionList,
|
||||||
|
viewModeOption,
|
||||||
|
viewMode,
|
||||||
|
handleViewModeChange,
|
||||||
|
handleViewModeOptionChange,
|
||||||
|
linesPerRow,
|
||||||
|
handleLinesPerRowChange,
|
||||||
|
};
|
||||||
|
};
|
@ -1,4 +1,4 @@
|
|||||||
import { Divider, Row } from 'antd';
|
import { Button, Col, Divider, Popover, Row, Select, Space } from 'antd';
|
||||||
import LogControls from 'container/LogControls';
|
import LogControls from 'container/LogControls';
|
||||||
import LogDetailedView from 'container/LogDetailedView';
|
import LogDetailedView from 'container/LogDetailedView';
|
||||||
import LogLiveTail from 'container/LogLiveTail';
|
import LogLiveTail from 'container/LogLiveTail';
|
||||||
@ -6,11 +6,67 @@ import LogsAggregate from 'container/LogsAggregate';
|
|||||||
import LogsFilters from 'container/LogsFilters';
|
import LogsFilters from 'container/LogsFilters';
|
||||||
import LogsSearchFilter from 'container/LogsSearchFilter';
|
import LogsSearchFilter from 'container/LogsSearchFilter';
|
||||||
import LogsTable from 'container/LogsTable';
|
import LogsTable from 'container/LogsTable';
|
||||||
import React from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import AppActions from 'types/actions';
|
||||||
|
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
||||||
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
|
||||||
|
import { defaultSelectStyle, logsOptions } from './config';
|
||||||
|
import { useSelectedLogView } from './hooks';
|
||||||
|
import PopoverContent from './PopoverContent';
|
||||||
import SpaceContainer from './styles';
|
import SpaceContainer from './styles';
|
||||||
|
|
||||||
function Logs(): JSX.Element {
|
function Logs(): JSX.Element {
|
||||||
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
|
|
||||||
|
const showExpandedLog = useCallback(
|
||||||
|
(logData: ILog) => {
|
||||||
|
dispatch({
|
||||||
|
type: SET_DETAILED_LOG_DATA,
|
||||||
|
payload: logData,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[dispatch],
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
viewModeOptionList,
|
||||||
|
viewModeOption,
|
||||||
|
viewMode,
|
||||||
|
handleViewModeOptionChange,
|
||||||
|
linesPerRow,
|
||||||
|
handleLinesPerRowChange,
|
||||||
|
} = useSelectedLogView();
|
||||||
|
|
||||||
|
const renderPopoverContent = useCallback(
|
||||||
|
() => (
|
||||||
|
<PopoverContent
|
||||||
|
linesPerRow={linesPerRow}
|
||||||
|
handleLinesPerRowChange={handleLinesPerRowChange}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[linesPerRow, handleLinesPerRowChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
const isFormatButtonVisible = useMemo(() => logsOptions.includes(viewMode), [
|
||||||
|
viewMode,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const selectedViewModeOption = useMemo(() => viewModeOption.value.toString(), [
|
||||||
|
viewModeOption.value,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const onChangeVeiwMode = useCallback(
|
||||||
|
(key: string) => {
|
||||||
|
handleViewModeOptionChange({
|
||||||
|
key,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[handleViewModeOptionChange],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SpaceContainer
|
<SpaceContainer
|
||||||
@ -23,13 +79,44 @@ function Logs(): JSX.Element {
|
|||||||
</SpaceContainer>
|
</SpaceContainer>
|
||||||
|
|
||||||
<LogsAggregate />
|
<LogsAggregate />
|
||||||
<LogControls />
|
|
||||||
<Divider plain orientationMargin={1} />
|
|
||||||
<Row gutter={20} wrap={false}>
|
<Row gutter={20} wrap={false}>
|
||||||
<LogsFilters />
|
<LogsFilters />
|
||||||
<Divider type="vertical" />
|
<Col flex={1}>
|
||||||
<LogsTable />
|
<Row>
|
||||||
|
<Col flex={1}>
|
||||||
|
<Space align="baseline" direction="horizontal">
|
||||||
|
<Select
|
||||||
|
style={defaultSelectStyle}
|
||||||
|
value={selectedViewModeOption}
|
||||||
|
onChange={onChangeVeiwMode}
|
||||||
|
>
|
||||||
|
{viewModeOptionList.map((option) => (
|
||||||
|
<Select.Option key={option.value}>{option.label}</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
{isFormatButtonVisible && (
|
||||||
|
<Popover placement="right" content={renderPopoverContent}>
|
||||||
|
<Button>Format</Button>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col>
|
||||||
|
<LogControls />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<LogsTable
|
||||||
|
viewMode={viewMode}
|
||||||
|
linesPerRow={linesPerRow}
|
||||||
|
onClickExpand={showExpandedLog}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<LogDetailedView />
|
<LogDetailedView />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
17
frontend/src/pages/Logs/types.ts
Normal file
17
frontend/src/pages/Logs/types.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { ItemType } from 'antd/es/menu/hooks/useItems';
|
||||||
|
import { LogViewMode } from 'container/LogsTable';
|
||||||
|
|
||||||
|
export type ViewModeOption = ItemType & {
|
||||||
|
label: string;
|
||||||
|
value: LogViewMode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SelectedLogViewData = {
|
||||||
|
viewModeOptionList: ViewModeOption[];
|
||||||
|
viewModeOption: ViewModeOption;
|
||||||
|
viewMode: LogViewMode;
|
||||||
|
handleViewModeChange: (s: LogViewMode) => void;
|
||||||
|
handleViewModeOptionChange: ({ key }: { key: string }) => void;
|
||||||
|
linesPerRow: number;
|
||||||
|
handleLinesPerRowChange: (l: unknown) => void;
|
||||||
|
};
|
7
frontend/src/pages/Logs/utils.ts
Normal file
7
frontend/src/pages/Logs/utils.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { LogViewMode } from 'container/LogsTable';
|
||||||
|
|
||||||
|
import { viewModeOptionList } from './config';
|
||||||
|
|
||||||
|
export const isLogViewMode = (value: unknown): value is LogViewMode =>
|
||||||
|
typeof value === 'string' &&
|
||||||
|
viewModeOptionList.some((option) => option.key === value);
|
14
frontend/src/store/actions/logs/setLInesPerRow.ts
Normal file
14
frontend/src/store/actions/logs/setLInesPerRow.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import set from 'api/browser/localstorage/set';
|
||||||
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
|
import { SET_LINES_PER_ROW } from 'types/actions/logs';
|
||||||
|
|
||||||
|
type ActionSetLinesPerRow = { type: typeof SET_LINES_PER_ROW; payload: number };
|
||||||
|
|
||||||
|
export function setLinesPerRow(lines: number): ActionSetLinesPerRow {
|
||||||
|
set(LOCALSTORAGE.LOGS_LINES_PER_ROW, lines.toString());
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: SET_LINES_PER_ROW,
|
||||||
|
payload: lines,
|
||||||
|
};
|
||||||
|
}
|
15
frontend/src/store/actions/logs/setViewMode.ts
Normal file
15
frontend/src/store/actions/logs/setViewMode.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import set from 'api/browser/localstorage/set';
|
||||||
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
|
import { LogViewMode } from 'container/LogsTable';
|
||||||
|
import { SET_VIEW_MODE } from 'types/actions/logs';
|
||||||
|
|
||||||
|
type ActionSetViewMode = { type: typeof SET_VIEW_MODE; payload: LogViewMode };
|
||||||
|
|
||||||
|
export function setViewMode(viewMode: LogViewMode): ActionSetViewMode {
|
||||||
|
set(LOCALSTORAGE.LOGS_VIEW_MODE, viewMode);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: SET_VIEW_MODE,
|
||||||
|
payload: viewMode,
|
||||||
|
};
|
||||||
|
}
|
@ -10,6 +10,7 @@ import {
|
|||||||
RESET_ID_START_AND_END,
|
RESET_ID_START_AND_END,
|
||||||
SET_DETAILED_LOG_DATA,
|
SET_DETAILED_LOG_DATA,
|
||||||
SET_FIELDS,
|
SET_FIELDS,
|
||||||
|
SET_LINES_PER_ROW,
|
||||||
SET_LIVE_TAIL_START_TIME,
|
SET_LIVE_TAIL_START_TIME,
|
||||||
SET_LOADING,
|
SET_LOADING,
|
||||||
SET_LOADING_AGGREGATE,
|
SET_LOADING_AGGREGATE,
|
||||||
@ -18,6 +19,7 @@ import {
|
|||||||
SET_LOGS_AGGREGATE_SERIES,
|
SET_LOGS_AGGREGATE_SERIES,
|
||||||
SET_SEARCH_QUERY_PARSED_PAYLOAD,
|
SET_SEARCH_QUERY_PARSED_PAYLOAD,
|
||||||
SET_SEARCH_QUERY_STRING,
|
SET_SEARCH_QUERY_STRING,
|
||||||
|
SET_VIEW_MODE,
|
||||||
STOP_LIVE_TAIL,
|
STOP_LIVE_TAIL,
|
||||||
TOGGLE_LIVE_TAIL,
|
TOGGLE_LIVE_TAIL,
|
||||||
UPDATE_INTERESTING_FIELDS,
|
UPDATE_INTERESTING_FIELDS,
|
||||||
@ -36,6 +38,8 @@ const initialState: ILogsReducer = {
|
|||||||
},
|
},
|
||||||
logs: [],
|
logs: [],
|
||||||
logLinesPerPage: 25,
|
logLinesPerPage: 25,
|
||||||
|
linesPerRow: 2,
|
||||||
|
viewMode: 'raw',
|
||||||
idEnd: '',
|
idEnd: '',
|
||||||
idStart: '',
|
idStart: '',
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
@ -205,6 +209,20 @@ export const LogsReducer = (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case SET_LINES_PER_ROW: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
linesPerRow: action.payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case SET_VIEW_MODE: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
viewMode: action.payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
case UPDATE_INTERESTING_FIELDS: {
|
case UPDATE_INTERESTING_FIELDS: {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { LogViewMode } from 'container/LogsTable';
|
||||||
import { ILogQLParsedQueryItem } from 'lib/logql/types';
|
import { ILogQLParsedQueryItem } from 'lib/logql/types';
|
||||||
import { IField, IFieldMoveToSelected, IFields } from 'types/api/logs/fields';
|
import { IField, IFieldMoveToSelected, IFields } from 'types/api/logs/fields';
|
||||||
import { TLogsLiveTailState } from 'types/api/logs/liveTail';
|
import { TLogsLiveTailState } from 'types/api/logs/liveTail';
|
||||||
@ -28,6 +29,8 @@ export const PUSH_LIVE_TAIL_EVENT = 'LOGS_PUSH_LIVE_TAIL_EVENT';
|
|||||||
export const STOP_LIVE_TAIL = 'LOGS_STOP_LIVE_TAIL';
|
export const STOP_LIVE_TAIL = 'LOGS_STOP_LIVE_TAIL';
|
||||||
export const FLUSH_LOGS = 'LOGS_FLUSH_LOGS';
|
export const FLUSH_LOGS = 'LOGS_FLUSH_LOGS';
|
||||||
export const SET_LIVE_TAIL_START_TIME = 'LOGS_SET_LIVE_TAIL_START_TIME';
|
export const SET_LIVE_TAIL_START_TIME = 'LOGS_SET_LIVE_TAIL_START_TIME';
|
||||||
|
export const SET_LINES_PER_ROW = 'SET_LINES_PER_ROW';
|
||||||
|
export const SET_VIEW_MODE = 'SET_VIEW_MODE';
|
||||||
export const UPDATE_SELECTED_FIELDS = 'LOGS_UPDATE_SELECTED_FIELDS';
|
export const UPDATE_SELECTED_FIELDS = 'LOGS_UPDATE_SELECTED_FIELDS';
|
||||||
export const UPDATE_INTERESTING_FIELDS = 'LOGS_UPDATE_INTERESTING_FIELDS';
|
export const UPDATE_INTERESTING_FIELDS = 'LOGS_UPDATE_INTERESTING_FIELDS';
|
||||||
|
|
||||||
@ -118,6 +121,15 @@ export interface SetLiveTailStartTime {
|
|||||||
payload: number;
|
payload: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SetLinesPerRow {
|
||||||
|
type: typeof SET_LINES_PER_ROW;
|
||||||
|
payload: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetViewMode {
|
||||||
|
type: typeof SET_VIEW_MODE;
|
||||||
|
payload: LogViewMode;
|
||||||
|
}
|
||||||
type IFieldType = 'interesting' | 'selected';
|
type IFieldType = 'interesting' | 'selected';
|
||||||
|
|
||||||
export interface UpdateSelectedInterestFields {
|
export interface UpdateSelectedInterestFields {
|
||||||
@ -149,4 +161,6 @@ export type LogsActions =
|
|||||||
| StopLiveTail
|
| StopLiveTail
|
||||||
| FlushLogs
|
| FlushLogs
|
||||||
| SetLiveTailStartTime
|
| SetLiveTailStartTime
|
||||||
|
| SetLinesPerRow
|
||||||
|
| SetViewMode
|
||||||
| UpdateSelectedInterestFields;
|
| UpdateSelectedInterestFields;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { LogViewMode } from 'container/LogsTable';
|
||||||
import { ILogQLParsedQueryItem } from 'lib/logql/types';
|
import { ILogQLParsedQueryItem } from 'lib/logql/types';
|
||||||
import { IFields } from 'types/api/logs/fields';
|
import { IFields } from 'types/api/logs/fields';
|
||||||
import { TLogsLiveTailState } from 'types/api/logs/liveTail';
|
import { TLogsLiveTailState } from 'types/api/logs/liveTail';
|
||||||
@ -12,6 +13,8 @@ export interface ILogsReducer {
|
|||||||
};
|
};
|
||||||
logs: ILog[];
|
logs: ILog[];
|
||||||
logLinesPerPage: number;
|
logLinesPerPage: number;
|
||||||
|
linesPerRow: number;
|
||||||
|
viewMode: LogViewMode;
|
||||||
idEnd: string;
|
idEnd: string;
|
||||||
idStart: string;
|
idStart: string;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user